Reference Counting and Ownership

Relevant source files

This document explains how the axprocess crate implements memory management through Rust's reference counting mechanisms. It details the ownership patterns between system components (Sessions, ProcessGroups, Processes, and Threads) and how they prevent memory leaks while maintaining proper object lifetimes.

For information about cleanup of terminated processes, see Zombie Processes and Cleanup.

Reference Counting Fundamentals

The axprocess crate relies on Rust's smart pointers to manage memory and object lifetimes:

  • Arc (Atomic Reference Counting): Provides shared ownership of a value with thread-safe reference counting
  • Weak: A non-owning reference that doesn't prevent deallocation when all Arc references are dropped

This approach avoids both manual memory management and garbage collection, guaranteeing memory safety while maintaining predictable resource cleanup.

flowchart TD
A["Arc"]
O["Object"]
W["Weak"]
RC["Reference Count"]
D["Deallocate"]

A --> O
A --> RC
RC --> D
W --> O
W --> RC

Diagram: Reference Counting Basics

Sources: src/process.rs(L1 - L4) 

Ownership Hierarchy in axprocess

The axprocess system employs a careful hierarchy of strong and weak references to maintain proper component ownership while preventing reference cycles.

classDiagram
class Process {
    pid: Pid
    children: StrongMap~
    parent: Weak
    group: Arc
    
}

class ProcessGroup {
    pgid: Pid
    session: Arc
    processes: WeakMap~
    
}

class Session {
    sid: Pid
    process_groups: WeakMap~
    
}

class Thread {
    tid: Pid
    process: Arc
    
}

Process  -->  Process
Process  -->  Process : "weak reference to parent"
Process  ..>  Process
Process  -->  ProcessGroup : "strong reference"
ProcessGroup  -->  Session : "strong reference"
ProcessGroup  ..>  Process : "weak references"
Session  ..>  Process : "weak references"
Thread  -->  Process : "strong reference"

Diagram: Reference Relationships Between Components

Sources: src/process.rs(L35 - L47)  src/process_group.rs(L13 - L17)  src/session.rs(L13 - L17) 

Reference Direction Strategy

The system uses a deliberate pattern for determining which direction uses strong vs. weak references:

Upward Strong References

Components hold strong references (Arc) to their "container" components:

  • Processes strongly reference their ProcessGroup
  • ProcessGroups strongly reference their Session

This ensures container components remain alive as long as any child component needs them.

Downward Weak References

Container components hold weak references to their "members":

  • Sessions weakly reference their ProcessGroups
  • ProcessGroups weakly reference their Processes
  • ThreadGroups weakly reference their Threads

This prevents reference cycles while allowing containers to access their members.

Hierarchical Strong References

Processes hold strong references to their children, ensuring child processes remain valid while the parent exists. This reflects the parent-child ownership model where parents are responsible for their children's lifecycle.

flowchart TD
subgraph subGraph2["Hierarchical Strong References"]
    Parent["Parent Process"]
    Child["Child Process"]
    S2["Session"]
    PG2["ProcessGroup"]
    P["Process"]
    PG["ProcessGroup"]
end

Child --> Parent
P --> PG
PG --> S
PG2 --> P2
Parent --> Child
S2 --> PG2

Diagram: Reference Direction Strategy

Sources: src/process.rs(L43 - L46)  src/process_group.rs(L14 - L16)  src/session.rs(L14 - L15) 

Implementation Details

Process Ownership

The Process struct maintains:

  • Strong references to children in a StrongMap
  • Weak reference to its parent
  • Strong reference to its ProcessGroup
Process {
    children: SpinNoIrq<StrongMap<Pid, Arc<Process>>>,
    parent: SpinNoIrq<Weak<Process>>,
    group: SpinNoIrq<Arc<ProcessGroup>>,
}

Sources: src/process.rs(L43 - L46) 

ProcessGroup Ownership

The ProcessGroup struct maintains:

  • Strong reference to its Session
  • Weak references to its member Processes
ProcessGroup {
    session: Arc<Session>,
    processes: SpinNoIrq<WeakMap<Pid, Weak<Process>>>,
}

Sources: src/process_group.rs(L14 - L16) 

Session Ownership

The Session struct maintains:

  • Weak references to its member ProcessGroups
Session {
    process_groups: SpinNoIrq<WeakMap<Pid, Weak<ProcessGroup>>>,
}

Sources: src/session.rs(L14 - L15) 

Reference Management During Object Creation

When creating objects, the system carefully establishes the appropriate references:

  1. When a Process is created:
  • It acquires a strong reference to its ProcessGroup
  • The ProcessGroup stores a weak reference back to the Process
  • If it has a parent, the parent stores a strong reference to it
  • It stores a weak reference to its parent
  1. When a ProcessGroup is created:
  • It acquires a strong reference to its Session
  • The Session stores a weak reference back to the ProcessGroup
sequenceDiagram
    participant ProcessBuilder as ProcessBuilder
    participant Process as Process
    participant ProcessGroup as ProcessGroup
    participant Session as Session

    Note over ProcessBuilder: ProcessBuilder::build()
    alt No parent (init process)
        ProcessBuilder ->> Session: Session::new(pid)
        ProcessBuilder ->> ProcessGroup: ProcessGroup::new(pid, &session)
    else Has parent
        ProcessBuilder ->> ProcessGroup: parent.group()
    end
    ProcessBuilder ->> Process: Create new Process
    Process ->> ProcessGroup: group.processes.insert(pid, weak_ref)
    alt Has parent
        Process ->> Process: parent.children.insert(pid, strong_ref)
    else No parent (init process)
        Process ->> Process: INIT_PROC.init_once(process)
    end

Diagram: Reference Setup During Process Creation

Sources: src/process.rs(L302 - L331)  src/process_group.rs(L21 - L28)  src/session.rs(L20 - L26) 

Reference Management During Process Termination

When a Process exits:

  1. It is marked as a zombie
  2. Its children are re-parented to the init process
  3. The children update their weak parent reference to point to the init process
  4. The init process takes strong ownership of the children
sequenceDiagram
    participant Process as Process
    participant InitProcess as Init Process
    participant ChildProcesses as Child Processes

    Process ->> Process: is_zombie.store(true)
    Process ->> InitProcess: Get init_proc()
    Process ->> Process: Take children
    loop For each child
        Process ->> ChildProcesses: Update weak parent reference to init
        Process ->> InitProcess: Add child to init's children
    end

Diagram: Reference Management During Process Exit

Sources: src/process.rs(L207 - L225) 

Memory Safety Considerations

The reference counting design in axprocess provides several safety guarantees:

Safety FeatureImplementationBenefit
No reference cyclesStrategic use of weak referencesPrevents memory leaks
Component lifetime guaranteesUpward strong referencesComponents can't be deallocated while in use
Clean resource releaseWeak references in containersEnables efficient cleanup without dangling pointers
Automatic cleanupArc drop semanticsResources are freed when no longer needed
Thread safetyArc's atomic reference countingSafe to use across threads

Sources: src/process.rs(L35 - L47)  src/process_group.rs(L13 - L17)  src/session.rs(L13 - L17) 

Practical Example: Process Lifecycle References

Let's trace the reference management during a process's lifecycle:

  1. Process creation:
  • Parent process creates a child using fork() and ProcessBuilder::build()
  • Child gets a strong reference to parent's process group
  • Parent stores a strong reference to child
  • Child stores a weak reference to parent
  1. Process execution:
  • Process maintains its references throughout execution
  1. Process termination:
  • Process calls exit() and is marked as zombie
  • Child processes are re-parented to init
  • Parent process eventually calls free() to remove its strong reference
  • When all strong references are gone, process is deallocated

Sources: src/process.rs(L207 - L236)  src/process.rs(L275 - L331) 

Utility Functions for Reference Management

The codebase provides several methods to manage references between components:

MethodPurposeReference Type
Process::parent()Get parent processWeak → Strong conversion
Process::children()Get child processesStrong references
Process::group()Get process groupStrong reference
ProcessGroup::session()Get sessionStrong reference
ProcessGroup::processes()Get member processesWeak → Strong conversion
Session::process_groups()Get process groupsWeak → Strong conversion

Sources: src/process.rs(L73 - L80)  src/process.rs(L86 - L88)  src/process_group.rs(L33 - L46)  src/session.rs(L30 - L38) 

Conclusion

The reference counting and ownership model in axprocess provides a robust foundation for memory management by:

  1. Using strong references strategically to ensure components remain alive as needed
  2. Using weak references to prevent reference cycles
  3. Following a consistent pattern of upward strong references and downward weak references
  4. Maintaining proper parent-child relationships through appropriate reference types

This approach leverages Rust's ownership model to provide memory safety without garbage collection, ensuring efficient and predictable resource management.