The Process Pipe module provides cross-platform process spawning and inter-process communication (IPC) for the Microstack runtime. It abstracts platform differences between Windows and POSIX systems and integrates tightly with the Microstack chain/event loop to deliver non-blocking, event-driven process I/O.
At its core, Process Pipe enables:
- Spawning child processes (interactive, detached, or terminal-backed)
- Asynchronous reading from
stdoutandstderr - Asynchronous writing to
stdin - Cross-platform pipe management (Win32 overlapped I/O and POSIX non-blocking pipes)
- Graceful and forced process termination
- Integration with the Microstack chain scheduler
Process Pipe is part of the Microstack Core and builds on:
- [Parsers and Chain](../Parsers and Chain/Parsers and Chain.md)
- [Remote Logging](../Remote Logging/Remote Logging.md)
Process Pipe is composed of three primary object types:
- Manager – Integrates with the chain and tracks active pipes
- Process Object – Represents a spawned OS process
- Pipe Object – Represents a single I/O channel (stdin, stdout, stderr, or custom pipe)
flowchart TD
Chain["Microstack Chain"] --> Manager["ILibProcessPipe_Manager_Object"]
Manager --> Process["ILibProcessPipe_Process_Object"]
Process --> StdIn["PipeObject (stdin)"]
Process --> StdOut["PipeObject (stdout)"]
Process --> StdErr["PipeObject (stderr)"]
StdOut --> ReadHandler["Generic Read Handler"]
StdIn --> WriteQueue["Write Buffer Queue"]
The Manager integrates Process Pipe into the Microstack chain.
Responsibilities:
- Maintains a linked list of active pipes
- Registers
PreSelect/PostSelecthandlers (POSIX) - Registers wait handles (Windows)
- Cleans up pipe resources during destruction
On POSIX systems, the manager adds file descriptors to the select() read set and dispatches readable pipes during PostSelect.
On Windows, it uses overlapped I/O events and wait handles attached to the chain.
Represents a spawned child process.
Key Fields:
PID– OS process identifierstdIn,stdOut,stdErr– Associated Pipe objectsexitHandler– Callback when process terminatesuserObject– Opaque user contextmetadata– Diagnostic label
Capabilities:
- Spawn (default, detached, terminal, specified user)
- Soft kill (
SIGTERMthenSIGKILLfallback on POSIX) - Hard kill (terminate + destroy)
- Attach output handlers
- Attach exit handler
Represents one side of a pipe.
Key Fields:
mPipe_ReadEnd,mPipe_WriteEnd– OS handlesbuffer,bufferSize– Read bufferWriteBuffer– Outgoing write queuehandler– Read or write completion callbackbrokenPipeHandler– Invoked on pipe failurePAUSED– Flow control flag
The Pipe object encapsulates:
- Read buffering and partial consumption
- Non-blocking writes
- Broken pipe detection
- Pause/resume semantics
Spawning differs by platform.
flowchart TD
Start["Spawn Request"] --> CreatePipes["Create stdin/stdout/stderr Pipes"]
CreatePipes --> ForkOrCreate["fork/exec or CreateProcess"]
ForkOrCreate --> Parent["Parent Process"]
Parent --> AttachHandlers["Attach Read/Exit Handlers"]
AttachHandlers --> Running["Process Running"]
Windows:
- Uses
CreateProcessWorCreateProcessAsUserW - Configures inherited handles
- Uses overlapped I/O for async reads
POSIX:
- Uses
fork(),vfork(), orforkpty() - Duplicates pipe FDs to
STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO - Executes with
execv()orexecve()
When data becomes available:
flowchart TD
FDReady["FD/Event Ready"] --> Read["Read OS Pipe"]
Read --> Buffer["Append to Internal Buffer"]
Buffer --> Handler["Invoke Generic Read Handler"]
Handler --> Consumed{"Bytes Consumed?"}
Consumed -->|All| Reset["Reset Buffer"]
Consumed -->|Partial| Shift["Adjust Offsets"]
Consumed -->|None| Compact["memmove to Start"]
The read handler receives:
- Pointer to buffer
- Available length
- Output parameter indicating bytes consumed
This allows streaming parsers to process partial data safely.
Writes are non-blocking and queued if necessary.
flowchart TD
WriteCall["Pipe_Write()"] --> EmptyQ{"Queue Empty?"}
EmptyQ -->|Yes| DirectWrite["Attempt OS Write"]
EmptyQ -->|No| Enqueue["Add to Write Queue"]
DirectWrite --> Complete{"Completed?"}
Complete -->|Yes| Done["DoneState_COMPLETE"]
Complete -->|Pending| QueueAndWait["Enqueue + Wait Handle"]
Complete -->|Error| Broken["Broken Pipe Handler"]
On Windows, overlapped writes trigger a wait handle callback.
On POSIX, incomplete writes re-schedule via the chain timer.
The Pipe object supports pausing reads.
Pipe_Pause()removes the FD from active monitoringPipe_Resume()re-registers it with the chain- Buffered unread data is processed before resuming OS reads
flowchart TD
Pause["Pipe_Pause()"] --> SetFlag["PAUSED = 1"]
Resume["Pipe_Resume()"] --> ClearFlag["PAUSED = 0"]
ClearFlag --> ProcessBuffered["Process Remaining Buffer"]
ProcessBuffered --> Reattach["Re-register with Chain"]
This is critical for backpressure handling and streaming parsers.
On POSIX, ILibProcessPipe_SpawnTypes_TERM uses forkpty().
Features:
- Allocates pseudo-terminal
- Configurable window size (
winsize) - Optional
termiosflags - Unified stdin/stdout via PTY
This enables interactive shells or terminal-bound applications.
A broken pipe occurs when:
- The child process exits
- The write end is closed
- An I/O error occurs
When detected:
- The pipe is removed from the manager
brokenPipeHandleris invoked- The Process object may trigger exit handling
- Resources are freed
On POSIX, process exit status is retrieved via waitpid().
On Windows, exit is detected via a wait handle on the process handle.
- Windows:
TerminateProcess() - POSIX:
SIGTERM, wait up to 500ms, thenSIGKILL
The staged termination on POSIX prevents corruption of system state (e.g., macOS TCC permissions).
- Calls soft kill
- Immediately destroys process object
Each pipe and process can store metadata strings used for:
- Chain diagnostics
- Wait handle labeling (Windows)
- Remote logging context
Metadata is surfaced via the Manager QueryHandler.
Process Pipe is tightly integrated into the Microstack event model.
flowchart TD
ChainLoop["Chain Event Loop"] --> PreSelect["Manager PreSelect"]
PreSelect --> Select["select() or Wait Handles"]
Select --> PostSelect["Manager PostSelect"]
PostSelect --> PipeRead["Process_ReadHandler"]
PipeRead --> UserCallback["User Output Handler"]
This design ensures:
- No dedicated polling threads (POSIX)
- Native overlapped I/O (Windows)
- Unified callback-driven model
Typical usage pattern:
- Create manager (once per chain)
- Spawn process
- Attach handlers
- Write to stdin as needed
- Handle output callbacks
- Handle exit callback
Example lifecycle:
Manager_Create()
→ SpawnProcessEx4()
→ Process_AddHandlers()
→ Output callbacks fire
→ Exit callback fires
| Feature | Windows | POSIX |
|---|---|---|
| Spawn | CreateProcess | fork/exec |
| Async Read | Overlapped I/O | Non-blocking FD + select |
| Async Write | Overlapped I/O | Non-blocking write |
| Exit Detection | Wait handle on process | waitpid |
| Terminal Mode | N/A | forkpty |
The abstraction ensures callers do not need to manage platform-specific process semantics.
Process Pipe builds upon:
- [Parsers and Chain](../Parsers and Chain/Parsers and Chain.md) – Event scheduling and lifecycle
- [Remote Logging](../Remote Logging/Remote Logging.md) – Diagnostic output
It is frequently used by higher-level modules that require:
- Script execution
- Shell interaction
- External tool invocation
- Privilege-separated operations
- Event-driven, not thread-per-process (except legacy Windows background reader fallback)
- Cross-platform parity
- Safe buffering with explicit consumption semantics
- Backpressure-aware pause/resume model
- Deterministic cleanup via chain lifecycle
The Process Pipe module provides a robust, event-driven abstraction over OS process management and inter-process communication. It bridges Windows overlapped I/O and POSIX non-blocking pipes into a unified Microstack chain-integrated model.
By encapsulating pipe management, buffering, and lifecycle control, Process Pipe enables reliable execution and supervision of child processes within the MeshAgent runtime.