swactor/crates/data-plane/src/data_plane.rs
Zachery Aaron Shores-Chmielewski 291698c56f feat(data-plane): add descriptor-based I/O
Add descriptor lifecycle, mapping, errno, namespace reservations, stream cursor operations, Python bindings, guarantees, and benchmarks.
2026-08-24 19:22:20 +04:00

2784 lines
90 KiB
Rust

//! Child-side data-plane session, per-operation actors, and native API.
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt;
use std::os::fd::OwnedFd;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::{Arc, Weak};
use std::time::Duration;
use swactor::actor::{ActorAddress, ActorInterface, Ctx};
use swactor::runtime::{ExternalSender, Runtime};
use swactor_engine::{ActorCompletion, EngineHandle};
use crate::blob::{
Blob, BlobLease, BlobMetadata, BlobView, LeaseReleaser, WritableArenaView, WritableBlobLease,
WritableViewObserver,
};
use crate::byte_ring::{
Endpoint, FlowError, RecordCursor, RecordKind, RingHandle, Role, attach_mapped,
};
use crate::mapped_arena::MappedArena;
use crate::path::DataPath;
pub use crate::protocol::{
AccessMode, BlobAllocation, DataPlaneError, DescriptorCapabilities, DescriptorKind, Errno,
OpenOptions,
};
use crate::protocol::{ChildSessionIn, HostSessionIn, HostStreamIn, JobCapability, OpenPolicy};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChildSessionState {
Attaching,
Running,
Closing,
Closed,
}
pub struct DataPlaneBootstrap {
pub arena: Arc<MappedArena>,
pub data_plane: DataPlane,
}
pub struct AttachDeadline {
pub engine: EngineHandle,
pub timeout: Duration,
}
impl DataPlaneBootstrap {
pub async fn attach(
arena_fd: OwnedFd,
runtime: Runtime,
host_session: ActorAddress,
job_capability: JobCapability,
) -> Result<Self, DataPlaneError> {
Self::attach_routed(arena_fd, runtime, host_session, job_capability, None).await
}
pub fn map_arena(
arena_fd: OwnedFd,
) -> Result<(Arc<MappedArena>, crate::bootstrap::ResolvedBootstrap), DataPlaneError> {
let (arena, resolved) = MappedArena::map(arena_fd)
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
Ok((Arc::new(arena), resolved))
}
pub async fn attach_routed(
arena_fd: OwnedFd,
runtime: Runtime,
host_session: ActorAddress,
job_capability: JobCapability,
child_node: Option<[u8; 32]>,
) -> Result<Self, DataPlaneError> {
let (arena, resolved) = Self::map_arena(arena_fd)?;
Self::attach_mapped(
arena,
resolved,
runtime,
host_session,
job_capability,
child_node,
)
.await
}
pub async fn attach_mapped(
arena: Arc<MappedArena>,
resolved: crate::bootstrap::ResolvedBootstrap,
runtime: Runtime,
host_session: ActorAddress,
job_capability: JobCapability,
child_node: Option<[u8; 32]>,
) -> Result<Self, DataPlaneError> {
Self::attach_mapped_inner(
arena,
resolved,
runtime,
host_session,
job_capability,
child_node,
None,
)
.await
}
pub async fn attach_mapped_with_deadline(
arena: Arc<MappedArena>,
resolved: crate::bootstrap::ResolvedBootstrap,
runtime: Runtime,
host_session: ActorAddress,
job_capability: JobCapability,
child_node: Option<[u8; 32]>,
deadline: AttachDeadline,
) -> Result<Self, DataPlaneError> {
let sender = runtime.create_sender();
Self::attach_mapped_inner(
arena,
resolved,
runtime,
host_session,
job_capability,
child_node,
Some((deadline.engine, sender, deadline.timeout)),
)
.await
}
async fn attach_mapped_inner(
arena: Arc<MappedArena>,
resolved: crate::bootstrap::ResolvedBootstrap,
runtime: Runtime,
host_session: ActorAddress,
job_capability: JobCapability,
child_node: Option<[u8; 32]>,
deadline: Option<(EngineHandle, ExternalSender, Duration)>,
) -> Result<Self, DataPlaneError> {
let attached = runtime
.new_inbox::<Result<u64, DataPlaneError>>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
let attach_reply = *attached.addr();
let child = runtime
.spawn(ChildDataPlaneSessionActor {
runtime: runtime.clone(),
host_session,
arena_generation: resolved.arena_generation,
job_capability,
child_node,
session_generation: None,
attach_reply: Some(attach_reply),
operations: HashSet::new(),
open_operations: HashMap::new(),
state: ChildSessionState::Attaching,
stream_operations: HashMap::new(),
pending_blob_releases: 0,
deferred_blob_opens: VecDeque::new(),
})
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
if let Some((engine, sender, timeout)) = deadline {
engine.send_after(timeout, sender, child, ChildSessionIn::AttachmentDeadline);
}
attached.recv().await?;
Ok(Self {
arena: arena.clone(),
data_plane: DataPlane {
runtime,
child_session: child,
arena,
},
})
}
}
struct DescriptorOpenCancellation {
runtime: Runtime,
child_session: ActorAddress,
reply_to: ActorAddress,
armed: bool,
}
impl Drop for DescriptorOpenCancellation {
fn drop(&mut self) {
if self.armed {
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::CancelOpen {
reply_to: self.reply_to,
},
);
}
}
}
#[derive(Clone)]
enum DescriptorOpenGrant {
ReadBlob {
host_binding: ActorAddress,
lease: BlobLease,
metadata: BlobMetadata,
cancellation: Arc<DescriptorGrantCancellation>,
},
WriteBlob {
operation: ActorAddress,
lease: BlobLease,
metadata: BlobMetadata,
access: AccessMode,
cancellation: Arc<DescriptorGrantCancellation>,
},
Stream {
operation: ActorAddress,
host_binding: ActorAddress,
ring: RingHandle,
role: Role,
cancellation: Arc<DescriptorGrantCancellation>,
},
}
#[derive(Clone, Copy)]
enum DescriptorGrantCancellationAction {
HostOpen {
host_session: ActorAddress,
operation: ActorAddress,
},
WriteBlob {
operation: ActorAddress,
lease: BlobLease,
},
}
struct DescriptorGrantCancellation {
runtime: Runtime,
action: DescriptorGrantCancellationAction,
armed: AtomicBool,
}
impl DescriptorGrantCancellation {
fn disarm(&self) {
self.armed.store(false, Ordering::Release);
}
}
impl Drop for DescriptorGrantCancellation {
fn drop(&mut self) {
if !self.armed.swap(false, Ordering::AcqRel) {
return;
}
match self.action {
DescriptorGrantCancellationAction::HostOpen {
host_session,
operation,
} => {
let _ = self
.runtime
.send_to(host_session, HostSessionIn::CancelOpen { operation });
}
DescriptorGrantCancellationAction::WriteBlob { operation, lease } => {
let _ = self.runtime.send_to(
operation,
ChildOperationIn::AbortRequested {
reply_to: None,
lease,
},
);
}
}
}
}
#[derive(Clone)]
pub struct DataPlane {
runtime: Runtime,
child_session: ActorAddress,
arena: Arc<MappedArena>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Protection {
Read,
ReadWrite,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Sharing {
Shared,
Private,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DeviceId(pub u64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MapTarget {
Host,
Device(DeviceId),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MapRequest {
pub protection: Protection,
pub sharing: Sharing,
pub target: MapTarget,
pub offset: u64,
pub length: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RegionKind {
Host,
Arena,
Device,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TransferRoute {
Direct,
Staged,
}
pub struct DeviceRegion {
identity: [u8; 32],
generation: u64,
length: u64,
readable: bool,
writable: bool,
direct_required: bool,
}
impl DeviceRegion {
pub const fn identity(&self) -> &[u8; 32] {
&self.identity
}
pub const fn generation(&self) -> u64 {
self.generation
}
pub const fn length(&self) -> u64 {
self.length
}
pub const fn is_readable(&self) -> bool {
self.readable
}
pub const fn is_writable(&self) -> bool {
self.writable
}
pub const fn requires_direct_route(&self) -> bool {
self.direct_required
}
}
pub struct DeviceRegionSlice<'a> {
region: &'a DeviceRegion,
offset: u64,
length: u64,
}
pub enum RegionSlice<'a> {
Host(&'a mut [u8]),
Arena(&'a mut [u8]),
Device(DeviceRegionSlice<'a>),
}
impl<'a> RegionSlice<'a> {
pub fn host(bytes: &'a mut [u8]) -> Self {
Self::Host(bytes)
}
pub fn arena(bytes: &'a mut [u8]) -> Self {
Self::Arena(bytes)
}
pub fn device(
region: &'a DeviceRegion,
offset: u64,
length: u64,
) -> Result<Self, DataPlaneError> {
if offset
.checked_add(length)
.is_none_or(|end| end > region.length)
{
return Err(DataPlaneError::InvalidArgument(
"device region slice is out of bounds".to_owned(),
));
}
Ok(Self::Device(DeviceRegionSlice {
region,
offset,
length,
}))
}
pub const fn kind(&self) -> RegionKind {
match self {
Self::Host(_) => RegionKind::Host,
Self::Arena(_) => RegionKind::Arena,
Self::Device(_) => RegionKind::Device,
}
}
pub fn len(&self) -> usize {
match self {
Self::Host(bytes) | Self::Arena(bytes) => bytes.len(),
Self::Device(slice) => usize::try_from(slice.length).unwrap_or(usize::MAX),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn as_ref(&self) -> Result<&[u8], DataPlaneError> {
match self {
Self::Host(bytes) | Self::Arena(bytes) => Ok(bytes),
Self::Device(slice) if !slice.region.readable => Err(DataPlaneError::BadDescriptor),
Self::Device(slice) => {
let _ = slice.offset;
Err(DataPlaneError::Unsupported(
"no registered device read route is installed".to_owned(),
))
}
}
}
fn as_mut(&mut self) -> Result<&mut [u8], DataPlaneError> {
match self {
Self::Host(bytes) | Self::Arena(bytes) => Ok(bytes),
Self::Device(slice) if !slice.region.writable => Err(DataPlaneError::BadDescriptor),
Self::Device(slice) => {
let _ = slice.offset;
Err(DataPlaneError::Unsupported(
"no registered device write route is installed".to_owned(),
))
}
}
}
}
pub enum DescriptorMapping {
ReadOnly(BlobView),
WritableReadOnly(WritableArenaView),
Writable(WritableArenaView),
}
impl DescriptorMapping {
pub fn len(&self) -> usize {
match self {
Self::ReadOnly(view) => view.len(),
Self::WritableReadOnly(view) | Self::Writable(view) => view.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_ref(&self) -> &[u8] {
match self {
Self::ReadOnly(view) => view.as_ref(),
Self::WritableReadOnly(view) | Self::Writable(view) => view.as_ref(),
}
}
pub fn as_mut(&mut self) -> Result<&mut [u8], DataPlaneError> {
match self {
Self::ReadOnly(_) | Self::WritableReadOnly(_) => Err(DataPlaneError::BadDescriptor),
Self::Writable(view) => Ok(view.as_mut()),
}
}
pub const fn route(&self) -> TransferRoute {
TransferRoute::Direct
}
}
impl fmt::Debug for DescriptorMapping {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("DescriptorMapping")
.field("len", &self.len())
.field("route", &self.route())
.field("writable", &matches!(self, Self::Writable(_)))
.finish()
}
}
enum DescriptorBackend {
ReadBlob { blob: Blob, offset: u64 },
WriteBlob { writer: BlobWriter, offset: u64 },
ReadStream(StreamReader),
WriteStream(StreamWriter),
}
pub struct Descriptor {
kind: DescriptorKind,
capabilities: DescriptorCapabilities,
access: AccessMode,
backend: Option<DescriptorBackend>,
last_route: Option<TransferRoute>,
}
impl Descriptor {
fn read_blob(blob: Blob) -> Self {
Self {
kind: DescriptorKind::Blob,
capabilities: DescriptorCapabilities::READ
.union(DescriptorCapabilities::MAP_HOST)
.union(DescriptorCapabilities::SEEK),
access: AccessMode::ReadOnly,
backend: Some(DescriptorBackend::ReadBlob { blob, offset: 0 }),
last_route: None,
}
}
fn write_blob(writer: BlobWriter, access: AccessMode) -> Self {
let mut capabilities = DescriptorCapabilities::WRITE
.union(DescriptorCapabilities::MAP_HOST)
.union(DescriptorCapabilities::SEEK);
if access.can_read() {
capabilities = capabilities.union(DescriptorCapabilities::READ);
}
Self {
kind: DescriptorKind::Blob,
capabilities,
access,
backend: Some(DescriptorBackend::WriteBlob { writer, offset: 0 }),
last_route: None,
}
}
fn read_stream(reader: StreamReader) -> Self {
Self {
kind: DescriptorKind::Stream,
capabilities: DescriptorCapabilities::READ,
access: AccessMode::ReadOnly,
backend: Some(DescriptorBackend::ReadStream(reader)),
last_route: None,
}
}
fn write_stream(writer: StreamWriter) -> Self {
Self {
kind: DescriptorKind::Stream,
capabilities: DescriptorCapabilities::WRITE,
access: AccessMode::WriteOnly,
backend: Some(DescriptorBackend::WriteStream(writer)),
last_route: None,
}
}
pub const fn kind(&self) -> DescriptorKind {
self.kind
}
pub const fn capabilities(&self) -> DescriptorCapabilities {
self.capabilities
}
pub const fn access(&self) -> AccessMode {
self.access
}
pub const fn last_route(&self) -> Option<TransferRoute> {
self.last_route
}
pub fn is_closed(&self) -> bool {
self.backend.is_none()
}
pub async fn read(&mut self, destination: &mut [u8]) -> Result<usize, DataPlaneError> {
if !self.access.can_read() {
return Err(DataPlaneError::BadDescriptor);
}
let backend = self.backend.as_mut().ok_or(DataPlaneError::BadDescriptor)?;
if destination.is_empty() {
return Ok(0);
}
let result = match backend {
DescriptorBackend::ReadBlob { blob, offset } => {
let count = blob.copy_at(*offset, destination)?;
*offset += count as u64;
Ok(count)
}
DescriptorBackend::WriteBlob { writer, offset } => {
let count = writer.copy_at(*offset, destination)?;
*offset += count as u64;
Ok(count)
}
DescriptorBackend::ReadStream(reader) => reader.read_into(destination).await,
DescriptorBackend::WriteStream(_) => Err(DataPlaneError::BadDescriptor),
};
if result.is_ok() {
self.last_route = Some(TransferRoute::Staged);
}
result
}
pub async fn write(&mut self, source: &[u8]) -> Result<usize, DataPlaneError> {
if !self.access.can_write() {
return Err(DataPlaneError::BadDescriptor);
}
let backend = self.backend.as_mut().ok_or(DataPlaneError::BadDescriptor)?;
if source.is_empty() {
return Ok(0);
}
let result = match backend {
DescriptorBackend::WriteBlob { writer, offset } => {
let count = writer.copy_from(*offset, source)?;
*offset += count as u64;
Ok(count)
}
DescriptorBackend::WriteStream(writer) => writer.write_partial(source).await,
DescriptorBackend::ReadBlob { .. } | DescriptorBackend::ReadStream(_) => {
Err(DataPlaneError::BadDescriptor)
}
};
if result.is_ok() {
self.last_route = Some(TransferRoute::Staged);
}
result
}
pub async fn read_into(
&mut self,
mut destination: RegionSlice<'_>,
) -> Result<usize, DataPlaneError> {
let destination = destination.as_mut()?;
self.read(destination).await
}
pub async fn write_from(&mut self, source: RegionSlice<'_>) -> Result<usize, DataPlaneError> {
let source = source.as_ref()?;
self.write(source).await
}
pub async fn read_exact(&mut self, destination: &mut [u8]) -> Result<(), DataPlaneError> {
let mut completed = 0;
while completed < destination.len() {
let count = self.read(&mut destination[completed..]).await?;
if count == 0 {
return Err(DataPlaneError::InvalidArgument(
"unexpected EOF during read_exact".to_owned(),
));
}
completed += count;
}
Ok(())
}
pub async fn write_all(&mut self, source: &[u8]) -> Result<(), DataPlaneError> {
let mut completed = 0;
while completed < source.len() {
let count = self.write(&source[completed..]).await?;
if count == 0 {
return Err(DataPlaneError::SessionFailed(
"zero-byte write made no progress".to_owned(),
));
}
completed += count;
}
Ok(())
}
pub fn map(&self, request: MapRequest) -> Result<DescriptorMapping, DataPlaneError> {
let backend = self.backend.as_ref().ok_or(DataPlaneError::BadDescriptor)?;
if request.target != MapTarget::Host || request.sharing != Sharing::Shared {
return Err(DataPlaneError::Unsupported(
"requested mapping target or sharing mode is not supported".to_owned(),
));
}
match backend {
DescriptorBackend::ReadBlob { blob, .. } => {
if request.protection != Protection::Read {
return Err(DataPlaneError::BadDescriptor);
}
Ok(DescriptorMapping::ReadOnly(
blob.map_range(request.offset, request.length)?,
))
}
DescriptorBackend::WriteBlob { writer, .. } => match request.protection {
Protection::Read if self.access.can_read() => {
Ok(DescriptorMapping::WritableReadOnly(
writer.map_range(request.offset, request.length)?,
))
}
Protection::Read => Err(DataPlaneError::BadDescriptor),
Protection::ReadWrite if self.access.can_write() => Ok(
DescriptorMapping::Writable(writer.map_range(request.offset, request.length)?),
),
Protection::ReadWrite => Err(DataPlaneError::BadDescriptor),
},
DescriptorBackend::ReadStream(_) | DescriptorBackend::WriteStream(_) => {
Err(DataPlaneError::MappingUnsupported)
}
}
}
pub async fn close(&mut self) -> Result<(), DataPlaneError> {
let backend = self.backend.take().ok_or(DataPlaneError::BadDescriptor)?;
match backend {
DescriptorBackend::ReadBlob { .. } => Ok(()),
DescriptorBackend::WriteBlob { mut writer, .. } => {
if writer.defer_seal_if_mapped() {
Ok(())
} else {
writer.seal().await
}
}
DescriptorBackend::ReadStream(mut reader) => reader.close_descriptor().await,
DescriptorBackend::WriteStream(mut writer) => writer.close().await,
}
}
pub async fn abort(&mut self) -> Result<(), DataPlaneError> {
let backend = self.backend.take().ok_or(DataPlaneError::BadDescriptor)?;
match backend {
DescriptorBackend::ReadBlob { .. } => Ok(()),
DescriptorBackend::WriteBlob { mut writer, .. } => {
if writer.defer_abort_if_mapped() {
Ok(())
} else {
writer.abort().await
}
}
DescriptorBackend::ReadStream(mut reader) => reader.abort_descriptor().await,
DescriptorBackend::WriteStream(mut writer) => writer.abort_descriptor().await,
}
}
fn into_blob(mut self) -> Result<Blob, DataPlaneError> {
match self.backend.take() {
Some(DescriptorBackend::ReadBlob { blob, .. }) => Ok(blob),
_ => Err(DataPlaneError::BadDescriptor),
}
}
fn into_blob_writer(mut self) -> Result<BlobWriter, DataPlaneError> {
match self.backend.take() {
Some(DescriptorBackend::WriteBlob { writer, .. }) => Ok(writer),
_ => Err(DataPlaneError::BadDescriptor),
}
}
fn into_stream_reader(mut self) -> Result<StreamReader, DataPlaneError> {
match self.backend.take() {
Some(DescriptorBackend::ReadStream(reader)) => Ok(reader),
_ => Err(DataPlaneError::BadDescriptor),
}
}
fn into_stream_writer(mut self) -> Result<StreamWriter, DataPlaneError> {
match self.backend.take() {
Some(DescriptorBackend::WriteStream(writer)) => Ok(writer),
_ => Err(DataPlaneError::BadDescriptor),
}
}
}
impl Drop for Descriptor {
fn drop(&mut self) {
let Some(backend) = self.backend.take() else {
return;
};
drop(backend);
}
}
impl fmt::Debug for Descriptor {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Descriptor")
.field("kind", &self.kind)
.field("capabilities", &self.capabilities)
.field("access", &self.access)
.field("closed", &self.is_closed())
.finish()
}
}
impl DataPlane {
pub fn child_session(&self) -> ActorAddress {
self.child_session
}
pub fn arena(&self) -> &Arc<MappedArena> {
&self.arena
}
fn descriptor_from_grant(
&self,
grant: DescriptorOpenGrant,
) -> Result<Descriptor, DataPlaneError> {
match grant {
DescriptorOpenGrant::ReadBlob {
host_binding,
lease,
metadata,
cancellation,
} => {
let releaser: Arc<dyn LeaseReleaser> = Arc::new(RuntimeLeaseReleaser {
runtime: self.runtime.clone(),
child_session: self.child_session,
host_binding,
});
let blob = Blob::from_sealed_lease(self.arena.clone(), lease, metadata, releaser)?;
cancellation.disarm();
Ok(Descriptor::read_blob(blob))
}
DescriptorOpenGrant::WriteBlob {
operation,
lease,
metadata,
access,
cancellation,
} => {
let writable = WritableBlobLease::from_grant(self.arena.clone(), lease, metadata)?;
let lifecycle = Arc::new(WritableDescriptorLifecycle {
runtime: self.runtime.clone(),
operation,
writable: Arc::downgrade(&writable),
terminal: AtomicU8::new(0),
completed: AtomicBool::new(false),
});
let observer: Arc<dyn WritableViewObserver> = lifecycle.clone();
writable.set_view_observer(observer);
let writer = BlobWriter {
runtime: self.runtime.clone(),
operation,
writable,
lifecycle,
finalized: false,
};
cancellation.disarm();
Ok(Descriptor::write_blob(writer, access))
}
DescriptorOpenGrant::Stream {
operation,
host_binding,
ring,
role,
cancellation,
} => {
let endpoint = attach_mapped(&self.arena, ring, role).map_err(|error| {
DataPlaneError::StreamFault(format!("attach descriptor stream: {error:?}"))
})?;
let descriptor = match role {
Role::Consumer => Descriptor::read_stream(StreamReader {
runtime: self.runtime.clone(),
child_session: self.child_session,
operation,
host_binding,
endpoint,
terminal: None,
pending_record: None,
}),
Role::Producer => Descriptor::write_stream(StreamWriter {
runtime: self.runtime.clone(),
child_session: self.child_session,
operation,
host_binding,
endpoint,
closed: false,
}),
};
cancellation.disarm();
Ok(descriptor)
}
}
}
async fn open_inner(
&self,
path: &DataPath,
options: OpenOptions,
policy: OpenPolicy,
) -> Result<Descriptor, DataPlaneError> {
options.validate()?;
let inbox = self
.runtime
.new_inbox::<Result<DescriptorOpenGrant, DataPlaneError>>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
let reply_to = *inbox.addr();
self.runtime
.send_to(
self.child_session,
ChildSessionIn::Open {
path: path.clone(),
options,
policy,
reply_to,
},
)
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
let mut cancellation = DescriptorOpenCancellation {
runtime: self.runtime.clone(),
child_session: self.child_session,
reply_to,
armed: true,
};
let result = inbox.recv().await;
cancellation.armed = false;
self.descriptor_from_grant(result?)
}
pub async fn open(
&self,
path: &DataPath,
options: OpenOptions,
) -> Result<Descriptor, DataPlaneError> {
self.open_inner(path, options, OpenPolicy::Ordinary).await
}
pub async fn open_path(
&self,
path: &str,
options: OpenOptions,
) -> Result<Descriptor, DataPlaneError> {
let path = DataPath::parse(path)
.map_err(|error| DataPlaneError::InvalidPath(error.to_string()))?;
self.open(&path, options).await
}
pub async fn read_blob(&self, path: &DataPath) -> Result<Blob, DataPlaneError> {
self.open(path, OpenOptions::read_only()).await?.into_blob()
}
pub async fn read_blob_path(&self, path: &str) -> Result<Blob, DataPlaneError> {
let path = DataPath::parse(path)
.map_err(|error| DataPlaneError::InvalidPath(error.to_string()))?;
self.read_blob(&path).await
}
pub async fn write_blob(
&self,
path: &DataPath,
length: u64,
) -> Result<BlobWriter, DataPlaneError> {
self.open(path, OpenOptions::staged_blob(length))
.await?
.into_blob_writer()
}
pub async fn write_blob_path(
&self,
path: &str,
length: u64,
) -> Result<BlobWriter, DataPlaneError> {
let path = DataPath::parse(path)
.map_err(|error| DataPlaneError::InvalidPath(error.to_string()))?;
self.write_blob(&path, length).await
}
pub async fn read_stream(&self, path: &DataPath) -> Result<StreamReader, DataPlaneError> {
self.open_inner(
path,
OpenOptions {
access: AccessMode::ReadOnly,
..OpenOptions::default()
},
OpenPolicy::EnsureStream { replace: false },
)
.await?
.into_stream_reader()
}
pub async fn write_stream(&self, path: &DataPath) -> Result<StreamWriter, DataPlaneError> {
self.open_inner(
path,
OpenOptions {
access: AccessMode::WriteOnly,
..OpenOptions::default()
},
OpenPolicy::EnsureStream { replace: false },
)
.await?
.into_stream_writer()
}
pub async fn write_stream_replacing(
&self,
path: &DataPath,
) -> Result<StreamWriter, DataPlaneError> {
self.open_inner(
path,
OpenOptions {
access: AccessMode::WriteOnly,
..OpenOptions::default()
},
OpenPolicy::EnsureStream { replace: true },
)
.await?
.into_stream_writer()
}
pub fn close(&self) -> Result<(), DataPlaneError> {
self.runtime
.send_to(self.child_session, ChildSessionIn::Close)
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))
}
}
pub trait StreamConsumer: Send + Sync + 'static {
fn consume(&self, bytes: &[u8]) -> Result<(), String>;
}
impl DataPlane {
pub fn collect_stream(
&self,
path: DataPath,
consumer: Arc<dyn StreamConsumer>,
) -> Result<ActorCompletion<Result<(), DataPlaneError>>, DataPlaneError> {
let completion = ActorCompletion::new();
self.runtime
.spawn(StreamConsumerActor {
child_session: self.child_session,
arena: self.arena.clone(),
path,
consumer,
completion: completion.clone(),
operation: None,
host_binding: None,
endpoint: None,
pending_result: None,
finished: false,
})
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
Ok(completion)
}
}
struct WritableDescriptorLifecycle {
runtime: Runtime,
operation: ActorAddress,
writable: Weak<WritableBlobLease>,
terminal: AtomicU8,
completed: AtomicBool,
}
impl WritableDescriptorLifecycle {
const CLOSE: u8 = 1;
const ABORT: u8 = 2;
fn request(&self, terminal: u8) {
let _ = self
.terminal
.compare_exchange(0, terminal, Ordering::AcqRel, Ordering::Acquire);
if self
.writable
.upgrade()
.is_some_and(|writable| !writable.has_active_view())
{
self.finalize();
}
}
fn finalize(&self) {
if self.completed.swap(true, Ordering::AcqRel) {
return;
}
let Some(writable) = self.writable.upgrade() else {
return;
};
let lease = writable.lease();
match self.terminal.load(Ordering::Acquire) {
Self::CLOSE => match writable.seal() {
Ok(metadata) => {
let _ = self.runtime.send_to(
self.operation,
ChildOperationIn::SealRequested {
reply_to: None,
lease,
metadata,
},
);
}
Err(_) => {
let _ = self.runtime.send_to(
self.operation,
ChildOperationIn::AbortRequested {
reply_to: None,
lease,
},
);
}
},
Self::ABORT => {
let _ = writable.abort();
let _ = self.runtime.send_to(
self.operation,
ChildOperationIn::AbortRequested {
reply_to: None,
lease,
},
);
}
_ => {
self.completed.store(false, Ordering::Release);
}
}
}
}
impl WritableViewObserver for WritableDescriptorLifecycle {
fn view_released(&self) {
if self.terminal.load(Ordering::Acquire) != 0 {
self.finalize();
}
}
}
pub struct BlobWriter {
runtime: Runtime,
operation: ActorAddress,
writable: Arc<WritableBlobLease>,
lifecycle: Arc<WritableDescriptorLifecycle>,
finalized: bool,
}
impl BlobWriter {
pub fn length(&self) -> u64 {
self.writable.metadata().length
}
pub fn map(&self) -> Result<WritableArenaView, DataPlaneError> {
self.writable.map().map_err(Into::into)
}
fn copy_at(&self, offset: u64, destination: &mut [u8]) -> Result<usize, DataPlaneError> {
self.writable
.copy_at(offset, destination)
.map_err(Into::into)
}
fn copy_from(&self, offset: u64, source: &[u8]) -> Result<usize, DataPlaneError> {
let end = offset.checked_add(source.len() as u64).ok_or_else(|| {
DataPlaneError::Unsupported("blob growth is not supported".to_owned())
})?;
if end > self.length() {
return Err(DataPlaneError::Unsupported(
"blob growth is not supported".to_owned(),
));
}
self.writable.copy_from(offset, source).map_err(Into::into)
}
fn map_range(&self, offset: u64, length: u64) -> Result<WritableArenaView, DataPlaneError> {
self.writable.map_range(offset, length).map_err(Into::into)
}
pub async fn seal(&mut self) -> Result<(), DataPlaneError> {
let metadata = self.writable.seal()?;
let lease = self.writable.lease();
let ask = self
.runtime
.ask::<ChildOperationIn, Result<(), DataPlaneError>>(self.operation, |reply_to| {
ChildOperationIn::SealRequested {
reply_to: Some(reply_to),
lease,
metadata,
}
})
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
let result = ask.await;
if result.is_ok() {
self.finalized = true;
}
result
}
pub async fn abort(&mut self) -> Result<(), DataPlaneError> {
self.writable.abort()?;
self.finalized = true;
let lease = self.writable.lease();
let ask = self
.runtime
.ask::<ChildOperationIn, Result<(), DataPlaneError>>(self.operation, |reply_to| {
ChildOperationIn::AbortRequested {
reply_to: Some(reply_to),
lease,
}
})
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
ask.await
}
fn defer_seal_if_mapped(&mut self) -> bool {
if !self.writable.has_active_view() {
return false;
}
self.finalized = true;
self.lifecycle.request(WritableDescriptorLifecycle::CLOSE);
true
}
fn defer_abort_if_mapped(&mut self) -> bool {
if !self.writable.has_active_view() {
return false;
}
self.finalized = true;
self.lifecycle.request(WritableDescriptorLifecycle::ABORT);
true
}
}
impl Drop for BlobWriter {
fn drop(&mut self) {
if self.finalized {
return;
}
let should_abort = self.writable.is_finished() || self.writable.abort().is_ok();
if should_abort {
let _ = self.runtime.send_to(
self.operation,
ChildOperationIn::AbortRequested {
reply_to: None,
lease: self.writable.lease(),
},
);
}
}
}
#[derive(Clone)]
pub(crate) struct StreamOpenGrant {
operation: ActorAddress,
host_binding: ActorAddress,
ring: RingHandle,
}
#[derive(Clone)]
pub(crate) enum ChildStreamIn {
Opened(Result<StreamOpenGrant, DataPlaneError>),
Wake(Result<(), DataPlaneError>),
}
pub struct StreamWriter {
runtime: Runtime,
child_session: ActorAddress,
operation: ActorAddress,
host_binding: ActorAddress,
endpoint: Endpoint,
closed: bool,
}
impl StreamWriter {
pub fn capacity(&self) -> u64 {
self.endpoint.capacity()
}
fn send_control(&self, message: HostStreamIn) -> Result<(), DataPlaneError> {
self.runtime
.send_to(
self.child_session,
ChildSessionIn::StreamControl {
binding: self.host_binding,
message,
},
)
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))
}
async fn send_one(&mut self, kind: RecordKind, bytes: &[u8]) -> Result<(), DataPlaneError> {
loop {
match self.endpoint.send_record(kind, bytes) {
Ok(()) => {
self.send_control(HostStreamIn::DataAvailable)?;
return Ok(());
}
Err(FlowError::InsufficientSpace { .. }) => {
let inbox = self
.runtime
.new_inbox::<ChildStreamIn>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
self.send_control(HostStreamIn::WaitCapacity {
reply_to: *inbox.addr(),
})?;
match self.endpoint.send_record(kind, bytes) {
Ok(()) => {
self.send_control(HostStreamIn::DataAvailable)?;
return Ok(());
}
Err(FlowError::InsufficientSpace { .. }) => match inbox.recv().await {
ChildStreamIn::Wake(result) => result?,
ChildStreamIn::Opened(_) => {
return Err(DataPlaneError::StreamFault(
"received stream-open result while waiting for capacity"
.to_owned(),
));
}
},
Err(error) => {
return Err(DataPlaneError::StreamFault(format!(
"write stream ring: {error:?}"
)));
}
}
}
Err(error) => {
return Err(DataPlaneError::StreamFault(format!(
"write stream ring: {error:?}"
)));
}
}
}
}
pub async fn flush(&mut self) -> Result<(), DataPlaneError> {
let target = self
.endpoint
.positions()
.map_err(|error| {
DataPlaneError::StreamFault(format!("observe stream flush position: {error:?}"))
})?
.0;
loop {
let consumed = self
.endpoint
.positions()
.map_err(|error| {
DataPlaneError::StreamFault(format!("observe stream flush progress: {error:?}"))
})?
.1;
if consumed >= target {
return Ok(());
}
let inbox = self
.runtime
.new_inbox::<ChildStreamIn>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
self.send_control(HostStreamIn::WaitCapacity {
reply_to: *inbox.addr(),
})?;
self.send_control(HostStreamIn::DataAvailable)?;
if self
.endpoint
.positions()
.map_err(|error| {
DataPlaneError::StreamFault(format!("observe stream flush progress: {error:?}"))
})?
.1
>= target
{
return Ok(());
}
match inbox.recv().await {
ChildStreamIn::Wake(result) => {
if let Err(error) = result
&& self
.endpoint
.positions()
.map_err(|flow| {
DataPlaneError::StreamFault(format!(
"observe terminal flush progress: {flow:?}"
))
})?
.1
< target
{
return Err(error);
}
}
ChildStreamIn::Opened(_) => {
return Err(DataPlaneError::StreamFault(
"received stream-open result while flushing".to_owned(),
));
}
}
}
}
pub async fn write_partial(&mut self, bytes: &[u8]) -> Result<usize, DataPlaneError> {
if self.closed {
return Err(DataPlaneError::StreamClosed);
}
if bytes.is_empty() {
return Ok(0);
}
loop {
if self.endpoint.peer_terminated().map_err(|error| {
DataPlaneError::StreamFault(format!(
"observe stream peer terminal state: {error:?}"
))
})? {
return Err(DataPlaneError::BrokenPipe);
}
let available = self.endpoint.writable_payload_capacity().map_err(|error| {
DataPlaneError::StreamFault(format!("observe writable stream capacity: {error:?}"))
})?;
if available != 0 {
let count = bytes
.len()
.min(usize::try_from(available).unwrap_or(usize::MAX));
let mut record = self
.endpoint
.reserve_record(RecordKind::Data, count as u64)
.map_err(|error| {
DataPlaneError::StreamFault(format!(
"reserve partial stream record: {error:?}"
))
})?;
let (first, second) = record.spans_mut();
first.copy_from_slice(&bytes[..first.len()]);
second.copy_from_slice(&bytes[first.len()..count]);
record.commit().map_err(|error| {
DataPlaneError::StreamFault(format!("commit partial stream record: {error:?}"))
})?;
self.send_control(HostStreamIn::DataAvailable)?;
return Ok(count);
}
let inbox = self
.runtime
.new_inbox::<ChildStreamIn>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
self.send_control(HostStreamIn::WaitCapacity {
reply_to: *inbox.addr(),
})?;
if self.endpoint.writable_payload_capacity().map_err(|error| {
DataPlaneError::StreamFault(format!("recheck writable stream capacity: {error:?}"))
})? != 0
{
continue;
}
match inbox.recv().await {
ChildStreamIn::Wake(Ok(())) => {}
ChildStreamIn::Wake(Err(
DataPlaneError::StreamClosed | DataPlaneError::PeerLost,
)) => return Err(DataPlaneError::BrokenPipe),
ChildStreamIn::Wake(Err(error)) => return Err(error),
ChildStreamIn::Opened(_) => {
return Err(DataPlaneError::StreamFault(
"received stream-open result while waiting for capacity".to_owned(),
));
}
}
}
}
pub async fn write(&mut self, bytes: &[u8]) -> Result<(), DataPlaneError> {
let mut completed = 0;
while completed < bytes.len() {
completed += self.write_partial(&bytes[completed..]).await?;
}
Ok(())
}
pub async fn close(&mut self) -> Result<(), DataPlaneError> {
if self.closed {
return Ok(());
}
self.send_one(RecordKind::Eof, &[]).await?;
self.flush().await?;
self.closed = true;
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::OperationDone {
operation: self.operation,
},
);
Ok(())
}
pub fn abort(&mut self) -> Result<(), DataPlaneError> {
if self.closed {
return Ok(());
}
self.closed = true;
self.send_control(HostStreamIn::Close {
clean: false,
reply_to: None,
})
}
async fn abort_descriptor(&mut self) -> Result<(), DataPlaneError> {
if self.closed {
return Ok(());
}
let inbox = self
.runtime
.new_inbox::<ChildStreamIn>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
self.closed = true;
self.send_control(HostStreamIn::Close {
clean: false,
reply_to: Some(*inbox.addr()),
})?;
let result = match inbox.recv().await {
ChildStreamIn::Wake(result) => result,
ChildStreamIn::Opened(_) => Err(DataPlaneError::StreamFault(
"received stream-open result while aborting writer".to_owned(),
)),
};
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::OperationDone {
operation: self.operation,
},
);
result
}
}
impl Drop for StreamWriter {
fn drop(&mut self) {
if !self.closed {
let _ = self.send_control(HostStreamIn::Close {
clean: false,
reply_to: None,
});
}
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::OperationDone {
operation: self.operation,
},
);
}
}
#[derive(Clone)]
enum StreamReadTerminal {
Eof,
Error(DataPlaneError),
}
pub struct StreamReader {
runtime: Runtime,
child_session: ActorAddress,
operation: ActorAddress,
host_binding: ActorAddress,
endpoint: Endpoint,
terminal: Option<StreamReadTerminal>,
pending_record: Option<(RecordCursor, u64)>,
}
impl StreamReader {
pub fn capacity(&self) -> u64 {
self.endpoint.capacity()
}
fn send_control(&self, message: HostStreamIn) -> Result<(), DataPlaneError> {
self.runtime
.send_to(
self.child_session,
ChildSessionIn::StreamControl {
binding: self.host_binding,
message,
},
)
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))
}
async fn close_clean(&mut self) -> Result<(), DataPlaneError> {
let inbox = self
.runtime
.new_inbox::<ChildStreamIn>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
self.send_control(HostStreamIn::Close {
clean: true,
reply_to: Some(*inbox.addr()),
})?;
match inbox.recv().await {
ChildStreamIn::Wake(result) => result,
ChildStreamIn::Opened(_) => Err(DataPlaneError::StreamFault(
"received stream-open result while closing reader".to_owned(),
)),
}
}
fn terminal_read_count(&self) -> Option<Result<usize, DataPlaneError>> {
self.terminal.as_ref().map(|terminal| match terminal {
StreamReadTerminal::Eof => Ok(0),
StreamReadTerminal::Error(error) => Err(error.clone()),
})
}
fn release_record(&mut self, cursor: RecordCursor) -> Result<(), DataPlaneError> {
self.endpoint
.release_record_cursor(cursor)
.map_err(|error| {
DataPlaneError::StreamFault(format!("consume stream ring: {error:?}"))
})?;
self.send_control(HostStreamIn::CapacityAvailable)
}
pub async fn read_into(&mut self, destination: &mut [u8]) -> Result<usize, DataPlaneError> {
if destination.is_empty() {
return Ok(0);
}
if let Some(result) = self.terminal_read_count() {
return result;
}
loop {
if self.pending_record.is_none() {
if let Some(cursor) = self.endpoint.record_cursor().map_err(|error| {
DataPlaneError::StreamFault(format!("read stream ring: {error:?}"))
})? {
match cursor.kind() {
RecordKind::Data if cursor.is_empty() => {
self.release_record(cursor)?;
continue;
}
RecordKind::Data => {
self.pending_record = Some((cursor, 0));
}
RecordKind::Eof => {
self.release_record(cursor)?;
self.terminal = Some(StreamReadTerminal::Eof);
let close_result = self.close_clean().await;
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::OperationDone {
operation: self.operation,
},
);
close_result?;
return Ok(0);
}
RecordKind::Fault => {
let mut bytes = vec![0_u8; cursor.len()];
let count = self
.endpoint
.copy_record_range(cursor, 0, &mut bytes)
.map_err(|error| {
DataPlaneError::StreamFault(format!(
"read stream fault record: {error:?}"
))
})?;
bytes.truncate(count);
self.release_record(cursor)?;
let error = DataPlaneError::StreamFault(
String::from_utf8_lossy(&bytes).into_owned(),
);
self.terminal = Some(StreamReadTerminal::Error(error.clone()));
let _ = self.send_control(HostStreamIn::Close {
clean: false,
reply_to: None,
});
return Err(error);
}
}
}
}
if let Some((cursor, offset)) = self.pending_record {
let count = self
.endpoint
.copy_record_range(cursor, offset, destination)
.map_err(|error| {
DataPlaneError::StreamFault(format!(
"copy partial stream record: {error:?}"
))
})?;
let next_offset = offset + count as u64;
if next_offset == cursor.len() as u64 {
self.pending_record = None;
self.release_record(cursor)?;
} else {
self.pending_record = Some((cursor, next_offset));
}
return Ok(count);
}
let inbox = self
.runtime
.new_inbox::<ChildStreamIn>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
self.send_control(HostStreamIn::WaitData {
reply_to: *inbox.addr(),
})?;
if self
.endpoint
.record_cursor()
.map_err(|error| {
DataPlaneError::StreamFault(format!("recheck stream ring: {error:?}"))
})?
.is_some()
{
continue;
}
match inbox.recv().await {
ChildStreamIn::Wake(Ok(())) => {}
ChildStreamIn::Wake(Err(error)) => {
self.terminal = Some(StreamReadTerminal::Error(error.clone()));
return Err(error);
}
ChildStreamIn::Opened(_) => {
return Err(DataPlaneError::StreamFault(
"received stream-open result while waiting for data".to_owned(),
));
}
}
}
}
pub async fn read(&mut self) -> Result<Option<Vec<u8>>, DataPlaneError> {
if matches!(self.terminal, Some(StreamReadTerminal::Eof)) {
return Ok(None);
}
let capacity = usize::try_from(self.capacity())
.unwrap_or(usize::MAX)
.max(1);
let mut bytes = vec![0_u8; capacity];
let count = self.read_into(&mut bytes).await?;
if count == 0 {
Ok(None)
} else {
bytes.truncate(count);
Ok(Some(bytes))
}
}
async fn close_descriptor(&mut self) -> Result<(), DataPlaneError> {
if self.terminal.is_some() {
return Ok(());
}
let result = self.close_clean().await;
self.terminal = Some(StreamReadTerminal::Eof);
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::OperationDone {
operation: self.operation,
},
);
result
}
async fn abort_descriptor(&mut self) -> Result<(), DataPlaneError> {
if self.terminal.is_some() {
return Ok(());
}
let inbox = self
.runtime
.new_inbox::<ChildStreamIn>()
.map_err(|error| DataPlaneError::SessionFailed(error.to_string()))?;
self.terminal = Some(StreamReadTerminal::Error(
DataPlaneError::OperationCancelled,
));
self.send_control(HostStreamIn::Close {
clean: false,
reply_to: Some(*inbox.addr()),
})?;
let result = match inbox.recv().await {
ChildStreamIn::Wake(result) => result,
ChildStreamIn::Opened(_) => Err(DataPlaneError::StreamFault(
"received stream-open result while aborting reader".to_owned(),
)),
};
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::OperationDone {
operation: self.operation,
},
);
result
}
}
impl Drop for StreamReader {
fn drop(&mut self) {
if self.terminal.is_none() {
let _ = self.send_control(HostStreamIn::Close {
clean: false,
reply_to: None,
});
}
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::OperationDone {
operation: self.operation,
},
);
}
}
struct StreamConsumerActor {
child_session: ActorAddress,
arena: Arc<MappedArena>,
path: DataPath,
consumer: Arc<dyn StreamConsumer>,
completion: ActorCompletion<Result<(), DataPlaneError>>,
operation: Option<ActorAddress>,
host_binding: Option<ActorAddress>,
endpoint: Option<Endpoint>,
pending_result: Option<Result<(), DataPlaneError>>,
finished: bool,
}
enum ConsumerDrainStep {
Empty,
Data,
Eof,
Fault(String),
}
fn consume_next_record(
endpoint: &mut Endpoint,
consumer: &dyn StreamConsumer,
) -> Result<ConsumerDrainStep, DataPlaneError> {
let Some(view) = endpoint
.peek_record()
.map_err(|error| DataPlaneError::StreamFault(format!("collect stream ring: {error:?}")))?
else {
return Ok(ConsumerDrainStep::Empty);
};
let kind = view.kind();
let (first, second) = view.spans();
let fault = (kind == RecordKind::Fault).then(|| {
let mut reason = Vec::with_capacity(first.len() + second.len());
reason.extend_from_slice(first);
reason.extend_from_slice(second);
String::from_utf8_lossy(&reason).into_owned()
});
if kind == RecordKind::Data {
consumer
.consume(first)
.and_then(|()| consumer.consume(second))
.map_err(DataPlaneError::StreamFault)?;
}
view.release().map_err(|error| {
DataPlaneError::StreamFault(format!("release collected stream ring: {error:?}"))
})?;
Ok(match kind {
RecordKind::Data => ConsumerDrainStep::Data,
RecordKind::Eof => ConsumerDrainStep::Eof,
RecordKind::Fault => ConsumerDrainStep::Fault(fault.expect("fault payload captured")),
})
}
impl StreamConsumerActor {
fn send_control(&self, ctx: &Ctx<'_>, binding: ActorAddress, message: HostStreamIn) {
let _ = ctx.send(
self.child_session,
ChildSessionIn::StreamControl { binding, message },
);
}
fn complete_now(&mut self, ctx: &Ctx<'_>, result: Result<(), DataPlaneError>) {
if self.finished {
return;
}
self.finished = true;
if let Some(operation) = self.operation {
let _ = ctx.send(
self.child_session,
ChildSessionIn::OperationDone { operation },
);
}
let _ = self.completion.complete(result);
ctx.stop_self();
}
fn finish(&mut self, ctx: &Ctx<'_>, result: Result<(), DataPlaneError>, clean: bool) {
if self.finished || self.pending_result.is_some() {
return;
}
if clean && let Some(host_binding) = self.host_binding {
self.pending_result = Some(result);
self.send_control(
ctx,
host_binding,
HostStreamIn::Close {
clean: true,
reply_to: Some(ctx.self_addr()),
},
);
return;
}
if let Some(host_binding) = self.host_binding {
self.send_control(
ctx,
host_binding,
HostStreamIn::Close {
clean: false,
reply_to: None,
},
);
}
self.complete_now(ctx, result);
}
fn drain(&mut self, ctx: &Ctx<'_>) {
loop {
let step = consume_next_record(
self.endpoint
.as_mut()
.expect("collector endpoint is installed before drain"),
self.consumer.as_ref(),
);
match step {
Ok(ConsumerDrainStep::Empty) => {
if let Some(host_binding) = self.host_binding {
self.send_control(
ctx,
host_binding,
HostStreamIn::WaitData {
reply_to: ctx.self_addr(),
},
);
}
return;
}
Ok(ConsumerDrainStep::Data) => {
if let Some(host_binding) = self.host_binding {
self.send_control(ctx, host_binding, HostStreamIn::CapacityAvailable);
}
}
Ok(ConsumerDrainStep::Eof) => {
if let Some(host_binding) = self.host_binding {
self.send_control(ctx, host_binding, HostStreamIn::CapacityAvailable);
}
self.finish(ctx, Ok(()), true);
return;
}
Ok(ConsumerDrainStep::Fault(reason)) => {
self.finish(ctx, Err(DataPlaneError::StreamFault(reason)), false);
return;
}
Err(error) => {
self.finish(ctx, Err(error), false);
return;
}
}
}
}
}
impl ActorInterface for StreamConsumerActor {
type Incoming = ChildStreamIn;
type Response = ();
fn on_start(&mut self, ctx: &Ctx<'_>) {
let _ = ctx.send(
self.child_session,
ChildSessionIn::OpenReadStream {
path: self.path.clone(),
reply_to: ctx.self_addr(),
replace: false,
},
);
}
fn handle(&mut self, ctx: &Ctx<'_>, message: ChildStreamIn) {
match message {
ChildStreamIn::Opened(Ok(grant)) => {
match attach_mapped(&self.arena, grant.ring, Role::Consumer) {
Ok(endpoint) => {
self.operation = Some(grant.operation);
self.host_binding = Some(grant.host_binding);
self.endpoint = Some(endpoint);
self.drain(ctx);
}
Err(error) => self.finish(
ctx,
Err(DataPlaneError::StreamFault(format!(
"attach stream collector: {error:?}"
))),
false,
),
}
}
ChildStreamIn::Opened(Err(error)) => {
self.finish(ctx, Err(error), false);
}
ChildStreamIn::Wake(result) => {
if let Some(pending) = self.pending_result.take() {
let completed = match result {
Ok(()) => pending,
Err(error) => Err(error),
};
self.complete_now(ctx, completed);
} else {
match result {
Ok(()) => self.drain(ctx),
Err(error) => self.finish(ctx, Err(error), false),
}
}
}
}
}
fn on_stop(&mut self, ctx: &Ctx<'_>) {
if !self.finished {
if let Some(host_binding) = self.host_binding {
self.send_control(
ctx,
host_binding,
HostStreamIn::Close {
clean: false,
reply_to: None,
},
);
} else {
let _ = ctx.send(
self.child_session,
ChildSessionIn::CancelStream {
reply_to: ctx.self_addr(),
},
);
}
let _ = self
.completion
.complete(Err(DataPlaneError::OperationCancelled));
}
}
}
pub struct ChildDataPlaneSessionActor {
runtime: Runtime,
host_session: ActorAddress,
arena_generation: u64,
job_capability: JobCapability,
child_node: Option<[u8; 32]>,
session_generation: Option<u64>,
attach_reply: Option<ActorAddress>,
operations: HashSet<ActorAddress>,
open_operations: HashMap<ActorAddress, ActorAddress>,
state: ChildSessionState,
stream_operations: HashMap<ActorAddress, ActorAddress>,
pending_blob_releases: usize,
deferred_blob_opens: VecDeque<ChildSessionIn>,
}
impl ChildDataPlaneSessionActor {
pub fn state(&self) -> ChildSessionState {
self.state
}
fn start_stream_open(
&mut self,
ctx: &Ctx<'_>,
path: DataPath,
reply_to: ActorAddress,
role: Role,
replace: bool,
) {
if self.state != ChildSessionState::Running {
let _ = ctx.send(
reply_to,
Err::<StreamOpenGrant, _>(DataPlaneError::SessionNotRunning),
);
return;
}
let actor = StreamOpenOperationActor {
host_session: self.host_session,
child_session: ctx.self_addr(),
path,
role,
replace,
reply_to,
replied: false,
};
match ctx.spawn(actor) {
Ok(operation) => {
self.operations.insert(operation);
self.stream_operations.insert(reply_to, operation);
}
Err(error) => {
let _ = ctx.send(
reply_to,
Err::<StreamOpenGrant, _>(DataPlaneError::SessionFailed(error.to_string())),
);
}
}
}
}
impl ActorInterface for ChildDataPlaneSessionActor {
type Incoming = ChildSessionIn;
type Response = ();
fn on_start(&mut self, ctx: &Ctx<'_>) {
if ctx
.send(
self.host_session,
HostSessionIn::Attach {
child_session: ctx.self_addr(),
arena_generation: self.arena_generation,
job_capability: self.job_capability,
child_node: self.child_node,
},
)
.is_err()
{
self.state = ChildSessionState::Closed;
if let Some(reply_to) = self.attach_reply.take() {
let _ = ctx.send(
reply_to,
Err::<u64, _>(DataPlaneError::SessionFailed(
"route to host data-plane session is unavailable".to_owned(),
)),
);
}
}
}
fn handle(&mut self, ctx: &Ctx<'_>, message: ChildSessionIn) {
match message {
ChildSessionIn::Attached { session_generation }
if self.state == ChildSessionState::Attaching =>
{
self.session_generation = Some(session_generation);
self.state = ChildSessionState::Running;
if let Some(reply_to) = self.attach_reply.take() {
let _ = ctx.send(reply_to, Ok::<_, DataPlaneError>(session_generation));
}
}
ChildSessionIn::AttachmentFailed { error }
if self.state == ChildSessionState::Attaching =>
{
self.state = ChildSessionState::Closed;
if let Some(reply_to) = self.attach_reply.take() {
let _ = ctx.send(reply_to, Err::<u64, _>(error));
}
}
ChildSessionIn::AttachmentDeadline if self.state == ChildSessionState::Attaching => {
self.state = ChildSessionState::Closed;
if let Some(reply_to) = self.attach_reply.take() {
let _ = ctx.send(
reply_to,
Err::<u64, _>(DataPlaneError::SessionFailed(
"data-plane attachment deadline elapsed".to_owned(),
)),
);
}
ctx.stop_self();
}
ChildSessionIn::Open {
path,
options,
policy,
reply_to,
} => {
if self.pending_blob_releases != 0 {
self.deferred_blob_opens.push_back(ChildSessionIn::Open {
path,
options,
policy,
reply_to,
});
return;
}
if self.state != ChildSessionState::Running {
let _ = ctx.send(
reply_to,
Err::<DescriptorOpenGrant, _>(DataPlaneError::SessionNotRunning),
);
return;
}
let actor = DescriptorOpenOperationActor {
runtime: self.runtime.clone(),
host_session: self.host_session,
child_session: ctx.self_addr(),
path,
options,
policy,
open_reply: Some(reply_to),
finish_reply: None,
grant: None,
state: WriteOperationState::Opening,
replied: false,
};
match ctx.spawn(actor) {
Ok(operation) => {
self.operations.insert(operation);
self.open_operations.insert(reply_to, operation);
}
Err(error) => {
let _ = ctx.send(
reply_to,
Err::<DescriptorOpenGrant, _>(DataPlaneError::SessionFailed(
error.to_string(),
)),
);
}
}
}
ChildSessionIn::CancelOpen { reply_to } => {
if let Some(operation) = self.open_operations.remove(&reply_to) {
self.operations.remove(&operation);
let _ = ctx.stop_actor(operation);
}
}
ChildSessionIn::OpenReadStream {
path,
reply_to,
replace,
} => {
self.start_stream_open(ctx, path, reply_to, Role::Consumer, replace);
}
ChildSessionIn::CancelStream { reply_to } => {
if let Some(operation) = self.stream_operations.remove(&reply_to) {
self.operations.remove(&operation);
let _ = ctx.stop_actor(operation);
}
}
ChildSessionIn::StreamWake { reply_to, result } => {
let _ = ctx.send(reply_to, ChildStreamIn::Wake(result));
}
ChildSessionIn::StreamControl { binding, message } => {
let _ = ctx.send(
self.host_session,
HostSessionIn::StreamControl { binding, message },
);
}
ChildSessionIn::BlobReleased => {
self.pending_blob_releases = self.pending_blob_releases.saturating_sub(1);
if self.pending_blob_releases == 0 {
while let Some(deferred) = self.deferred_blob_opens.pop_front() {
self.handle(ctx, deferred);
if self.pending_blob_releases != 0 {
break;
}
}
}
}
ChildSessionIn::ReleaseBlob {
binding,
lease_id,
generation,
} => {
self.pending_blob_releases = self.pending_blob_releases.saturating_add(1);
let _ = ctx.send(
self.host_session,
HostSessionIn::ReleaseBlob {
binding,
lease_id,
generation,
},
);
}
ChildSessionIn::BlobOpened {
operation,
host_binding,
lease,
metadata,
} => {
if self.operations.contains(&operation) {
let _ = ctx.send(
operation,
ChildOperationIn::ReadOpened {
host_binding,
lease,
metadata,
},
);
}
}
ChildSessionIn::WriteBlobOpened {
operation,
host_binding,
lease,
metadata,
} => {
if self.operations.contains(&operation) {
let _ = ctx.send(
operation,
ChildOperationIn::WriteOpened {
host_binding,
lease,
metadata,
},
);
}
}
ChildSessionIn::StreamOpened {
operation,
host_binding,
ring,
role,
} => {
if self.operations.contains(&operation) {
let _ = ctx.send(
operation,
ChildOperationIn::StreamOpened {
host_binding,
ring,
role,
},
);
}
}
ChildSessionIn::OperationFailed { operation, error } => {
if self.operations.contains(&operation) {
let _ = ctx.send(operation, ChildOperationIn::Failed(error));
}
}
ChildSessionIn::WritePublished { operation } => {
if self.operations.contains(&operation) {
let _ = ctx.send(operation, ChildOperationIn::WritePublished);
}
}
ChildSessionIn::WriteAborted { operation } => {
if self.operations.contains(&operation) {
let _ = ctx.send(operation, ChildOperationIn::WriteAborted);
}
}
ChildSessionIn::OperationDone { operation } => {
self.operations.remove(&operation);
self.open_operations
.retain(|_, open_operation| *open_operation != operation);
self.stream_operations
.retain(|_, stream_operation| *stream_operation != operation);
}
ChildSessionIn::Close => {
if matches!(
self.state,
ChildSessionState::Closing | ChildSessionState::Closed
) {
return;
}
self.state = ChildSessionState::Closing;
for operation in self.operations.iter().copied() {
let _ = ctx.stop_actor(operation);
}
self.open_operations.clear();
self.stream_operations.clear();
let _ = ctx.send(self.host_session, HostSessionIn::Close);
self.state = ChildSessionState::Closed;
}
_ => {}
}
}
}
#[derive(Clone)]
enum ChildOperationIn {
ReadOpened {
host_binding: ActorAddress,
lease: BlobLease,
metadata: BlobMetadata,
},
WriteOpened {
host_binding: ActorAddress,
lease: BlobLease,
metadata: BlobMetadata,
},
StreamOpened {
host_binding: ActorAddress,
ring: RingHandle,
role: Role,
},
Failed(DataPlaneError),
SealRequested {
reply_to: Option<ActorAddress>,
lease: BlobLease,
metadata: BlobMetadata,
},
AbortRequested {
reply_to: Option<ActorAddress>,
lease: BlobLease,
},
WritePublished,
WriteAborted,
}
struct StreamOpenOperationActor {
host_session: ActorAddress,
child_session: ActorAddress,
path: DataPath,
role: Role,
reply_to: ActorAddress,
replace: bool,
replied: bool,
}
impl StreamOpenOperationActor {
fn finish(&mut self, ctx: &Ctx<'_>, result: Result<StreamOpenGrant, DataPlaneError>) {
self.replied = true;
let _ = ctx.send(self.reply_to, ChildStreamIn::Opened(result));
let _ = ctx.send(
self.child_session,
ChildSessionIn::OperationDone {
operation: ctx.self_addr(),
},
);
ctx.stop_self();
}
}
impl ActorInterface for StreamOpenOperationActor {
type Incoming = ChildOperationIn;
type Response = ();
fn on_start(&mut self, ctx: &Ctx<'_>) {
let access = match self.role {
Role::Consumer => AccessMode::ReadOnly,
Role::Producer => AccessMode::WriteOnly,
};
let message = HostSessionIn::Open {
path: self.path.clone(),
options: OpenOptions {
access,
..OpenOptions::default()
},
policy: OpenPolicy::EnsureStream {
replace: self.replace,
},
child_session: self.child_session,
operation: ctx.self_addr(),
};
if let Err(error) = ctx.send(self.host_session, message) {
self.finish(ctx, Err(DataPlaneError::SessionFailed(error.to_string())));
}
}
fn handle(&mut self, ctx: &Ctx<'_>, message: ChildOperationIn) {
match message {
ChildOperationIn::StreamOpened {
host_binding,
ring,
role,
} if role == self.role => self.finish(
ctx,
Ok(StreamOpenGrant {
operation: ctx.self_addr(),
host_binding,
ring,
}),
),
ChildOperationIn::StreamOpened { .. } => self.finish(
ctx,
Err(DataPlaneError::StreamFault(
"host opened stream with the wrong ring role".to_owned(),
)),
),
ChildOperationIn::Failed(error) => self.finish(ctx, Err(error)),
_ => {}
}
}
fn on_stop(&mut self, ctx: &Ctx<'_>) {
if !self.replied {
let _ = ctx.send(
self.reply_to,
ChildStreamIn::Opened(Err(DataPlaneError::OperationCancelled)),
);
let _ = ctx.send(
self.host_session,
HostSessionIn::CancelOpen {
operation: ctx.self_addr(),
},
);
}
}
}
struct RuntimeLeaseReleaser {
runtime: Runtime,
child_session: ActorAddress,
host_binding: ActorAddress,
}
impl LeaseReleaser for RuntimeLeaseReleaser {
fn release(&self, lease: BlobLease) {
let _ = self.runtime.send_to(
self.child_session,
ChildSessionIn::ReleaseBlob {
binding: self.host_binding,
lease_id: lease.lease_id,
generation: lease.generation,
},
);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum WriteOperationState {
Opening,
Filling,
Sealing,
Aborting,
Finished,
}
struct DescriptorOpenOperationActor {
runtime: Runtime,
host_session: ActorAddress,
child_session: ActorAddress,
path: DataPath,
options: OpenOptions,
policy: OpenPolicy,
open_reply: Option<ActorAddress>,
finish_reply: Option<ActorAddress>,
grant: Option<(ActorAddress, BlobLease, BlobMetadata)>,
state: WriteOperationState,
replied: bool,
}
impl DescriptorOpenOperationActor {
fn operation_done(&self, ctx: &Ctx<'_>) {
let _ = ctx.send(
self.child_session,
ChildSessionIn::OperationDone {
operation: ctx.self_addr(),
},
);
}
fn finish_open(
&mut self,
ctx: &Ctx<'_>,
result: Result<DescriptorOpenGrant, DataPlaneError>,
keep_alive: bool,
) {
self.replied = true;
if let Some(reply_to) = self.open_reply.take() {
let _ = ctx.send(reply_to, result);
}
if !keep_alive {
self.state = WriteOperationState::Finished;
self.operation_done(ctx);
ctx.stop_self();
}
}
fn finish_write(&mut self, ctx: &Ctx<'_>, result: Result<(), DataPlaneError>) {
self.state = WriteOperationState::Finished;
if let Some(reply_to) = self.finish_reply.take() {
let _ = ctx.send(reply_to, result);
}
self.operation_done(ctx);
ctx.stop_self();
}
fn fail(&mut self, ctx: &Ctx<'_>, error: DataPlaneError) {
if self.open_reply.is_some() {
self.finish_open(ctx, Err(error), false);
} else {
self.finish_write(ctx, Err(error));
}
}
}
impl ActorInterface for DescriptorOpenOperationActor {
type Incoming = ChildOperationIn;
type Response = ();
fn on_start(&mut self, ctx: &Ctx<'_>) {
if ctx
.send(
self.host_session,
HostSessionIn::Open {
path: self.path.clone(),
options: self.options.clone(),
policy: self.policy,
child_session: self.child_session,
operation: ctx.self_addr(),
},
)
.is_err()
{
self.fail(
ctx,
DataPlaneError::SessionFailed("send descriptor open to host session".to_owned()),
);
}
}
fn handle(&mut self, ctx: &Ctx<'_>, message: ChildOperationIn) {
match message {
ChildOperationIn::ReadOpened {
host_binding,
lease,
metadata,
} if self.state == WriteOperationState::Opening => {
let cancellation = Arc::new(DescriptorGrantCancellation {
runtime: self.runtime.clone(),
action: DescriptorGrantCancellationAction::HostOpen {
host_session: self.host_session,
operation: ctx.self_addr(),
},
armed: AtomicBool::new(true),
});
self.finish_open(
ctx,
Ok(DescriptorOpenGrant::ReadBlob {
host_binding,
lease,
metadata,
cancellation,
}),
false,
);
}
ChildOperationIn::WriteOpened {
host_binding,
lease,
metadata,
} if self.state == WriteOperationState::Opening => {
self.state = WriteOperationState::Filling;
self.grant = Some((host_binding, lease, metadata.clone()));
let cancellation = Arc::new(DescriptorGrantCancellation {
runtime: self.runtime.clone(),
action: DescriptorGrantCancellationAction::WriteBlob {
operation: ctx.self_addr(),
lease,
},
armed: AtomicBool::new(true),
});
self.finish_open(
ctx,
Ok(DescriptorOpenGrant::WriteBlob {
operation: ctx.self_addr(),
lease,
metadata,
access: self.options.access,
cancellation,
}),
true,
);
}
ChildOperationIn::StreamOpened {
host_binding,
ring,
role,
} if self.state == WriteOperationState::Opening => {
let expected_role = match self.options.access {
AccessMode::ReadOnly => Role::Consumer,
AccessMode::WriteOnly => Role::Producer,
AccessMode::ReadWrite => {
self.fail(
ctx,
DataPlaneError::Unsupported(
"read-write stream descriptors are not supported".to_owned(),
),
);
return;
}
};
if role != expected_role {
self.fail(
ctx,
DataPlaneError::StreamFault(
"host opened stream with the wrong ring role".to_owned(),
),
);
return;
}
let cancellation = Arc::new(DescriptorGrantCancellation {
runtime: self.runtime.clone(),
action: DescriptorGrantCancellationAction::HostOpen {
host_session: self.host_session,
operation: ctx.self_addr(),
},
armed: AtomicBool::new(true),
});
self.finish_open(
ctx,
Ok(DescriptorOpenGrant::Stream {
operation: ctx.self_addr(),
host_binding,
ring,
role,
cancellation,
}),
false,
);
}
ChildOperationIn::SealRequested {
reply_to,
lease,
metadata,
} if self.state == WriteOperationState::Filling => {
let Some((host_binding, expected_lease, expected_metadata)) = self.grant.clone()
else {
self.fail(ctx, DataPlaneError::OperationCancelled);
return;
};
if lease != expected_lease || metadata != expected_metadata {
self.fail(
ctx,
DataPlaneError::Blob(crate::protocol::BlobFailure::InvalidLease),
);
return;
}
self.state = WriteOperationState::Sealing;
self.finish_reply = reply_to;
if ctx
.send(
self.host_session,
HostSessionIn::SealWriteBlob {
binding: host_binding,
operation: ctx.self_addr(),
lease,
metadata,
},
)
.is_err()
{
self.fail(
ctx,
DataPlaneError::SessionFailed(
"send descriptor blob seal to host session".to_owned(),
),
);
}
}
ChildOperationIn::AbortRequested { reply_to, lease }
if matches!(
self.state,
WriteOperationState::Filling | WriteOperationState::Sealing
) =>
{
let Some((host_binding, expected_lease, _)) = self.grant.clone() else {
self.fail(ctx, DataPlaneError::OperationCancelled);
return;
};
if lease != expected_lease {
self.fail(
ctx,
DataPlaneError::Blob(crate::protocol::BlobFailure::InvalidLease),
);
return;
}
self.state = WriteOperationState::Aborting;
self.finish_reply = reply_to;
if ctx
.send(
self.host_session,
HostSessionIn::AbortWriteBlob {
binding: host_binding,
operation: ctx.self_addr(),
lease_id: lease.lease_id,
generation: lease.generation,
},
)
.is_err()
{
self.fail(
ctx,
DataPlaneError::SessionFailed(
"send descriptor blob abort to host session".to_owned(),
),
);
}
}
ChildOperationIn::WritePublished if self.state == WriteOperationState::Sealing => {
self.finish_write(ctx, Ok(()));
}
ChildOperationIn::WriteAborted if self.state == WriteOperationState::Aborting => {
self.finish_write(ctx, Ok(()));
}
ChildOperationIn::Failed(error) => self.fail(ctx, error),
_ => {}
}
}
fn on_stop(&mut self, ctx: &Ctx<'_>) {
if !self.replied {
let _ = ctx.send(
self.host_session,
HostSessionIn::CancelOpen {
operation: ctx.self_addr(),
},
);
if let Some(reply_to) = self.open_reply.take() {
let _ = ctx.send(
reply_to,
Err::<DescriptorOpenGrant, _>(DataPlaneError::OperationCancelled),
);
}
}
if let Some(reply_to) = self.finish_reply.take() {
let _ = ctx.send(reply_to, Err::<(), _>(DataPlaneError::OperationCancelled));
}
}
}
pub fn parse_actor_address(encoded: &str) -> Result<ActorAddress, DataPlaneError> {
if encoded.len() != 64 {
return Err(DataPlaneError::SessionFailed(
"data-plane actor address must contain 64 hexadecimal digits".to_owned(),
));
}
let mut bytes = [0_u8; 32];
for (index, pair) in encoded.as_bytes().chunks_exact(2).enumerate() {
let text = std::str::from_utf8(pair).map_err(|_| {
DataPlaneError::SessionFailed("data-plane actor address is not hexadecimal".to_owned())
})?;
bytes[index] = u8::from_str_radix(text, 16).map_err(|_| {
DataPlaneError::SessionFailed("data-plane actor address is not hexadecimal".to_owned())
})?;
}
Ok(ActorAddress(bytes))
}