-
-
Notifications
You must be signed in to change notification settings - Fork 704
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
[mac] Fix callback exceptions when the watcher is deleted but still receiving events #786
Conversation
…eceiving events The watcher might be cleared on thread stop but the C callback would still send events. Let's ignore them to prevent unhandled exceptions later: AttributeError: 'NoneType' object has no attribute 'is_recursive' File "watchdog/observers/fsevents.py", line 299, in callback emitter.queue_events(emitter.timeout, events) File "watchdog/observers/fsevents.py", line 261, in queue_events self._queue_created_event(event, src_path, src_dirname) File "watchdog/observers/fsevents.py", line 124, in _queue_created_event self.queue_event(DirModifiedEvent(dirname)) File "watchdog/observers/fsevents.py", line 97, in queue_event if self._watch.is_recursive : Or even: AttributeError: 'NoneType' object has no attribute 'path' File "watchdog/observers/fsevents.py", line 299, in callback emitter.queue_events(emitter.timeout, events) File "watchdog/observers/fsevents.py", line 174, in queue_events src_path = self._encode_path(event.path) File "watchdog/observers/fsevents.py", line 323, in _encode_path if isinstance(self.watch.path, bytes): Co-authored-by: Romain Grasland <rgrasland@nuxeo.com>
cc @samschott and @CCP-Aporia. |
Ouch, what a nasty exception. Just a few questions to understand your changes:
In any case, moving the callback out of that inline definition is an improvement in the first place :) |
I would say pending events are lost in any cases, that's just cleaner now. As the code processing events heavily requires |
Those are fair points. It's just that the explicit check for Maybe I just find it odd that the supplied callback, wrapped in extension module as
Similarly, the callback acquires the GIL so calling I certainly can't argue with the issue that you are seeing or with the success of the fix. So I'm happy with the changes! |
My understanding (and it may be wrong) is that the issue happens in the callback, more specifically here: watchdog/src/watchdog/observers/fsevents.py Lines 295 to 299 in 8ebb408
The watcher is stopped while iterating and before sending events to queue_events() . And so the self._watch attribute is set to None . That's a tricky one for sure, and it indeed fixes reported issues. Maybe a better fix is possible, that's why I pinged @CCP-Aporia and you :)
|
I'm in agreement with @samschott that the explicit |
Of course, go ahead :) |
These tests were great, indeed. Thank you for those! :-) I've fixed the issue in my fork and opened a PR to update your branch here, see #788 |
Co-authored-by: Thomas <thomas@ccpgames.com>
Thanks a lot @CCP-Aporia 🍾 If the review is OK for you both, let's merge when the CI will be green. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @CCP-Aporia, this looks much cleaner :)
So, just to understand why those changes work:
- The workaround with setting
self._watch = None
was added to prevent issues whenon_thread_stop
is called twice because the C extension did not properly handle the case ofwatchdog_remove_watch
being called twice. - This workaround led to
queue_events
raising errors whenself._watch
isNone
. - It is still possible that
queue_events
is called while the emitter is being stopped but this no longer raises "unhandled" exceptions.
PyObject *streamref_capsule = PyDict_GetItem(watch_to_stream, watch); | ||
if (!streamref_capsule) { | ||
// A watch might have been removed explicitly before, in which case we can simply early out. | ||
Py_RETURN_NONE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When exactly does this case occur?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it says in the comment - if someone explicitly calls remove_watch
twice for the same watch then PyDict_GetItem
won't find it again. Not checking for the nullptr
return here was the main reason for the work-arounds in Python code. 🙂
Effectively, streamref_capsule
being a nullptr
caused the assertion failures for the three fsevents
related API calls that follow, as well as the error message about returning a value with an exception set.
You are right about the 3rd statement @samschott. Without testing now, I guess it simply works because I do not have a strong opinion if we should process those events or not, I am just thinking loud. |
To ease working on it, I could split the PR: a smaller one could be merged ASAP to move out the callback from the inline function. Then that PR will only contains tests + C-fix to ease thinking about a potential fix. |
I don't really have a strong opinion either. My intuition says that those events should be processed since they are triggered before the emitter is stopped. There also should not be any consequences with unexpected events being dispatched to the handlers after stopping and joining the observer thread. So it's really up to you!
Personally I am quite happy with the current fix. Is there any more work to do that's worth splitting off? You can of course split things off to have a cleaner commit history. |
@samschott - your understanding of the fix is correct. 🙂 @BoboTiG - indeed, resetting |
Thank you both, let's do a new release with that fix :) |
The watcher might be cleared on thread stop but the C callback would still send events.
Let's ignore them to prevent unhandled exceptions later:
Or even:
Co-authored-by: Romain Grasland rgrasland@nuxeo.com