Skip to content

force_unlock_after_fork#7186

Merged
youknowone merged 2 commits intoRustPython:mainfrom
youknowone:after-fork
Feb 20, 2026
Merged

force_unlock_after_fork#7186
youknowone merged 2 commits intoRustPython:mainfrom
youknowone:after-fork

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Feb 17, 2026

Summary by CodeRabbit

  • Chores

    • Enhanced fork handling to prevent potential deadlocks by ensuring proper cleanup of internal locks after process forking, improving stability in multi-threaded scenarios.
  • Style

    • Minor formatting improvements.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

This PR adds post-fork unlock mechanisms to prevent deadlocks in child processes. It introduces unsafe force_unlock_after_fork() methods on CodecsRegistry and StringPool, then uses them to unlock various global VM state locks in the POSIX fork handler. Thread cleanup logic is updated to forcefully release locks when needed before proceeding with cleanup operations.

Changes

Cohort / File(s) Summary
Lock/Registry Force-Unlock Methods
crates/vm/src/codecs.rs, crates/vm/src/intern.rs
Add unsafe force_unlock_after_fork() methods to CodecsRegistry and StringPool that repeatedly unlock read locks or forcefully unlock write locks held by dead threads until a write lock can be acquired.
Post-Fork Lock Cleanup
crates/vm/src/stdlib/posix.rs
Introduces unsafe helper to force-unlock PyMutex instances and calls it on string pool, codec registry, atexit funcs, fork-related thread lists, and global trace/profile state after fork. Replaces try_lock fallback with direct lock-and-clone for after_forkers_child.
Thread Handle Cleanup
crates/vm/src/stdlib/thread.rs
Updates thread_handles and shutdown_handles cleanup in after_fork_child to check if locks are held via try_lock, force-unlock if needed, then proceed with guarded cleanup that marks non-current threads as Done and notifies waiters when possible.
Formatting
crates/vm/src/stdlib/sys.rs
Minor single-line formatting adjustment to excepthook binding; no functional changes.

Sequence Diagram

sequenceDiagram
    participant Parent as Parent Process
    participant Fork as Fork Operation
    participant Child as Child Process
    participant Locks as VM State Locks
    participant Cleanup as Cleanup Handler

    Parent->>Fork: Call fork()
    Fork->>Child: Create child, copy memory/locks
    Fork-->>Parent: Return to parent
    
    Child->>Cleanup: Begin child initialization
    Cleanup->>Locks: Check if write lock available (try_lock)
    alt Lock held by dead thread
        Cleanup->>Locks: force_unlock_after_fork()
        Locks->>Locks: Repeatedly unlock read locks
        Locks->>Locks: Or force unlock write lock
        Locks->>Locks: Acquire clean write lock
    else Lock available
        Locks-->>Cleanup: Write lock acquired
    end
    
    Cleanup->>Locks: Unlock and proceed
    Cleanup->>Child: Resume child execution
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Use try_lock in py_os_after_fork_child #7178: Modifies after-fork child handling in posix.py around acquiring the after_forkers_child lock; this PR replaces its try_lock fallback with forced unlocking and direct clone semantics.
  • clear_after_fork #6933: Modifies Unix child-after-fork initialization in posix.rs to reset global VM state; this PR adds complementary force-unlock logic for various mutexed registries in the same path.
  • Fix ssl shutdown #6871: Updates after_fork_child logic in thread.rs for shutdown_handles cleanup; this PR extends that function with force-unlock patterns for both thread_handles and shutdown_handles.

Poem

🐰 After the fork, the locks do run,
But child threads are gone, leaving locks undone.
With force_unlock() we set them free,
No deadlock haunts the child, wheee! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'force_unlock_after_fork' directly and clearly describes the primary change: adding force_unlock_after_fork methods across multiple lock types (CodecsRegistry, StringPool) and their integration into the fork child cleanup paths.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

Code has been automatically formatted

The code in this PR has been formatted using:

  • cargo fmt --all
    Please pull the latest changes before pushing again:
git pull origin after-fork

@github-actions
Copy link
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[ ] lib: cpython/Lib/ctypes
[ ] test: cpython/Lib/test/test_ctypes (TODO: 17)
[x] test: cpython/Lib/test/test_stable_abi_ctypes.py

dependencies:

  • ctypes (native: _ctypes, ctypes._endian, ctypes.macholib.dylib, ctypes.macholib.framework, itertools, sys)
    • subprocess (native: builtins, errno, sys, time)
    • sysconfig (native: os.path, sys)
    • types
    • os, re, shutil, struct, warnings

dependent tests: (1 tests)

  • ctypes: test_ctypes

[x] lib: cpython/Lib/inspect.py
[ ] test: cpython/Lib/test/test_inspect (TODO: 48)

dependencies:

  • inspect

dependent tests: (45 tests)

  • inspect: test_abc test_argparse test_asyncgen test_buffer test_builtin test_code test_collections test_coroutines test_decimal test_enum test_functools test_generators test_grammar test_inspect test_ntpath test_operator test_patma test_posixpath test_pydoc test_signal test_traceback test_type_annotations test_types test_typing test_unittest test_yield_from test_zipimport
    • asyncio: test_asyncio test_contextlib_async test_logging test_os test_sys_settrace test_unittest
    • bdb: test_bdb
    • dataclasses: test__colorize test_ctypes test_genericalias test_pprint test_regrtest test_zoneinfo
    • importlib.metadata: test_importlib
    • pydoc:
      • xmlrpc.server: test_docxmlrpc test_xmlrpc
    • rlcompleter: test_rlcompleter
    • trace: test_trace

[ ] lib: cpython/Lib/multiprocessing
[ ] test: cpython/Lib/test/test_multiprocessing_fork (TODO: 35)
[ ] test: cpython/Lib/test/test_multiprocessing_forkserver (TODO: 10)
[ ] test: cpython/Lib/test/test_multiprocessing_spawn (TODO: 13)
[x] test: cpython/Lib/test/test_multiprocessing_main_handling.py

dependencies:

  • multiprocessing (native: _multiprocessing, _winapi, array, atexit, collections.abc, errno, itertools, mmap, msvcrt, sys, time)
    • collections (native: _weakref, itertools, sys)
    • json (native: json.tool, sys)
    • pickle (native: itertools, sys)
    • tempfile (native: _thread, errno, sys)
    • ctypes, subprocess, types
    • _weakrefset, abc, base64, bisect, copyreg, functools, io, os, queue, runpy, secrets, selectors, signal, socket, struct, threading, traceback, warnings, weakref

dependent tests: (5 tests)

  • multiprocessing: test_asyncio test_concurrent_futures test_fcntl test_multiprocessing_main_handling
    • concurrent.futures.process: test_concurrent_futures

[ ] lib: cpython/Lib/collections
[x] lib: cpython/Lib/_collections_abc.py
[x] test: cpython/Lib/test/test_collections.py (TODO: 3)
[x] test: cpython/Lib/test/test_deque.py (TODO: 3)
[x] test: cpython/Lib/test/test_defaultdict.py (TODO: 1)
[ ] test: cpython/Lib/test/test_ordered_dict.py (TODO: 4)

dependencies:

  • collections

dependent tests: (196 tests)

  • collections: test_annotationlib test_array test_asyncio test_bisect test_builtin test_c_locale_coercion test_call test_collections test_configparser test_contains test_csv test_ctypes test_defaultdict test_deque test_dict test_dictviews test_enum test_exception_group test_file test_fileinput test_fileio test_functools test_genericalias test_hash test_httpservers test_inspect test_io test_ipaddress test_iter test_iterlen test_json test_ordered_dict test_pathlib test_patma test_pickle test_plistlib test_pprint test_pydoc test_random test_set test_shelve test_sqlite3 test_statistics test_string test_struct test_traceback test_types test_typing test_unittest test_urllib test_userdict test_userlist test_userstring test_weakref test_weakset test_with
    • asyncio: test_asyncio test_contextlib_async test_logging test_os test_sys_settrace test_unittest
    • concurrent.futures._base: test_concurrent_futures
    • dbm.dumb: test_dbm_dumb
    • dbm.sqlite3: test_dbm_sqlite3
    • difflib: test_difflib
    • dis: test__opcode test_ast test_code test_compile test_compiler_assemble test_dis test_dtrace test_fstring test_opcache test_peepholer test_positional_only_arg
      • inspect: test_abc test_argparse test_asyncgen test_buffer test_coroutines test_decimal test_generators test_grammar test_ntpath test_operator test_posixpath test_signal test_type_annotations test_yield_from test_zipimport
      • trace: test_trace
    • http.client: test_docxmlrpc test_hashlib test_ssl test_ucn test_unicodedata test_urllib2 test_wsgiref test_xmlrpc
      • urllib.request: test_http_cookiejar test_site test_urllib2_localnet test_urllib2net test_urllibnet
    • importlib.metadata: test_importlib test_zoneinfo
    • inspect:
      • bdb: test_bdb
      • dataclasses: test__colorize test_ctypes test_regrtest
      • rlcompleter: test_rlcompleter
    • multiprocessing: test_concurrent_futures test_fcntl test_multiprocessing_main_handling
    • pkgutil: test_pkgutil
    • platform: test__locale test__osx_support test_android test_baseexception test_cmath test_ctypes test_math test_mimetypes test_platform test_posix test_socket test_sysconfig test_time test_winreg
    • pprint: test_htmlparser test_sys_setprofile
    • queue: test_concurrent_futures test_dummy_thread test_sched
    • selectors: test_selectors test_subprocess
      • socketserver: test_imaplib test_socketserver
    • shutil: test_bz2 test_compileall test_ctypes test_filecmp test_glob test_importlib test_largefile test_launcher test_py_compile test_reprlib test_shutil test_string_literals test_support test_tempfile test_venv
      • ctypes.util: test_ctypes
      • ensurepip: test_ensurepip
      • tarfile: test_tarfile
      • tempfile: test_bytes test_cmd_line test_contextlib test_doctest test_faulthandler test_importlib test_linecache test_mailbox test_pkg test_runpy test_tabnanny test_termios test_threadedtempfile test_tomllib test_urllib_response test_winconsoleio test_zipapp test_zipfile test_zipfile64 test_zstd
      • webbrowser: test_webbrowser
      • zipfile: test_zipfile
    • tokenize: test_tokenize test_unparse
      • traceback: test_code_module test_dictcomps test_importlib test_listcomps test_pyexpat test_setcomps test_threading test_unittest
    • traceback:
      • py_compile: test_cmd_line_script test_importlib
    • urllib.parse: test_sqlite3 test_urlparse
    • urllib.robotparser: test_robotparser
    • wave: test_wave

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@youknowone youknowone marked this pull request as ready for review February 19, 2026 23:14
@youknowone youknowone merged commit 6ced440 into RustPython:main Feb 20, 2026
14 checks passed
@youknowone youknowone deleted the after-fork branch February 20, 2026 00:53
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.

1 participant

Comments