Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3c510ce
Continue fork children in fresh runners from a parent snapshot
wdcui Oct 2, 2026
c94672b
Address fork review findings
wdcui Oct 2, 2026
ac363d2
Keep guest placement clear of host mappings and drop the fork option
wdcui Oct 2, 2026
c6e8c4c
Keep fork image restore next to the image writer
wdcui Oct 2, 2026
590160e
Speed up fork image restore and guest file loading
wdcui Oct 2, 2026
c3e18e5
Pause sibling threads while forking a multi-threaded process
wdcui Oct 2, 2026
e3b7fb6
Fix fork review findings and speed up the image transfer
wdcui Oct 2, 2026
c9f6262
Fix second-round fork review findings and send 1 MiB image writes
wdcui Oct 2, 2026
a38d512
Keep mremap growth from merging areas and race concurrent forks in tests
wdcui Oct 3, 2026
ea3a623
Pass the seccomp scope when continuing a forked child
wdcui Oct 3, 2026
26c0424
Document that a partial host mremap move ends in the copy fallback's …
wdcui Oct 3, 2026
9dc9a2f
Copy instead of failing when a host mapping holds the mremap destination
wdcui Oct 3, 2026
7f9f7a1
Explain why fork does not copy vector state yet
wdcui Oct 3, 2026
5cb94c0
Raise file reads and writes to 16 shared-buffer slots
wdcui Oct 3, 2026
67e71ce
Drop the redundant program startup dispatch test
wdcui Oct 3, 2026
f0bb112
Drop the wait timeout test and its mock clock helper
wdcui Oct 3, 2026
205659a
Drop the single-variant child start source and its wire tag
wdcui Oct 3, 2026
8af4f3d
Drop process image transfer tests covered by runner tests
wdcui Oct 3, 2026
d6b038a
Trim the slot merge test to its range checks
wdcui Oct 3, 2026
38b1762
Name the runner image constructor after the launcher method
wdcui Oct 3, 2026
9d8748e
Drop the pending child image drop test covered by the budget test
wdcui Oct 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions litebox/src/broker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use litebox_broker_protocol::fs::{
};
use litebox_broker_protocol::pipe::{CreatePipeResponse, MAX_PIPE_TRANSFER_SIZE};
use litebox_broker_protocol::process::{
CreatedProcess, MAX_CHILD_OBJECT_DUPLICATES, MAX_PROCESS_BOOTSTRAP_SIZE, ProcessExitStatus,
ProcessTermination,
CreatedProcess, MAX_CHILD_MEMORY_WRITE_SIZE, MAX_CHILD_OBJECT_DUPLICATES,
MAX_PROCESS_BOOTSTRAP_SIZE, ProcessExitStatus, ProcessTermination,
};
use litebox_broker_protocol::random::MAX_RANDOM_TRANSFER_SIZE;
use litebox_broker_protocol::readiness::ReadinessFlags;
Expand Down Expand Up @@ -62,6 +62,13 @@ pub(crate) trait BrokerControl: Send + Sync {
payload: &[u8],
) -> core::result::Result<(), BrokerControlError>;

fn write_child_memory(
&self,
child_process_id: litebox_broker_protocol::ProcessId,
offset: u64,
data: &[u8],
) -> core::result::Result<(), BrokerControlError>;

fn exit_child_process(
&self,
child_process_id: litebox_broker_protocol::ProcessId,
Expand Down Expand Up @@ -523,6 +530,21 @@ where
})
}

fn write_child_memory(
&self,
child_process_id: litebox_broker_protocol::ProcessId,
offset: u64,
data: &[u8],
) -> core::result::Result<(), BrokerControlError> {
if data.is_empty() || data.len() > MAX_CHILD_MEMORY_WRITE_SIZE as usize {
return Err(BrokerControlError::Broker(ErrorCode::ResourceExhausted));
}
let lease = self.acquire_shared_buffer(data.len())?;
self.request(|local| {
local.write_child_memory(child_process_id, offset, lease.sequence(), data)
})
}

fn exit_child_process(
&self,
child_process_id: litebox_broker_protocol::ProcessId,
Expand Down
13 changes: 9 additions & 4 deletions litebox/src/event/wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,15 +412,20 @@ impl<'a, Platform: RawSyncPrimitivesProvider + TimeProvider> WaitContext<'a, Pla
// Check for timeout before checking for an interrupt. This is important
// for things like sleep(), where we want to return `TimedOut` rather than
// `Interrupted` if the deadline has already passed.
if self.deadline.is_some() && self.remaining_timeout().is_none() {
return Err(WaitError::TimedOut);
}
if self.check_interrupt.check_for_interrupt() {
return Err(WaitError::Interrupted);
}

// The interrupt check may block (for example, while another thread
// pauses this one), so compute the remaining timeout after it.
let timeout = if self.deadline.is_some() {
Some(self.remaining_timeout().ok_or(WaitError::TimedOut)?)
} else {
None
};
if self.check_interrupt.check_for_interrupt() {
return Err(WaitError::Interrupted);
}

if let Some(timeout) = timeout {
let r = self
.waker
Expand Down
8 changes: 8 additions & 0 deletions litebox/src/platform/page_mgmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ pub trait PageManagementProvider<const ALIGN: usize>: RawPointerProvider {
///
/// Note it must be aligned to `ALIGN`.
const TASK_ADDR_MAX: usize;
/// The upper bound (exclusive) for addresses that the memory manager chooses itself.
///
/// Mappings at caller-specified fixed addresses may still extend up to
/// [`Self::TASK_ADDR_MAX`]. Platforms whose host allocates in the upper part of the task
/// range can lower this to keep self-placed task memory clear of host memory.
///
/// Note it must be aligned to `ALIGN` and greater than [`Self::TASK_ADDR_MIN`].
const PLACEMENT_ADDR_MAX: usize = Self::TASK_ADDR_MAX;

/// Alignment of native reservation base addresses, in bytes.
///
Expand Down
20 changes: 19 additions & 1 deletion litebox/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use alloc::sync::Arc;
use alloc::vec::Vec;

use litebox_broker_protocol::error::ErrorCode;
use litebox_broker_protocol::process::{ProcessExitStatus, ProcessIdentity, ProcessTermination};
use litebox_broker_protocol::process::{
MAX_CHILD_MEMORY_WRITE_SIZE, ProcessExitStatus, ProcessIdentity, ProcessTermination,
};
use litebox_broker_protocol::signal::PendingSignal;
use litebox_broker_protocol::{ObjectHandle, ProcessId};
use litebox_platform::time::TimeProvider;
Expand Down Expand Up @@ -231,6 +233,22 @@ impl<Platform: RawSyncPrimitivesProvider + TimeProvider> Process<Platform> {
.collect())
}

/// Writes `data` at `offset` of this pending child process's memory image,
/// which the child's runner receives when the child starts.
///
/// The image is zero-filled where nothing was written.
pub fn write_memory(&self, offset: u64, data: &[u8]) -> Result<(), ProcessError> {
let mut offset = offset;
for chunk in data.chunks(MAX_CHILD_MEMORY_WRITE_SIZE as usize) {
self.broker
.write_child_memory(self.identity.process_id, offset, chunk)?;
offset = offset
.checked_add(chunk.len() as u64)
.ok_or(ProcessError::ResourceExhausted)?;
}
Ok(())
}

/// Records that this pending child process exited without starting a
/// runner, leaving it a zombie reporting `exit_status`.
pub fn exit(&self, exit_status: ProcessExitStatus) -> Result<(), ProcessError> {
Expand Down
36 changes: 34 additions & 2 deletions litebox_broker_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ mod test_platform;
pub mod test_support;

use alloc::sync::{Arc, Weak};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};

use hashbrown::HashMap;
use litebox_broker_protocol::{ObjectHandle, ProcessId};
Expand All @@ -55,7 +55,8 @@ pub use policy::{
PolicyProfile, SocketPolicy, SocketPolicyError,
};
pub use process::{
AssociationCancellation, BrokerProcess, CallerCredential, ProcessLifecycleSink, ProcessShutdown,
AssociationCancellation, BrokerProcess, CallerCredential, ChildImage, ProcessImage,
ProcessLifecycleSink, ProcessShutdown,
};
use random::RandomProvider;
use socket::{BrokerSocketPorts, SocketProvider};
Expand Down Expand Up @@ -87,6 +88,10 @@ pub struct BrokerCoreLimits {
pub max_threads: usize,
/// Maximum live broker-allocated thread IDs owned by one process.
pub max_threads_per_process: usize,
/// Maximum size in bytes of one pending child's memory image.
pub max_child_image_size: u64,
/// Maximum total size in bytes of child memory images held by the broker.
pub max_total_child_image_size: u64,
}

impl BrokerCoreLimits {
Expand All @@ -101,6 +106,8 @@ impl BrokerCoreLimits {
max_sockets_per_process: 256,
max_threads: 4096,
max_threads_per_process: 1024,
max_child_image_size: 1024 * 1024 * 1024,
max_total_child_image_size: 4 * 1024 * 1024 * 1024,
};

/// Creates a broker core limit set.
Expand All @@ -118,6 +125,8 @@ impl BrokerCoreLimits {
max_sockets_per_process: Self::DEFAULT.max_sockets_per_process,
max_threads: Self::DEFAULT.max_threads,
max_threads_per_process: Self::DEFAULT.max_threads_per_process,
max_child_image_size: Self::DEFAULT.max_child_image_size,
max_total_child_image_size: Self::DEFAULT.max_total_child_image_size,
}
}

Expand All @@ -141,6 +150,8 @@ impl BrokerCoreLimits {
max_sockets_per_process,
max_threads: Self::DEFAULT.max_threads,
max_threads_per_process: Self::DEFAULT.max_threads_per_process,
max_child_image_size: Self::DEFAULT.max_child_image_size,
max_total_child_image_size: Self::DEFAULT.max_total_child_image_size,
}
}

Expand Down Expand Up @@ -186,6 +197,24 @@ impl BrokerCoreLimits {
..self
}
}

/// Returns these limits with explicit per-child and broker-wide child
/// memory image size limits.
///
/// A per-child limit above the broker-wide limit is accepted; the
/// broker-wide limit still applies.
#[must_use]
pub const fn with_child_image_size_limits(
self,
max_child_image_size: u64,
max_total_child_image_size: u64,
) -> Self {
Self {
max_child_image_size,
max_total_child_image_size,
..self
}
}
}

impl Default for BrokerCoreLimits {
Expand All @@ -212,6 +241,8 @@ pub struct BrokerCore {
pub(crate) pending_references: Arc<AtomicUsize>,
pub(crate) reserved_pipe_capacity: Arc<AtomicUsize>,
pub(crate) reserved_sockets: Arc<AtomicUsize>,
/// Bytes of child memory images held by the broker.
pub(crate) reserved_child_image_size: Arc<AtomicU64>,
pub(crate) random_provider: Arc<dyn RandomProvider>,
pub(crate) socket_provider: Arc<dyn SocketProvider>,
pub(crate) timer_provider: Arc<dyn TimerProvider>,
Expand Down Expand Up @@ -272,6 +303,7 @@ impl BrokerCore {
pending_references: Arc::new(AtomicUsize::new(0)),
reserved_pipe_capacity: Arc::new(AtomicUsize::new(0)),
reserved_sockets: Arc::new(AtomicUsize::new(0)),
reserved_child_image_size: Arc::new(AtomicU64::new(0)),
random_provider,
socket_provider,
timer_provider,
Expand Down
Loading
Loading