Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix processEvents() reentrancy bug in progress manager window handling #3030

Merged
merged 3 commits into from Feb 27, 2024

Conversation

kieranlblack
Copy link
Contributor

My fix in #3029 was not fully correct, this PR should be a corrected version though.

@dae I was just thinking about this and I believe even though it seems to fix the being reported, the code I submitted still isn't fully free of race conditions due to the reentrancy of processEvents(). Consider the following scenario:

  1. finish() is called with _levels = 1
  2. _closeWin() is called and yields control via processEvents()
  3. start() is called and increments _levels to 2
  4. processEvents() returns
  5. _win is cancelled and set to None
  6. _levels is set to 0

So we end up in a state where _levels = 0 yet there is still a ProgressDialog active. It is a similar issue to as before just wandered into in a different way. Here we would never end up calling cancel() or isdeleted() on a ProgressDialog which is None but still have the issue of getting orphaned windows.

The reentrancy is somewhat tricky, I suppose we need to check the _levels "semaphore" on gaining control again. We can make the metaphorical bandaid slightly larger or remove it like you were saying. Some ideas:

  • we move the window presented check into the start finish() and out of _closeWin() and do it first before updating any internal state, this way after finishing the check we are guaranteed that the value we read for _levels won't change
  • we have _closeWin() check _levels on reentry, if it isn't 1 then we return False and can bail out in finish() after setting _levels appropriately
  • we use taskman to wait until the window has been shown for a while and then on the done callback check all the other logic from the main thread, this removes the usage of processEvents()

Do you feel one of these is significantly better than the others?

I implemented the third option from the list of ideas as it removed the processEvents() call but am more than happy to pivot to something else entirely if you want! Just lmk.

I tested this by playing around with the app for a little bit and inserting artificial delays in an addon. The path where the window was never shown and where we need to delay for half a second both seem to be working. No infinite Processing... windows got stuck open.

@kieranlblack kieranlblack force-pushed the progress-manager-race-pt-2 branch 2 times, most recently from 5f96bb4 to 53b14cf Compare February 25, 2024 16:48
Copy link
Member

@dae dae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing processEvents() is probably the best path forward. Thank you for looking into this!

if self._shown:
elapsed_time = time.monotonic() - self._shown
if (time_to_wait := 0.5 - elapsed_time) > 0:
self.mw.taskman.run_in_background(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using sleep() in a background thread could be done more simply with self.single_shot()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice that is perfect!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dae I just looked at this a little more and we can't actually use single_shot here since it wraps the callback and won't call it until _levels is 0 which is the whole point of our callback :/ I'll stick to the threadpool for now and add a comment about it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to lead you down the wrong path there - I'd forgotten about that check.

qt/aqt/progress.py Show resolved Hide resolved
@dae
Copy link
Member

dae commented Feb 27, 2024

Thanks for this Kieran!

@dae dae merged commit 7784321 into ankitects:main Feb 27, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants