-
Notifications
You must be signed in to change notification settings - Fork 4
feat: Implement process state machine for stop/continue in support for SIGSTOP, SIGCONT, and more waitpid features #4
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
Conversation
|
I'd like to add a license declaration to this PR as well. |
Improved process state management with composite state model and type safety. Key changes: - Introduced ProcessStateKind + ProcessStateFlags composite state - Encapsulated all transitions in ProcessState methods - Added atomic try_consume_* methods for waitpid event consumption - Introduced ExitCode type to replace magic number (128 + signal) - Extracted reaper_children helper to eliminate code duplication Addresses code review feedback from PR Starry-OS#4.
Improved process state management with composite state model and type safety. Key changes: - Introduced ProcessStateKind + ProcessStateFlags composite state - Encapsulated all transitions in ProcessState methods - Added atomic try_consume_* methods for waitpid event consumption - Introduced ExitCode type to replace magic number (128 + signal) - Extracted reaper_children helper to eliminate code duplication Addresses code review feedback from PR Starry-OS#4.
0991c5b to
8391dc5
Compare
There is a license type declared in the license = "MIT OR Apache-2.0" |
\- Fix license header in by removing placeholder authors. \- Fix to accept to handle both signal and ptrace continuations correctly without code duplication. \- Fix lint in by using a match expression. \- Add comprehensive integration tests in for: \- Stop/Continue lifecycle (SIGSTOP/SIGCONT). \- Ptrace stop/resume (ensuring no continued event is generated). \- Exit with signal (verifying exit code and signal info).
|
I have also updated some unit tests and integrated tests for these added process state machine methods in the tests directory. |
src/process.rs
Outdated
| @@ -1,13 +1,93 @@ | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
| // Copyright (C) 2025 KylinSoft Co., Ltd. https://www.kylinos.cn/ | |||
| // Copyright (C) 2025 Azure-stars Azure_stars@126.com | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No this crate was not written by @Azure-stars 🤣
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad.
Now I would change the copyright declaration from Azure-stars to AsakuraMizu based on the author information in Cargo.toml. My apologies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a comprehensive process state machine to support SIGSTOP/SIGCONT signals and enhanced waitpid features. The implementation replaces a simple boolean is_zombie flag with a rich state system that tracks process lifecycle (Running, Stopped, Zombie) and provides event acknowledgment mechanisms for proper parent notification.
Key Changes
- Introduced
ProcessStatewithProcessStateKindenum (Running/Stopped/Zombie) andProcessStateFlagsbitflags for event tracking - Added detailed zombie information (
ZombieInfo) capturing exit codes, terminating signals, and core dump status - Implemented ptrace stop support with distinction between signal-stops and ptrace-stops
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/process.rs | Core implementation of the state machine with ProcessState, ProcessStateKind, ProcessStateFlags, ExitCode, ZombieInfo types; added state transition methods, stop/continue/ptrace APIs, and comprehensive module documentation |
| tests/process.rs | Added integration tests for stop/continue transitions, ptrace stops, and signal-based process termination |
| tests/session.rs | Minor import statement consolidation |
| src/lib.rs | Exported new public types (ProcessState, ZombieInfo) |
| Cargo.toml | Added bitflags dependency for state flag management |
| LICENSES | Added Apache License 2.0 text |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…t code \- Correct license header in with correct author information. \- Export in . \- Remove redundant check in as it is implied by the state. \- Fix various typos in documentation comments (e.g., stoppade, ptrace.s). \- Correct documentation for to accurately reflect that it checks for unacked continuations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 12 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // has not been acked by its parent | ||
| const STOPPED_UNACKED = 1 << 0; | ||
| // A status of a process who has just continued but its continuation | ||
| // has not been acked by its parent |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent terminology: The comment uses "acked" (abbreviated form) instead of "acknowledged", which is used throughout the rest of the module documentation (e.g., lines 25, 57, 634, 656). For consistency and formality, use "acknowledged" instead of "acked".
| // has not been acked by its parent | |
| const STOPPED_UNACKED = 1 << 0; | |
| // A status of a process who has just continued but its continuation | |
| // has not been acked by its parent | |
| // has not been acknowledged by its parent | |
| const STOPPED_UNACKED = 1 << 0; | |
| // A status of a process who has just continued but its continuation | |
| // has not been acknowledged by its parent |
| pub fn transition_to_running(&mut self, with_continued_flag: bool) { | ||
| if let ProcessStateKind::Stopped { .. } = self.kind { | ||
| self.kind = ProcessStateKind::Running; | ||
| if with_continued_flag { | ||
| self.flags.insert(ProcessStateFlags::CONTINUED_UNACKED); | ||
| } | ||
| self.flags.remove(ProcessStateFlags::STOPPED_UNACKED); |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The transition_to_running method only transitions from Stopped to Running, silently ignoring calls when the process is already Running or Zombie. This could mask bugs where the method is called on a process in an unexpected state.
For instance, if a process is already running and continue_from_stop is called, this silently does nothing, which might not be the intended behavior. Consider either:
- Returning a
Resultorboolto indicate whether the transition succeeded - Adding an assertion or debug warning for invalid transitions
- Documenting this silent-fail behavior more explicitly in the public API methods that call this
This would help callers detect incorrect usage patterns.
| pub fn transition_to_running(&mut self, with_continued_flag: bool) { | |
| if let ProcessStateKind::Stopped { .. } = self.kind { | |
| self.kind = ProcessStateKind::Running; | |
| if with_continued_flag { | |
| self.flags.insert(ProcessStateFlags::CONTINUED_UNACKED); | |
| } | |
| self.flags.remove(ProcessStateFlags::STOPPED_UNACKED); | |
| /// Returns `true` if the transition from `Stopped` to `Running` occurred, | |
| /// or `false` if the process was not in the `Stopped` state and no transition was made. | |
| pub fn transition_to_running(&mut self, with_continued_flag: bool) -> bool { | |
| if let ProcessStateKind::Stopped { .. } = self.kind { | |
| self.kind = ProcessStateKind::Running; | |
| if with_continued_flag { | |
| self.flags.insert(ProcessStateFlags::CONTINUED_UNACKED); | |
| } | |
| self.flags.remove(ProcessStateFlags::STOPPED_UNACKED); | |
| true | |
| } else { | |
| false |
| /// including both the case which the process just stopped without acked by | ||
| /// its parent and already acked by its parent |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation states "the case which the process just stopped without acked" - this should be "acknowledged" not "acked" for consistency with other documentation in this module (e.g., line 25 uses "acknowledged", line 634 uses "acknowledged").
Additionally, "the case which" should be "the case where" or "the case in which" for better grammar.
| /// including both the case which the process just stopped without acked by | |
| /// its parent and already acked by its parent | |
| /// including both the case where the process just stopped without being acknowledged by | |
| /// its parent and the case where it has already been acknowledged by its parent |
| && state.flags.contains(ProcessStateFlags::CONTINUED_UNACKED) | ||
| } | ||
|
|
||
| /// Updating the status of a process continued from stoppage |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in method name and documentation: "Updating" should be "Updates" to match the imperative style used in other doc comments (e.g., "Terminates" at line 429, "Stops" at line 489, "Transitions" at line 629).
| /// Updating the status of a process continued from stoppage | |
| /// Updates the status of a process continued from stoppage |
| /// `CONTINUED_UNACKED`). | ||
| /// | ||
| /// For a `Stopped` process, if its stoppage has not been acked by its parent, | ||
| /// i.e., the parent has not been notified for the child's stoppage, the |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation mentions "the parent has not been notified for the child's stoppage" - this should be "the parent has not been notified of the child's stoppage" for correct grammar. The preposition "of" is more appropriate than "for" in this context.
| /// i.e., the parent has not been notified for the child's stoppage, the | |
| /// i.e., the parent has not been notified of the child's stoppage, the |
| pub struct ProcessStateFlags: u8 { | ||
| // A status of a process who has stopped but its stoppage | ||
| // has not been acked by its parent | ||
| const STOPPED_UNACKED = 1 << 0; | ||
| // A status of a process who has just continued but its continuation | ||
| // has not been acked by its parent | ||
| const CONTINUED_UNACKED = 1 << 1; | ||
| } | ||
| } |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProcessStateFlags is marked as pub but is not exported in src/lib.rs. This makes it inaccessible to external users. Similar to ProcessStateKind, if this is intended to be internal only, it should be marked as pub(crate) instead of pub to clarify the design intent.
| // A status of a process who has just continued but its continuation | ||
| // has not been acked by its parent | ||
| const CONTINUED_UNACKED = 1 << 1; |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent terminology: The comment uses "acked" (abbreviated form) instead of "acknowledged". For consistency with the rest of the module documentation, use "acknowledged" instead of "acked".
|
|
||
| self.kind = ProcessStateKind::Stopped { signal, ptraced }; | ||
| self.flags.insert(ProcessStateFlags::STOPPED_UNACKED); |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The transition_to_stopped method always sets the STOPPED_UNACKED flag even if the process is already stopped with a different signal. This means that calling stop_by_signal multiple times will keep resetting the unacked flag, potentially causing the same stop to be reported multiple times via waitpid.
For example:
- Process receives SIGSTOP (signal 19) → sets STOPPED_UNACKED
- Parent hasn't called waitpid yet
- Process receives SIGTSTP (signal 20) → sets STOPPED_UNACKED again (overwrites the signal)
This might be intentional behavior, but it should be documented. Alternatively, consider whether the flag should only be set on the first stop transition, not on subsequent re-stops.
| self.kind = ProcessStateKind::Stopped { signal, ptraced }; | |
| self.flags.insert(ProcessStateFlags::STOPPED_UNACKED); | |
| // Only transition to stopped if not already stopped | |
| match self.kind { | |
| ProcessStateKind::Stopped { .. } => { | |
| // Already stopped, do not reset STOPPED_UNACKED or overwrite signal | |
| return; | |
| } | |
| _ => { | |
| self.kind = ProcessStateKind::Stopped { signal, ptraced }; | |
| self.flags.insert(ProcessStateFlags::STOPPED_UNACKED); | |
| } | |
| } |
|
|
||
| /// The full process state machine. | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub struct ProcessState { |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProcessState is marked as pub but is not exported in src/lib.rs, making it inaccessible to external users. The struct's fields are private, and only internal state transition methods are implemented on it.
If ProcessState is meant to be an internal implementation detail (which appears to be the case based on the API design), it should be marked as pub(crate) instead of pub to make this clear and prevent accidental public exposure.
| pub struct ProcessState { | |
| pub(crate) struct ProcessState { |
| /// including both the case which the process just stopped without acked by | ||
| /// its parent and already acked by its parent | ||
| pub fn is_stopped(&self) -> bool { | ||
| let state = self.state.lock(); | ||
| matches!(state.kind, ProcessStateKind::Stopped { .. }) | ||
| } | ||
|
|
||
| /// Check whether the process has continued from the stoppage, | ||
| /// but its continuation has not been acked by its parent |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation states "acked by its parent" - for consistency with the rest of the module documentation, this should use "acknowledged" instead of "acked". The term "acknowledged" is used in other doc comments (lines 25, 57, 634, 656) and is more formal.
| /// including both the case which the process just stopped without acked by | |
| /// its parent and already acked by its parent | |
| pub fn is_stopped(&self) -> bool { | |
| let state = self.state.lock(); | |
| matches!(state.kind, ProcessStateKind::Stopped { .. }) | |
| } | |
| /// Check whether the process has continued from the stoppage, | |
| /// but its continuation has not been acked by its parent | |
| /// including both the case which the process just stopped without being acknowledged by | |
| /// its parent and already acknowledged by its parent | |
| pub fn is_stopped(&self) -> bool { | |
| let state = self.state.lock(); | |
| matches!(state.kind, ProcessStateKind::Stopped { .. }) | |
| } | |
| /// Check whether the process has continued from the stoppage, | |
| /// but its continuation has not been acknowledged by its parent |
|
For the /// Information about a zombie (terminated) process.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZombieInfo {
/// The exit code value passed to exit().
pub exit_code: i32,
/// The signal that terminated the process, if any.
pub signal: Option<i32>,
/// Whether a core dump was produced.
pub core_dumped: bool,
}and implement a function to encode these information, along with those inside impl ProcessState {
...
/// Returns the POSIX wait status word for the current state if there is a
/// reportable event.
///
/// - Stopped + STOPPED_UNACKED: Returns `(signal << 8) | 0x7f`
/// - Running + CONTINUED_UNACKED: Returns `0xffff`
/// - Zombie: Returns encoded exit status per POSIX
/// - Otherwise: Returns `None`
pub fn wait_status(&self) -> Option<i32> {
if self.flags.contains(ProcessStateFlags::STOPPED_UNACKED) {
if let ProcessStateKind::Stopped { signal, .. } = self.kind {
return Some((signal << 8) | 0x7f);
}
}
if self.flags.contains(ProcessStateFlags::CONTINUED_UNACKED) {
if let ProcessStateKind::Running = self.kind {
return Some(0xffff);
}
}
if let ProcessStateKind::Zombie { info } = self.kind {
if let Some(sig) = info.signal {
// WIFSIGNALED: Bits 0-6 are signal, Bit 7 is core dump.
let core_bit = if info.core_dumped { 0x80 } else { 0 };
return Some((sig & 0x7f) | core_bit);
} else {
// WIFEXITED: Bits 8-15 are exit code.
return Some((info.exit_code & 0xff) << 8);
}
}
None
}
}And thus, for the impl Process {
...
/// Returns the POSIX wait status word for the current state if there is a
/// reportable event.
pub fn wait_status(&self) -> Option<i32> {
self.state.lock().wait_status()
}
}The reason why we may do so is that we will leave all the wait status related stuff to wait system call, and taking the original status information of a process here. |
…for future improvements.
ProcessStateEnum:exit_with_signalMethod: