Reputation: 358
I have a piece of code which is trying to pause a particular operation. I have a method for pausing the application and another method which resumes the application after a certain timeout value. To achieve this I have a timer thread which runs for a fixed interval of time.
Consider the methods below like this -
def pause_my_operation():
with self._lock:
# check if there is already an existing timer, if present then cancel the timer and start a new timer
# pause the operation
def pausetimeout():
with self._lock:
# check if there is already an existing timer, if present then cancel it.
# resume the operation
The operation has two places in the UI from where it can be paused. Hence, the check of the timer in the pause method.
Now, the problem I am facing is there can be a race between these two functions.If the first pause is being fired some time early and it's about to be expired, i.e., pausetimeout of first pause has just entered the method, but before getting the lock, there is a second call from the UI to pause the operation i.e., pause_my_operation is called and it gets the lock. The second pause_my_operation will simply set an internal event to mark the timer cancelled, but that might not stop the pausetimeout to proceed as it is already being served. As a result of this, the second pause call won't have any effect and it's timer would get cancelled by the timeout call for the first pause.
Any idea how can I solve this problem?
Upvotes: 1
Views: 70
Reputation: 3157
You could create a variable that is incremented by pause_my_operation()
and decremented by pausetimeout()
. Then, pausetimeout()
would only execute its logic if after decrement the variable is 0. With this logic, only the last pausetimeot()
would resume the code.
For example:
def pause_my_operation():
with self._lock:
self._counter += 1
# check if there is already an existing timer, if present then cancel the timer and start a new timer
# pause the operation
def pausetimeout():
with self._lock:
self._counter -= 1
if self._counter == 0:
# check if there is already an existing timer, if present then cancel it.
# resume the operation
Edit
Apparently, this way you get another issue: if you cancel the timer without decrementing the value, then the cleanup code never fires. To fix that, you should never cancel the old timer, if it is possible, i.e.:
def pause_my_operation():
with self._lock:
self._counter += 1
# start a new timer
# pause the operation
def pausetimeout():
with self._lock:
self._counter -= 1
if self._counter == 0:
# resume the operation
This should not impact the performance, because there will almost always be only one timer at a time.
Upvotes: 2