-
-
Notifications
You must be signed in to change notification settings - Fork 731
Fix deadlocks and crashes seen on free-threaded Python #1133
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
base: master
Are you sure you want to change the base?
Changes from 4 commits
f476645
4bd5909
8bd96b4
0518749
722b2ac
8d496db
35e585a
7a49c97
d616afa
cda5a88
e7796de
8742748
c813d13
a65e950
f6eac57
a8772e2
4e7f9ef
c94854a
c903cfa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -189,6 +189,7 @@ def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: | |
| super().__init__() | ||
| self._event_queue = EventQueue() | ||
| self._timeout = timeout | ||
| self._lock = threading.RLock() | ||
|
|
||
| @property | ||
| def timeout(self) -> float: | ||
|
|
@@ -222,6 +223,9 @@ def dispatch_events(self, event_queue: EventQueue) -> None: | |
|
|
||
| def run(self) -> None: | ||
| while self.should_keep_running(): | ||
| with self._lock: | ||
| if not self.should_keep_running(): | ||
| return | ||
| try: | ||
| self.dispatch_events(self.event_queue) | ||
| except queue.Empty: | ||
|
|
@@ -234,7 +238,6 @@ class BaseObserver(EventDispatcher): | |
| def __init__(self, emitter_class: type[EventEmitter], *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: | ||
| super().__init__(timeout=timeout) | ||
| self._emitter_class = emitter_class | ||
| self._lock = threading.RLock() | ||
| self._watches: set[ObservedWatch] = set() | ||
| self._handlers: defaultdict[ObservedWatch, set[FileSystemEventHandler]] = defaultdict(set) | ||
| self._emitters: set[EventEmitter] = set() | ||
|
|
@@ -272,12 +275,15 @@ def emitters(self) -> set[EventEmitter]: | |
| return self._emitters | ||
|
|
||
| def start(self) -> None: | ||
| for emitter in self._emitters.copy(): | ||
| try: | ||
| emitter.start() | ||
| except Exception: | ||
| self._remove_emitter(emitter) | ||
| raise | ||
| with self._lock: | ||
| for emitter in self._emitters.copy(): | ||
| if not self.should_keep_running(): | ||
| break | ||
| try: | ||
| emitter.start() | ||
| except Exception: | ||
| self._remove_emitter(emitter) | ||
| raise | ||
| super().start() | ||
|
|
||
| def schedule( | ||
|
|
@@ -390,17 +396,21 @@ def on_thread_stop(self) -> None: | |
| self.unschedule_all() | ||
|
|
||
| def dispatch_events(self, event_queue: EventQueue) -> None: | ||
| with self._lock: | ||
| if not self.should_keep_running(): | ||
| return | ||
| entry = event_queue.get(block=True) | ||
| if entry is EventDispatcher.stop_event: | ||
| return | ||
|
|
||
| event, watch = entry | ||
|
|
||
| with self._lock: | ||
| # To allow unschedule/stop and safe removal of event handlers | ||
| # within event handlers itself, check if the handler is still | ||
| # registered after every dispatch. | ||
| for handler in self._handlers[watch].copy(): | ||
| if handler in self._handlers[watch]: | ||
| handler.dispatch(event) | ||
| # To allow unschedule/stop and safe removal of event handlers | ||
| # within event handlers itself, check if the handler is still | ||
| # registered after every dispatch. | ||
| for handler in self._handlers[watch].copy(): | ||
|
||
| with self._lock: | ||
| if handler not in self._handlers[watch]: | ||
| continue | ||
| handler.dispatch(event) | ||
|
||
| event_queue.task_done() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,8 +58,10 @@ def start(self) -> None: | |
| sleep(0.01) | ||
|
|
||
| def on_thread_stop(self) -> None: | ||
| if self._whandle: | ||
| close_directory_handle(self._whandle) | ||
| whandle = self._whandle | ||
| if whandle: | ||
| self._whandle = None | ||
|
Comment on lines
+61
to
+63
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand what race this is guarding against, but it seems like it would be better to have a lock around this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is guarding against the file handle re-use crash described here. Let me see if locking also works, since that's a lot more explicit.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried adding explicit locking, but that deadlocks in |
||
| close_directory_handle(whandle) | ||
|
|
||
| def _read_events(self) -> list[WinAPINativeEvent]: | ||
| if not self._whandle: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.