Skip to content

7833: signal: remember the result of SetConsoleCtrlHandler#46

Open
martin-augment wants to merge 1 commit intomasterfrom
pr-7833-2026-01-04-20-24-55
Open

7833: signal: remember the result of SetConsoleCtrlHandler#46
martin-augment wants to merge 1 commit intomasterfrom
pr-7833-2026-01-04-20-24-55

Conversation

@martin-augment
Copy link
Owner

7833: To review by AI

The unix implementation remembers whether it failed or not, so let's
mirror that for the windows implementation as well. This PR also
simplifies the unix implementation by replacing the (`Once`,
`AtomicBool`) pair with a single `OnceLock<Result<(), i32>>`.
@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

Walkthrough

This change modernizes synchronization primitives in Tokio's signal handling modules by replacing std::sync::Once with std::sync::OnceLock. In the Unix signal module, SignalInfo is refactored to use OnceLock<Result<(), i32>> instead of separate Once and AtomicBool fields, with initialization logic simplified using get_or_init. Similarly, the Windows sys module replaces Once with OnceLock in global signal initialization. Error handling is adjusted to return raw OS error codes through OnceLock and then converted back to io::Error. These changes streamline one-time initialization patterns while maintaining error propagation behavior.

✨ Finishing touches
  • 📝 Generate docstrings

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.

@claude
Copy link

claude bot commented Jan 4, 2026

Code Review: PR 46 - signal: remember the result of SetConsoleCtrlHandler

Thank you for this PR! This is a well-structured refactoring that modernizes the signal initialization code by migrating from Once+AtomicBool to OnceLock.

STRENGTHS:

  1. Better Idiomatic Rust: The use of OnceLock is more idiomatic than the previous Once + AtomicBool pattern
  2. Consistent Cross-Platform Approach: Both Unix and Windows implementations now use the same pattern
  3. Simplified Error Handling: The new implementation correctly stores and propagates initialization failures
  4. Code Reduction: The PR reduces 47 lines and adds only 22

CRITICAL ISSUES:

  1. Unwrap on Error Path (Unix) - tokio/src/signal/unix.rs:293
    Err(e) => Err(e.raw_os_error().unwrap())

    Problem: This will panic if raw_os_error() returns None.
    Recommendation: Use unwrap_or(libc::EINVAL) instead

  2. Unwrap on Error Path (Windows) - tokio/src/signal/windows/sys.rs:93
    Err(io::Error::last_os_error().raw_os_error().unwrap())

    Same Issue: This also unwraps raw_os_error(), which could theoretically panic.
    Recommendation: Use unwrap_or(-1) instead

QUALITY OBSERVATIONS:

  1. Closure Parameter Discrepancy (Unix) - tokio/src/signal/unix.rs:290
    The function now uses register_unchecked instead of register and takes a parameter being ignored.
    Please verify this aligns with the signal_hook_registry API.

SECURITY:

  • OnceLock provides proper synchronization (good)
  • Unix signal handler remains async-signal-safe (good)

TEST COVERAGE:

  • Missing tests for failure cases (initialization failures, error caching)
  • Recommendation: Add tests that verify initialization failure caching behavior

DOCUMENTATION:

  • PR description should clarify this affects both Windows AND Unix implementations
  • Should mention it fixes retry behavior and modernizes to OnceLock

PERFORMANCE:

  • Performance-neutral or slightly better
  • Eliminates one atomic operation

SUMMARY:
Must Fix Before Merge:

  1. Replace .unwrap() calls on raw_os_error() with .unwrap_or() to avoid potential panics

Should Address:
2. Verify that register_unchecked is the correct function
3. Add tests for error path caching
4. Improve PR description

Overall, this is a solid refactoring. With the unwrap issues fixed, this will be ready to merge!

@augmentcode
Copy link

augmentcode bot commented Jan 4, 2026

🤖 Augment PR Summary

Summary: Makes one-time signal handler initialization remember and reuse the first initialization result, so later callers don’t accidentally see success after a failure.

Changes:

  • Unix: replace `Once` + `AtomicBool` in `SignalInfo` with `OnceLock>` so signal registration success/failure is cached.
  • Windows: replace `Once` + local `Option` with `OnceLock>` to persist the result of `SetConsoleCtrlHandler` across calls.

Technical Notes: Caches an OS error code (i32) and converts it back to an io::Error on return.

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 1 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

match signal_hook_registry::register_unchecked(signal, move |_| action(globals, signal))
{
Ok(_) => Ok(()),
Err(e) => Err(e.raw_os_error().unwrap()),
Copy link

Choose a reason for hiding this comment

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

e.raw_os_error().unwrap() can panic if the io::Error returned by signal_hook_registry isn’t backed by an OS errno (i.e., raw_os_error() is None), which would turn a registration failure into a process crash.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

Copy link
Owner Author

Choose a reason for hiding this comment

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

value:useful; category:bug; feedback:The Augment AI reviewer is correct! Using .unwrap() looks safe here because the OS error should always be EINVAL. But to be prevent any eventual panic here it would be better to use .unwrap_or(EINVAL)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a80b4d3 and 4845f1f.

📒 Files selected for processing (2)
  • tokio/src/signal/unix.rs
  • tokio/src/signal/windows/sys.rs
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: martin-augment
Repo: martin-augment/tokio PR: 11
File: tokio/src/util/usdt/stapsdt_aarch64.rs:1-3
Timestamp: 2025-11-04T09:48:44.541Z
Learning: In USDT (User Statically-Defined Tracing) probe implementations in Rust, semaphores must be declared as `static mut` primitive types (e.g., `static mut __usdt_sema_tokio_task__details: u16`), not atomic types. This is required for ABI compatibility with external tracing tools like DTrace and SystemTap, which modify these semaphores directly. The inline assembly code references the raw address of these semaphores, and using Rust atomic types would break the USDT mechanism.
📚 Learning: 2025-11-04T09:48:44.541Z
Learnt from: martin-augment
Repo: martin-augment/tokio PR: 11
File: tokio/src/util/usdt/stapsdt_aarch64.rs:1-3
Timestamp: 2025-11-04T09:48:44.541Z
Learning: In USDT (User Statically-Defined Tracing) probe implementations in Rust, semaphores must be declared as `static mut` primitive types (e.g., `static mut __usdt_sema_tokio_task__details: u16`), not atomic types. This is required for ABI compatibility with external tracing tools like DTrace and SystemTap, which modify these semaphores directly. The inline assembly code references the raw address of these semaphores, and using Rust atomic types would break the USDT mechanism.

Applied to files:

  • tokio/src/signal/unix.rs
📚 Learning: 2025-10-28T15:30:50.368Z
Learnt from: martin-augment
Repo: martin-augment/tokio PR: 9
File: tokio/src/fs/file.rs:824-855
Timestamp: 2025-10-28T15:30:50.368Z
Learning: In io-uring write implementations for tokio::fs::File, writes must loop until the buffer is fully drained. A single Op::write_at call may perform a partial write. The code must: (1) loop calling Op::write_at until buf.is_empty(), (2) return io::ErrorKind::WriteZero when 0 bytes are written, (3) only return Operation::Write(Ok(())) after the buffer is completely empty. This applies to both poll_write and poll_write_vectored implementations.

Applied to files:

  • tokio/src/signal/unix.rs
📚 Learning: 2025-11-05T14:24:03.551Z
Learnt from: martin-augment
Repo: martin-augment/tokio PR: 13
File: tokio-util/src/io/simplex.rs:184-209
Timestamp: 2025-11-05T14:24:03.551Z
Learning: In AsyncWrite implementations, zero-length writes (when buf.is_empty()) should return Poll::Ready(Ok(0)) immediately without blocking or registering wakers. Only non-empty buffers that cannot be written due to backpressure should return Poll::Pending. This prevents indefinite hangs when empty slices are written.

Applied to files:

  • tokio/src/signal/unix.rs
🧬 Code graph analysis (1)
tokio/src/signal/unix.rs (1)
tokio/src/signal/registry.rs (1)
  • globals (156-164)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: claude-review
🔇 Additional comments (4)
tokio/src/signal/unix.rs (2)

17-17: LGTM: Clean import update.

The import change from Once/AtomicBool to OnceLock aligns with the modernized synchronization approach.


241-245: LGTM: Cleaner synchronization primitive.

The refactoring from separate Once and AtomicBool fields to OnceLock<Result<(), i32>> is more idiomatic and simplifies the initialization pattern. The #[derive(Default)] works correctly since both EventInfo and OnceLock implement Default.

tokio/src/signal/windows/sys.rs (2)

2-2: LGTM: Import aligns with refactoring.

The import of OnceLock replaces the previous Once primitive, consistent with the modernization across Unix and Windows signal modules.


87-99: LGTM: Consistent cross-platform initialization pattern.

The refactoring to use OnceLock<Result<(), i32>> mirrors the Unix implementation and simplifies the initialization logic. The .unwrap() on Line 93 is safe here because io::Error::last_os_error() always constructs errors with OS error codes from GetLastError().

The approach ensures that:

  • Initialization happens exactly once
  • Both success and failure results are cached
  • Errors are propagated consistently via from_raw_os_error

Comment on lines +287 to +296
siginfo
.init
.get_or_init(|| unsafe {
match signal_hook_registry::register_unchecked(signal, move |_| action(globals, signal))
{
Ok(_) => Ok(()),
Err(e) => Err(e.raw_os_error().unwrap()),
}
})
.map_err(io::Error::from_raw_os_error)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's verify the code snippet exists and examine the context
cd /tmp && find . -name "unix.rs" -path "*/signal/*" 2>/dev/null | head -5

Repository: martin-augment/tokio

Length of output: 46


🏁 Script executed:

# Search for the signal_hook_registry usage in the repository
rg -n "signal_hook_registry" --type rust -A 3 -B 3

Repository: martin-augment/tokio

Length of output: 1926


🏁 Script executed:

# Look specifically for the register_unchecked usage
rg -n "register_unchecked" --type rust -A 5 -B 2

Repository: martin-augment/tokio

Length of output: 573


🌐 Web query:

signal_hook_registry crate error handling raw_os_error

💡 Result:

Use the Error returned by register as a std::io::Error and call raw_os_error() on it.

Example:

let res = unsafe {
    signal_hook_registry::register(signal_hook::consts::SIGTERM, || { /* ... */ })
};
match res {
    Ok(id) => { /* registered */ }
    Err(e) => {
        if let Some(errno) = e.raw_os_error() {
            eprintln!("register failed: errno = {}", errno);
        } else {
            eprintln!("register failed: {}", e);
        }
    }
}

Reference: signal_hook_registry::register returns Result<SigId, Error> where examples and docs use std::io::Error; use std::io::Error::raw_os_error() to get the OS errno. [1][2]

Sources:

  1. signal_hook_registry::register docs. [1]
  2. signal-hook crate docs / examples showing std::io::Error usage. [2]

Remove .unwrap() from line 293; signal_hook_registry errors don't always contain raw OS error codes.

The call to e.raw_os_error().unwrap() will panic if the error lacks a raw OS error code. The signal_hook_registry documentation shows that raw_os_error() returns Option<i32>, which requires proper handling. Use e.raw_os_error().unwrap_or(1) or similar fallback, or restructure to handle the None case explicitly.

🤖 Prompt for AI Agents
In tokio/src/signal/unix.rs around lines 287 to 296, remove the panic-causing
call to e.raw_os_error().unwrap() and handle the Option properly; replace the
unwrap with a safe fallback (for example e.raw_os_error().unwrap_or(1)) or
otherwise convert the error into a meaningful io::Error (e.g., map None to a
generic errno or io::Error::new(io::ErrorKind::Other, e.to_string())). Ensure
the Err branch never calls unwrap so it cannot panic when raw_os_error() returns
None.

Copy link
Owner Author

Choose a reason for hiding this comment

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

value:useful; category:bug; feedback:The CodeRabbit AI reviewer is correct! Using .unwrap() looks safe here because the OS error should always be EINVAL. But to be prevent any eventual panic here it would be better to use .unwrap_or(EINVAL)

@martin-augment
Copy link
Owner Author

  • Unwrap on Error Path (Unix) - tokio/src/signal/unix.rs:293
    Err(e) => Err(e.raw_os_error().unwrap())
    Problem: This will panic if raw_os_error() returns None.
    Recommendation: Use unwrap_or(libc::EINVAL) instead

value:useful; category:bug; feedback:The Claude AI reviewer is correct! Using .unwrap() looks safe here because the OS error should always be EINVAL. But to be prevent any eventual panic here it would be better to use .unwrap_or(EINVAL)

@martin-augment
Copy link
Owner Author

2. Unwrap on Error Path (Windows) - tokio/src/signal/windows/sys.rs:93
Err(io::Error::last_os_error().raw_os_error().unwrap())
Same Issue: This also unwraps raw_os_error(), which could theoretically panic.
Recommendation: Use unwrap_or(-1) instead

value:useful; category:bug; feedback:The Claude AI reviewer is correct! Using .unwrap() could lead to a panic here. To be prevent any eventual panic here it would be better to use .unwrap_or(-1)

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.

2 participants