◐ Shell
reader mode source ↗
Skip to content

Use try_lock in py_os_after_fork_child#7178

Merged
youknowone merged 1 commit into
RustPython:mainfrom
youknowone:after-fork
Feb 17, 2026
Merged

Use try_lock in py_os_after_fork_child#7178
youknowone merged 1 commit into
RustPython:mainfrom
youknowone:after-fork

Conversation

@youknowone

@youknowone youknowone commented Feb 17, 2026

Copy link
Copy Markdown
Member

after_forkers_child.lock() can deadlock in the forked child if another thread held the mutex at the time of fork. Use try_lock and skip at-fork callbacks when the lock is unavailable, matching the pattern used in after_fork_child for thread_handles.

Summary by CodeRabbit

  • Bug Fixes

    • Safer handling of fork callbacks to ensure after-fork callbacks run reliably in child processes.
    • More robust environment construction for exec/execve to reduce errors when building process environments.
  • Refactor

    • Centralized environment-to-dictionary conversion into the OS layer for consistent, safer environment handling across platforms.

@coderabbitai

coderabbitai Bot commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Centralized environment-mapping→dict conversion into crate::stdlib::os::envobj_to_dict and replaced local helpers in posix.rs and nt.rs. In posix.rs, changed child after-fork callback acquisition to try a non-blocking lock with a force-unlock fallback before cloning the callback list.

Changes

Cohort / File(s) Summary
POSIX fork handling
crates/vm/src/stdlib/posix.rs
Use try_lock() then fallback to force-unlock+re-lock to obtain and clone after_forkers_child before executing callbacks in the child process (replaced direct clone from locked guard).
Windows NT env conversion
crates/vm/src/stdlib/nt.rs
Removed local envobj_to_dict helper; call sites updated to crate::stdlib::os::envobj_to_dict(env, vm) to obtain the environment dict.
Shared env conversion
crates/vm/src/stdlib/os.rs
Added pub(crate) fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult<PyDictRef> that returns an optimized PyDictRef for environment mappings (returns internal dict if available, inspects _data, or builds a dict by enumerating keys).
Imports/exports
crates/vm/src/lib.rs (imports)
Updated imports/exports to include ArgMapping and PyDictRef used by the new envobj_to_dict.

Sequence Diagram(s)

sequenceDiagram
    participant Parent
    participant Fork as fork()
    participant Child
    participant Lock as after_fork_lock
    participant Callbacks

    Parent->>Fork: fork()
    Fork-->>Child: child process starts
    Child->>Lock: try_lock() (non-blocking)
    alt lock acquired
        Lock-->>Child: guard
    else lock busy
        Child->>Lock: force_unlock state
        Child->>Lock: lock() (blocking)
        Lock-->>Child: guard
    end
    Child->>Callbacks: clone callback list from guard
    Child->>Callbacks: execute after-fork callbacks
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
I hopped through locks in forked daylight,
Moved env maps into one tidy night,
I try-locked, forced when things were tight,
Cloned callbacks, then set them right,
A tiny hop, a cleaner byte.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title focuses on using try_lock in py_os_after_fork_child, but the changeset also involves significant refactoring to consolidate envobj_to_dict across modules, which is not mentioned in the title. Consider updating the title to reflect the main changes, such as 'Use try_lock in py_os_after_fork_child and consolidate envobj_to_dict' or split into separate PRs if the changes are unrelated.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

@youknowone youknowone marked this pull request as ready for review February 17, 2026 12:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hide comment

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/stdlib/posix.rs`:
- Around line 719-727: The unsafe recovery using
vm.state.after_forkers_child.force_unlock() should be removed; instead, when
vm.state.after_forkers_child.try_lock() returns None in the forked child, treat
callbacks as optional and skip them (e.g., return an empty clone/Vec or no-op)
rather than forcing the mutex open. Update the match on
vm.state.after_forkers_child.try_lock() so the None branch does not call unsafe
force_unlock() but returns a safe empty value or sentinel so downstream code
that uses after_forkers_child simply sees "no callbacks" and proceeds safely.

after_forkers_child.lock() can deadlock in the forked child
if another thread held the mutex at the time of fork.
Use try_lock and skip at-fork callbacks when the lock is
unavailable, matching the pattern used in after_fork_child
for thread_handles.
Hide details View details @youknowone youknowone merged commit e81a0fc into RustPython:main Feb 17, 2026
13 checks passed
@youknowone youknowone deleted the after-fork branch February 17, 2026 17:24
This was referenced Feb 19, 2026
youknowone added a commit to youknowone/RustPython that referenced this pull request Mar 22, 2026
after_forkers_child.lock() can deadlock in the forked child
if another thread held the mutex at the time of fork.
Use try_lock and skip at-fork callbacks when the lock is
unavailable, matching the pattern used in after_fork_child
for thread_handles.
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