Closed SpiffSpaceman closed 2 years ago
I can look into this sometime in the next few days. My hunch is that for this to work with multiple processes, we may need to put a given read+write within an exclusive transaction.
Hello, yes running it within a single transaction seems to solve this.
I tried to look at the code, it seems that try_acquire() supports multiple buckets and perhaps that adds some complications.
My own use is case is very simple since i have only 1 sqlite bucket, and it seems its working. I ran it few times and every time it took about 30s to hit the minute limit and only ran 60 times.
1) i begin transaction after _init_buckets() + 2) commit at the end of the function + 3) call rollback before BucketFullException. There might be a side effect that one of the instance seems to get favored more than other - 40 vs 20 / 35vs25 for ex. But i did start them manually one by one. Not much of an issue for me and my limits will be much higher and will rarely get hit.
But i dont know how that will impact other usecases with multiple buckets. If i try to obtain lock for each bucket before proceeding then there is risk of deadlock and if it try to lock for each bucket individually within the loop, then perhaps the two calls could interfere by satisfying individual rules. Best to wait for your fix.
Thanks for nice library.
Hello, 1) In addition, I also got errors from sqlite3 on multithreaded access in single script. "sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 140152770070272 and this is thread id 140152736384768." I had to pass 'check_same_thread' as False in bucket_kwargs ( + 'isolation_level' = 'EXCLUSIVE' for above ).
2) Next i got another error "sqlite3.OperationalError: cannot start a transaction within a transaction" For this I had to use a threading lock to prevent simultaneous access of try_acquire(). With this it seems to work well for my use case now with multiple processes and multithreaded access within each process.
Both of this was done outside library code, but perhaps could be done internally too if it makes sense. Thanks
Just a note - Monotonic clock apparently resets after power reset - it seems to return time elapsed since last power on.
Because of this, if we try to get lock before restart ( which works ) and then again after power restart, remaining_time in Exception becomes corrupted as it compares values before and after power restart. I can simply use time.time which is good enough for me ( no dst here ), but this is a bit of a gotcha, esp if you need monotonic.
Just a note - Monotonic clock apparently resets after power reset - it seems to return time elapsed since last power on.
Because of this, if we try to get lock before restart ( which works ) and then again after power restart, remaining_time in Exception becomes corrupted as it compares values before and after power restart. I can simply use time.time which is good enough for me ( no dst here ), but this is a bit of a gotcha, esp if you need monotonic.
Interesting finding. I will make a note of this in the document.
Sorry for the delay on this! I started some changes in #61, but no unit tests yet.
I was hoping to be able to make changes only within SQLiteBucket
, but it looks like some changes are needed in Limiter
as well. I added a no-op AbstractBucket.commit()
method, and called that within try_acquire()
. That would also probably be needed for any other SQL-based backends added in the future.
For multi-threaded usage, I'm leaning toward putting the lock in SQLiteBucket
instead of try_acquire()
, since I don't believe the other bucket backends need this. I could be mistaken, though.
Just a note - Monotonic clock apparently resets after power reset - it seems to return time elapsed since last power on.
Interesting, I didn't know that either. @vutran1710 I wonder if we should go back to using time.time()
by default instead of monotonic? Seems like it has a few surprising behaviors, and users who want it can still opt in with time_function
.
Sorry, looks like this may not be fixed after all. I was working with the python multiprocessing
module, which does not behave the same as your example of running from two separate scripts.
Continuing this in #67
Hello, i run below code from two different py files at the same time. It looks like that there might be an issue. For each window, 1 extra call seems to pass through sometimes (ex - 20:54:14 )
At 2/sec and 60/min, i should hit the 60 limit after about 30 seconds. But it seems to happen after around 20 seconds. Also, sometimes 61 calls pass through instead of 60
Output ---