use super::*;
use crate::ProcessOutputEvent;
#[path = "process_output_wire.rs"]
mod wire;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum CompilerOutputError {
Admission(HubError),
Terminal(Terminal),
}
impl From<HubError> for CompilerOutputError {
fn from(error: HubError) -> Self {
Self::Admission(error)
}
}
pub(crate) struct CompilerOutputRead {
hub: Arc<OperationHub>,
process: OpaqueToken,
operation: OpaqueToken,
session: Arc<ProcessSession>,
cancellation: CancellationToken,
completed: bool,
disposition: OutputLeaseDisposition,
}
#[derive(Clone, Copy, Eq, PartialEq)]
enum OutputLeaseDisposition {
Native,
WirePending,
Transferred,
}
pub(crate) struct CompilerOutput {
event: Option<ProcessOutputEvent>,
lease: CompilerOutputRead,
}
impl CompilerOutput {
pub(crate) fn collect<T>(
mut self,
consume: impl FnOnce(Option<&ProcessOutputEvent>) -> T,
) -> Result<T, CompilerOutputError> {
let mut state = self.lease.hub.state.lock().map_err(|_| HubError::Closed)?;
let operation = state
.operations
.get(&self.lease.operation)
.ok_or(HubError::Closed)?;
if let Some(result) = operation.terminal {
return Err(CompilerOutputError::Terminal(result.terminal));
}
if !state.resources.contains_key(&self.lease.process) {
return Err(HubError::Closed.into());
}
let consumed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
consume(self.event.as_ref())
}));
let notify = if consumed.is_ok() {
self.lease.completed = true;
OperationHub::terminal_locked(
&mut state,
self.lease.operation,
TerminalResult {
terminal: Terminal::Completed,
resource: None,
},
)?
} else {
None
};
drop(state);
if let Some(notify) = notify {
notify.notify_one();
}
match consumed {
Ok(value) => Ok(value),
Err(panic) => std::panic::resume_unwind(panic),
}
}
}
impl OperationHub {
pub(crate) fn begin_compiler_output(
self: &Arc<Self>,
store: u64,
process: OpaqueToken,
) -> Result<CompilerOutputRead, HubError> {
let mut state = self.state.lock().map_err(|_| HubError::Closed)?;
self.begin_compiler_output_locked(&mut state, store, process)
}
fn begin_compiler_output_locked(
self: &Arc<Self>,
state: &mut State,
store: u64,
process: OpaqueToken,
) -> Result<CompilerOutputRead, HubError> {
let slot = state.resources.get(&process).ok_or(HubError::Closed)?;
Self::validate_resource(slot, store, PROCESS_KIND, PROCESS_RIGHT)?;
let ResourceValue::CompilerProcess(value) = &slot.value else {
return Err(HubError::WrongKind);
};
if value.output_busy {
return Err(HubError::Quota);
}
let session = Arc::clone(value.session.as_ref().ok_or(HubError::Closed)?);
if Self::transfer_capacity(state).saturating_add(MAX_PROCESS_OUTPUT_CHUNK)
> self.blob_limits.maximum_sketch_bytes
{
return Err(HubError::Quota);
}
let (operation, _) =
self.submit_locked(state, store, Some(process), PROCESS_KIND, PROCESS_RIGHT)?;
let cancellation = CancellationSource::new();
let token = cancellation.token();
state
.operations
.get_mut(&operation)
.ok_or(HubError::Closed)?
.producer_cancel = Some(cancellation);
let ResourceValue::CompilerProcess(value) = &mut state
.resources
.get_mut(&process)
.ok_or(HubError::Closed)?
.value
else {
return Err(HubError::WrongKind);
};
value.output_busy = true;
state.reserved_process_output_bytes += MAX_PROCESS_OUTPUT_CHUNK;
Self::record_transfer_capacity(state);
Ok(CompilerOutputRead {
hub: Arc::clone(self),
process,
operation,
session,
cancellation: token,
completed: false,
disposition: OutputLeaseDisposition::Native,
})
}
}
impl CompilerOutputRead {
pub(crate) async fn receive(self) -> Result<CompilerOutput, CompilerOutputError> {
let event = self.receive_event().await?;
Ok(CompilerOutput { event, lease: self })
}
async fn receive_event(&self) -> Result<Option<ProcessOutputEvent>, CompilerOutputError> {
let event =
crate::async_engine::cancellable(&self.cancellation, self.session.next_output())
.await
.map_err(|_| {
self.hub
.state
.lock()
.ok()
.and_then(|state| {
state
.operations
.get(&self.operation)
.and_then(|op| op.terminal)
})
.map_or(CompilerOutputError::Admission(HubError::Closed), |result| {
CompilerOutputError::Terminal(result.terminal)
})
})?;
{
let mut state = self.hub.state.lock().map_err(|_| HubError::Closed)?;
let operation = state
.operations
.get(&self.operation)
.ok_or(HubError::Closed)?;
if let Some(result) = operation.terminal {
return Err(CompilerOutputError::Terminal(result.terminal));
}
let slot = state
.resources
.get_mut(&self.process)
.ok_or(HubError::Closed)?;
let ResourceValue::CompilerProcess(value) = &mut slot.value else {
return Err(HubError::WrongKind.into());
};
if let Some(ProcessOutputEvent::Chunk(chunk)) = &event {
let bytes = match chunk {
crate::ProcessOutputChunk::Stdout(bytes)
| crate::ProcessOutputChunk::Stderr(bytes) => bytes,
};
if bytes.capacity() > MAX_PROCESS_OUTPUT_CHUNK {
return Err(HubError::Quota.into());
}
let total = value
.output_bytes
.checked_add(bytes.len())
.ok_or(HubError::Quota)?;
if total > value.output_limit {
return Err(HubError::Quota.into());
}
value.output_bytes = total;
}
}
Ok(event)
}
}
impl Drop for CompilerOutputRead {
fn drop(&mut self) {
if self.disposition == OutputLeaseDisposition::Transferred {
return;
}
let Ok(mut state) = self.hub.state.lock() else {
return;
};
let operation = if self.disposition == OutputLeaseDisposition::Native {
state.operations.remove(&self.operation)
} else {
if let Ok(Some(notify)) = OperationHub::terminal_locked(
&mut state,
self.operation,
TerminalResult {
terminal: Terminal::Closed,
resource: None,
},
) {
notify.notify_one();
}
None
};
if let Some(ResourceSlot {
value: ResourceValue::CompilerProcess(value),
..
}) = state.resources.get_mut(&self.process)
{
value.output_busy = false;
}
let notifications = if !self.completed && state.resources.contains_key(&self.process) {
OperationHub::close_resource_with_terminal_locked(
&mut state,
self.process,
Terminal::Closed,
)
.unwrap_or_default()
} else {
Vec::new()
};
state.reserved_process_output_bytes = state
.reserved_process_output_bytes
.saturating_sub(MAX_PROCESS_OUTPUT_CHUNK);
drop(state);
if let Some(operation) = operation {
operation.notify.notify_one();
}
for notify in notifications {
notify.notify_one();
}
let _ = self.hub.drive_blob_writes();
let _ = self.hub.drive_blob_reads();
}
}