Skip to content

Conversation

@HaoZeke
Copy link
Contributor

@HaoZeke HaoZeke commented May 17, 2025

Closes #1105. Closes #1089. Followup.

Draft until:

  • Tests pass
  • CI is green
  • [ ] Audit is complete

@HaoZeke HaoZeke force-pushed the ftBuild branch 2 times, most recently from 44ef7e6 to e7db077 Compare May 17, 2025 13:54
i.e.

tests/test_emitter.py::test_fast_subdirectory_creation_deletion FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_pickle FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_to_with_context_manager FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_internal FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_replace FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_dir_modify_on_create FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_dir_modify_on_move FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_replace_dir_with_file FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_start_failure_should_not_prevent_further_try FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_failure_should_not_prevent_future_schedules FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_start_should_start_emitter FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_stop_should_stop_emitter FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_unschedule_self FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_after_unschedule_all FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_should_start_emitter_if_running FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_delete_watched_directory_symlink_followed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_delete_watched_directory_symlink_followed_recursive FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_c.py::test_select_fd FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal_symlink_followed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal_batch FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_c.py::test_non_ascii_path FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_closed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_delete FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_modify FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_case_change FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_to_full FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_from_full FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_separate_consecutive_moves FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_fast_subdirectory_creation_deletion FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_recursive_on FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_recursive_off FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_renaming_top_level_directory FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_nested_subdirectories FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_nested_subdirectories_on_windows SKIPPED
tests/test_emitter.py::test_file_lifecyle FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
@HaoZeke HaoZeke changed the title ENH: Free threading support ENH: Baseline free threading support May 25, 2025
@HaoZeke HaoZeke force-pushed the ftBuild branch 3 times, most recently from f13e934 to b4190fa Compare May 25, 2025 18:24
@HaoZeke
Copy link
Contributor Author

HaoZeke commented May 25, 2025

It seems like a lot of the flaky tests seem to arbitrarily fail with the ft build.

e.g. https://github.com/HaoZeke/watchdog/actions/runs/15240749477 passes

but https://github.com/HaoZeke/watchdog/actions/runs/15240754178/job/42860520286 does not

@HaoZeke
Copy link
Contributor Author

HaoZeke commented May 25, 2025

CI is green now.

@HaoZeke
Copy link
Contributor Author

HaoZeke commented May 25, 2025

cc @BoboTiG @rgommers

@BoboTiG
Copy link
Collaborator

BoboTiG commented May 25, 2025

Is it possible to use "py313t" in the current CI file to unify everything?

@HaoZeke
Copy link
Contributor Author

HaoZeke commented May 25, 2025

Is it possible to use "py313t" in the current CI file to unify everything?

Yup, but it might get a it longer / slightly more complex because the coverage removal is only for the FT work. I was thinking it might be cleaner to have them in a separate job (also in case only the FT build is being run, like I often do locally) but I can update this with a single file.

@BoboTiG
Copy link
Collaborator

BoboTiG commented May 25, 2025

Is it possible to use "py313t" in the current CI file to unify everything?

Yup, but it might get a it longer / slightly more complex because the coverage removal is only for the FT work. I was thinking it might be cleaner to have them in a separate job (also in case only the FT build is being run, like I often do locally) but I can update this with a single file.

Lets keep it as-is: it works fine.

@BoboTiG BoboTiG merged commit 1068560 into gorakhargosh:master May 25, 2025
24 of 25 checks passed
@HaoZeke HaoZeke deleted the ftBuild branch May 25, 2025 22:06
@rgommers
Copy link

A comment on the test skips here. pytest-run-parallel auto-detects most thread-unsafe fixtures, including recwarn, but the problem here seems to be that the custom _no_warnings fixture hides it:

@pytest.fixture(autouse=True)
def _no_warnings(recwarn):

There is a way to make the code much more concise though, which is to add __thread_safe__ = False as an attribute of _no_warnings (see the docs here).

Also, I'd suggest actually turning that fixture off when testing under free-threading, since it applies to almost all tests (leaving pytest-run-parallel mostly disabled) and it isn't actually necessary to do those "no warnings" checks in all circumstances - running it on the default 3.13 interpreter will already catch anything that's actually interesting. Turning it off then allows actually checking for most thread-safety issues.

So I'd suggest:

  1. A cleanup PR adding __thread_safe__ = False to remove all the manual skips
  2. As part of the already-planned second PR for free-threading support, use autouse=not sys._is_gil_enabled() for the fixture (with a comment about why).

@HaoZeke
Copy link
Contributor Author

HaoZeke commented May 28, 2025

A comment on the test skips here. pytest-run-parallel auto-detects most thread-unsafe fixtures, including recwarn, but the problem here seems to be that the custom _no_warnings fixture hides it:

@pytest.fixture(autouse=True)
def _no_warnings(recwarn):

There is a way to make the code much more concise though, which is to add __thread_safe__ = False as an attribute of _no_warnings (see the docs here).

Also, I'd suggest actually turning that fixture off when testing under free-threading, since it applies to almost all tests (leaving pytest-run-parallel mostly disabled) and it isn't actually necessary to do those "no warnings" checks in all circumstances - running it on the default 3.13 interpreter will already catch anything that's actually interesting. Turning it off then allows actually checking for most thread-safety issues.

So I'd suggest:

  1. A cleanup PR adding __thread_safe__ = False to remove all the manual skips
  2. As part of the already-planned second PR for free-threading support, use autouse=not sys._is_gil_enabled() for the fixture (with a comment about why).

It seems to be more than _no_warnings, will try to narrow it down.

2 files changed, 5 insertions(+)
pyproject.toml    | 3 +++
tests/conftest.py | 2 ++

modified   pyproject.toml
@@ -38,6 +38,9 @@ disallow_untyped_calls = true
 strict_equality = true
 
 [tool.pytest.ini_options]
+thread_unsafe_fixtures = [
+    '_no_warnings',
+]
 pythonpath = "src"
 addopts = """
     --showlocals
modified   tests/conftest.py
@@ -36,6 +36,8 @@ def _no_thread_leaks():
 def _no_warnings(recwarn):
     """Fail on warning."""
 
+    __thread_safe__ = False
+
     yield
 
     warnings = []

Was tested on CI (and locally). However this doesn't seem prevent failures.

Failure log
tests/test_delayed_queue.py::test_delayed_get PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_delayed_queue.py::test_nondelayed_get PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_echo.py::test_format_arg_value[value0-x=(1, 2, 3)] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_emitter.py::test_create FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_closed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_create_wrong_encoding FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_delete FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_modify FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_chmod PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_emitter.py::test_move FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_case_change FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_move_to_full FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_move_from_full FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_separate_consecutive_moves FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_delete_self PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_emitter.py::test_fast_subdirectory_creation_deletion FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_passing_unicode_should_give_unicode PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_emitter.py::test_passing_bytes_should_give_bytes PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_emitter.py::test_recursive_on FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_recursive_off FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_renaming_top_level_directory FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_move_nested_subdirectories FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_emitter.py::test_move_nested_subdirectories_on_windows SKIPPED
tests/test_emitter.py::test_file_lifecyle FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_events.py::test_file_deleted_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_delete_event_is_directory PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_modified_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_modified_event_is_directory PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_created_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_moved_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_closed_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_closed_no_write_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_opened_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_dir_deleted_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_dir_modified_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_dir_created_event PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_file_system_event_handler_dispatch PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_events.py::test_event_comparison PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_inotify_c.py::test_late_double_deletion FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_c.py::test_raise_error[28-inotify watch limit reached] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_inotify_c.py::test_raise_error[24-inotify instance limit reached] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_inotify_c.py::test_raise_error[2-No such file or directory] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_inotify_c.py::test_raise_error[-1-error] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_inotify_c.py::test_non_ascii_path FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_c.py::test_watch_file PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_inotify_c.py::test_event_equality PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_inotify_c.py::test_select_fd FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_watch_group.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_watch_group.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal_symlink_followed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal_batch FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_watch_group.py::test_delete_watched_directory PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_inotify_watch_group.py::test_delete_watched_directory_symlink_followed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_watch_group.py::test_delete_watched_directory_symlink_followed_recursive FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_inotify_watch_group.py::test_unmount_watched_directory_filesystem SKIPPED
tests/test_inotify_watch_group.py::test_watch_groups_are_independent PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_isolated.py::test_observer_stops_in_eventlet SKIPPED (eventlet not
installed)
tests/test_isolated.py::test_eventlet_skip_repeat_queue SKIPPED (eventlet not
installed)
tests/test_logging_event_handler.py::test_logging_event_handler_dispatch PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observer.py::test_schedule_should_start_emitter_if_running FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_observer.py::test_schedule_should_not_start_emitter_if_not_running PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observer.py::test_start_should_start_emitter FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_observer.py::test_stop_should_stop_emitter FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_observer.py::test_unschedule_self FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_observer.py::test_schedule_after_unschedule_all FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_observer.py::test_2_observers_on_the_same_path PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observer.py::test_start_failure_should_not_prevent_further_try FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_observer.py::test_schedule_failure_should_not_prevent_future_schedules FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_observers_api.py::test_observer_constructor PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observers_api.py::test_observer__eq__ PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observers_api.py::test_observer__ne__ PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observers_api.py::test_observer__repr__ PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observers_api.py::test_event_emitter PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observers_api.py::test_event_dispatcher PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observers_api.py::test_observer_basic PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_observers_polling.py::test___init__ FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_observers_polling.py::test_delete_watched_dir FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_pattern_matching_event_handler.py::test_dispatch PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_pattern_matching_event_handler.py::test_handler PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_pattern_matching_event_handler.py::test_ignore_directories PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_pattern_matching_event_handler.py::test_ignore_patterns PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_pattern_matching_event_handler.py::test_patterns PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_path[/users/gorakhargosh/foobar.py-included_patterns0-excluded_patterns0-True-True] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_path[/users/gorakhargosh/foobar.py-included_patterns1-excluded_patterns1-True-False] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_path[/users/gorakhargosh/-included_patterns2-excluded_patterns2-False-False] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_path[/users/gorakhargosh/foobar.py-included_patterns3-excluded_patterns3-False-ValueError] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_filter_paths[None-None-True-None] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_filter_paths[None-None-False-None] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_filter_paths[included_patterns2-excluded_patterns2-True-expected2] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_any_paths[None-None-True-True] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_any_paths[None-None-False-True] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_any_paths[included_patterns2-excluded_patterns2-True-True] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_any_paths[included_patterns3-None-False-False] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_patterns.py::test_match_any_paths[included_patterns4-None-True-False] PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_regex_matching_event_handler.py::test_dispatch PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_regex_matching_event_handler.py::test_handler PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_regex_matching_event_handler.py::test_ignore_directories PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_regex_matching_event_handler.py::test_ignore_regexes PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_regex_matching_event_handler.py::test_regexes PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_regex_matching_event_handler.py::test_str_regexes PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_regex_matching_event_handler.py::test_logging_event_handler_dispatch PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_skip_repeats_queue.py::test_basic_queue PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_skip_repeats_queue.py::test_allow_nonconsecutive PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_skip_repeats_queue.py::test_put_with_watchdog_events PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_skip_repeats_queue.py::test_prevent_consecutive PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_skip_repeats_queue.py::test_consecutives_allowed_across_empties PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_snapshot_diff.py::test_pickle FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_move_to_with_context_manager FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_move_internal FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_move_replace FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_dir_modify_on_create FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_dir_modify_on_move FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_detect_modify_for_moved_files PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_snapshot_diff.py::test_replace_dir_with_file FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'})
tests/test_snapshot_diff.py::test_permission_error PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_snapshot_diff.py::test_ignore_device PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}
tests/test_snapshot_diff.py::test_empty_snapshot PASSED [thread-unsafe]: uses thread-unsafe fixture(s): {'_no_warnings', 'recwarn'}

Reduces the failures from 54 (the number of added skips) to 45.

@rgommers
Copy link

It would be good to consider what should actually be thread-safe and used from multiple threads - everything in watchdog, or some (possibly small) subset?. I haven't looked very closely, just started from the observation that the skips are a lot of boilerplate and it's not useful to add pytest-run-parallel and then skip every single test.

TobiasRzepka pushed a commit to TobiasRzepka/watchdog that referenced this pull request Oct 11, 2025
* ENH: Start testing free threaded builds on CI

* MAINT: Support both kinds of macos runners

* CI: Disable coverage for ft builds

* TST: Explicitly mark some failing parallel tests

i.e.

tests/test_emitter.py::test_fast_subdirectory_creation_deletion FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_pickle FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_to_with_context_manager FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_internal FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_replace FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_dir_modify_on_create FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_dir_modify_on_move FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_replace_dir_with_file FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_start_failure_should_not_prevent_further_try FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_failure_should_not_prevent_future_schedules FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_start_should_start_emitter FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_stop_should_stop_emitter FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_unschedule_self FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_after_unschedule_all FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_should_start_emitter_if_running FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_delete_watched_directory_symlink_followed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_delete_watched_directory_symlink_followed_recursive FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_c.py::test_select_fd FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal_symlink_followed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal_batch FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_c.py::test_non_ascii_path FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_closed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_delete FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_modify FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_case_change FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_to_full FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_from_full FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_separate_consecutive_moves FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_fast_subdirectory_creation_deletion FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_recursive_on FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_recursive_off FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_renaming_top_level_directory FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_nested_subdirectories FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_nested_subdirectories_on_windows SKIPPED
tests/test_emitter.py::test_file_lifecyle FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})

* DOC: Minor update

* TST: Mark more tests for free threaded

* MAINT: Update to reduce warnings
TobiasRzepka pushed a commit to TobiasRzepka/watchdog that referenced this pull request Oct 11, 2025
* ENH: Start testing free threaded builds on CI

* MAINT: Support both kinds of macos runners

* CI: Disable coverage for ft builds

* TST: Explicitly mark some failing parallel tests

i.e.

tests/test_emitter.py::test_fast_subdirectory_creation_deletion FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_pickle FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_to_with_context_manager FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_internal FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_move_replace FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_dir_modify_on_create FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_dir_modify_on_move FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_snapshot_diff.py::test_replace_dir_with_file FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_start_failure_should_not_prevent_further_try FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_failure_should_not_prevent_future_schedules FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_start_should_start_emitter FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_stop_should_stop_emitter FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_unschedule_self FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_after_unschedule_all FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_observer.py::test_schedule_should_start_emitter_if_running FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_delete_watched_directory_symlink_followed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_delete_watched_directory_symlink_followed_recursive FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_c.py::test_select_fd FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal_symlink_followed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_watch_group.py::test_move_internal_batch FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_inotify_c.py::test_non_ascii_path FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_closed FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_delete FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_modify FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_case_change FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_to FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_to_full FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_from FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_from_full FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_separate_consecutive_moves FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_fast_subdirectory_creation_deletion FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_recursive_on FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_recursive_off FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_renaming_top_level_directory FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_nested_subdirectories FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})
tests/test_emitter.py::test_move_nested_subdirectories_on_windows SKIPPED
tests/test_emitter.py::test_file_lifecyle FAILED ([thread-unsafe]: uses thread-unsafe fixture(s): {'recwarn'})

* DOC: Minor update

* TST: Mark more tests for free threaded

* MAINT: Update to reduce warnings
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.

ENH: Free-Threading (No-GIL) Support for Python 3.13+ Is watchdog safe to run without GIL?

3 participants