runtime: remove legacy event polling authority

This commit is contained in:
2026-08-01 22:55:58 +09:00
parent 24f7267d55
commit 0f9f06048a
12 changed files with 72 additions and 615 deletions
-3
View File
@@ -261,7 +261,6 @@ pub struct WorkerSummary {
pub profile_source: ProfileSourceArchiveRef, pub profile_source: ProfileSourceArchiveRef,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub config_bundle: Option<ConfigBundleRef>, pub config_bundle: Option<ConfigBundleRef>,
pub last_event_id: u64,
} }
/// Full Worker catalog/lifecycle detail. /// Full Worker catalog/lifecycle detail.
@@ -280,7 +279,6 @@ pub struct WorkerDetail {
pub profile_source: ProfileSourceArchiveRef, pub profile_source: ProfileSourceArchiveRef,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub config_bundle: Option<ConfigBundleRef>, pub config_bundle: Option<ConfigBundleRef>,
pub last_event_id: u64,
} }
/// Acknowledgement returned by stop/cancel lifecycle operations. /// Acknowledgement returned by stop/cancel lifecycle operations.
@@ -288,5 +286,4 @@ pub struct WorkerDetail {
pub struct WorkerLifecycleAck { pub struct WorkerLifecycleAck {
pub worker_ref: WorkerRef, pub worker_ref: WorkerRef,
pub status: WorkerStatus, pub status: WorkerStatus,
pub event_id: u64,
} }
+15 -150
View File
@@ -3,18 +3,16 @@ use crate::config_bundle::ConfigBundle;
use crate::diagnostics::{DiagnosticSeverity, RuntimeDiagnostic}; use crate::diagnostics::{DiagnosticSeverity, RuntimeDiagnostic};
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::identity::{WorkerId, WorkerRef}; use crate::identity::{WorkerId, WorkerRef};
use crate::management::{RuntimeBackendKind, RuntimeLimits, RuntimeStatus}; use crate::management::{RuntimeBackendKind, RuntimeStatus};
use crate::observation::{EventCursor, RuntimeEvent, RuntimeEventBatch};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions}; use std::fs::{self, File, OpenOptions};
use std::io::{BufRead, BufReader, Write}; use std::io::{BufReader, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
const SCHEMA_VERSION: u32 = 1; const SCHEMA_VERSION: u32 = 1;
const RUNTIME_FILE: &str = "runtime.json"; const RUNTIME_FILE: &str = "runtime.json";
const EVENTS_FILE: &str = "events.jsonl";
const WORKERS_DIR: &str = "workers"; const WORKERS_DIR: &str = "workers";
const LEGACY_RUNTIMES_DIR: &str = "runtimes"; const LEGACY_RUNTIMES_DIR: &str = "runtimes";
const WORKER_FILE: &str = "worker.json"; const WORKER_FILE: &str = "worker.json";
@@ -28,7 +26,6 @@ pub struct FsRuntimeStoreOptions {
/// Root directory containing this Runtime's store data. /// Root directory containing this Runtime's store data.
pub root: PathBuf, pub root: PathBuf,
pub display_name: Option<String>, pub display_name: Option<String>,
pub limits: RuntimeLimits,
} }
impl FsRuntimeStoreOptions { impl FsRuntimeStoreOptions {
@@ -36,7 +33,6 @@ impl FsRuntimeStoreOptions {
Self { Self {
root: root.into(), root: root.into(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
} }
} }
} }
@@ -59,43 +55,6 @@ impl FsRuntimeStore {
&self.root &self.root
} }
/// Read persisted Runtime events directly from the event log with the same
/// bounded cursor semantics as [`crate::Runtime::read_events`].
pub fn read_events(
&self,
cursor: &EventCursor,
limit: usize,
max_limit: usize,
) -> Result<RuntimeEventBatch, RuntimeError> {
if limit > max_limit {
return Err(RuntimeError::LimitTooLarge {
requested: limit,
max: max_limit,
});
}
let events = read_json_lines::<RuntimeEvent>(&self.events_path(), "read events")?;
let mut selected = Vec::new();
for event in events
.iter()
.filter(|event| event.id >= cursor.next_event_id)
.take(limit)
{
selected.push(event.clone());
}
let next_event_id = selected
.last()
.map(|event| event.id + 1)
.unwrap_or(cursor.next_event_id);
let has_more = events.iter().any(|event| event.id >= next_event_id);
Ok(RuntimeEventBatch {
cursor: EventCursor { next_event_id },
events: selected,
has_more,
})
}
pub(crate) fn open_or_create(root: PathBuf) -> Result<OpenedFsRuntimeStore, RuntimeError> { pub(crate) fn open_or_create(root: PathBuf) -> Result<OpenedFsRuntimeStore, RuntimeError> {
let existed = root.exists(); let existed = root.exists();
if existed && !root.is_dir() { if existed && !root.is_dir() {
@@ -115,6 +74,18 @@ impl FsRuntimeStore {
path: root.join(WORKERS_DIR), path: root.join(WORKERS_DIR),
source, source,
})?; })?;
let legacy_events = root.join("events.jsonl");
match fs::remove_file(&legacy_events) {
Ok(()) => {}
Err(source) if source.kind() == std::io::ErrorKind::NotFound => {}
Err(source) => {
return Err(RuntimeError::StoreIo {
operation: "remove legacy runtime events",
path: legacy_events,
source,
});
}
}
let store = Self { root }; let store = Self { root };
let state = if existed { let state = if existed {
@@ -165,19 +136,11 @@ impl FsRuntimeStore {
}) })
} }
pub(crate) fn append_event(&self, event: &RuntimeEvent) -> Result<(), RuntimeError> {
if let Some(worker_ref) = &event.worker_ref {
self.ensure_worker_ref(worker_ref)?;
}
append_json_line(&self.events_path(), event, "append event")
}
pub(crate) fn load_runtime_state(&self) -> Result<PersistedRuntimeState, RuntimeError> { pub(crate) fn load_runtime_state(&self) -> Result<PersistedRuntimeState, RuntimeError> {
let runtime_path = self.runtime_path(); let runtime_path = self.runtime_path();
let mut snapshot: RuntimeSnapshot = read_json(&runtime_path, "read runtime snapshot")?; let mut snapshot: RuntimeSnapshot = read_json(&runtime_path, "read runtime snapshot")?;
snapshot.validate(&runtime_path)?; snapshot.validate(&runtime_path)?;
let events = read_json_lines::<RuntimeEvent>(&self.events_path(), "read events")?;
let workers_dir = self.root.join(WORKERS_DIR); let workers_dir = self.root.join(WORKERS_DIR);
if !workers_dir.exists() { if !workers_dir.exists() {
return Err(RuntimeError::StoreMissing { return Err(RuntimeError::StoreMissing {
@@ -250,7 +213,7 @@ impl FsRuntimeStore {
} }
} }
Ok(snapshot.into_persisted(events, workers)) Ok(snapshot.into_persisted(workers))
} }
fn ensure_worker_ref(&self, _worker_ref: &WorkerRef) -> Result<(), RuntimeError> { fn ensure_worker_ref(&self, _worker_ref: &WorkerRef) -> Result<(), RuntimeError> {
@@ -261,10 +224,6 @@ impl FsRuntimeStore {
self.root.join(RUNTIME_FILE) self.root.join(RUNTIME_FILE)
} }
fn events_path(&self) -> PathBuf {
self.root.join(EVENTS_FILE)
}
fn worker_dir(&self, worker_id: &WorkerId) -> PathBuf { fn worker_dir(&self, worker_id: &WorkerId) -> PathBuf {
self.root.join(WORKERS_DIR).join(worker_id.to_string()) self.root.join(WORKERS_DIR).join(worker_id.to_string())
} }
@@ -287,14 +246,11 @@ pub(crate) struct OpenedFsRuntimeStore {
pub(crate) struct PersistedRuntimeState { pub(crate) struct PersistedRuntimeState {
pub(crate) display_name: Option<String>, pub(crate) display_name: Option<String>,
pub(crate) status: RuntimeStatus, pub(crate) status: RuntimeStatus,
pub(crate) limits: RuntimeLimits,
pub(crate) next_worker_sequence: u64, pub(crate) next_worker_sequence: u64,
pub(crate) next_event_id: u64,
pub(crate) next_diagnostic_id: u64, pub(crate) next_diagnostic_id: u64,
pub(crate) workers: BTreeMap<WorkerId, PersistedWorkerRecord>, pub(crate) workers: BTreeMap<WorkerId, PersistedWorkerRecord>,
pub(crate) workspace_owners: BTreeMap<String, String>, pub(crate) workspace_owners: BTreeMap<String, String>,
pub(crate) config_bundles: BTreeMap<String, ConfigBundle>, pub(crate) config_bundles: BTreeMap<String, ConfigBundle>,
pub(crate) events: Vec<RuntimeEvent>,
pub(crate) diagnostics: Vec<RuntimeDiagnostic>, pub(crate) diagnostics: Vec<RuntimeDiagnostic>,
} }
@@ -305,7 +261,6 @@ pub(crate) struct PersistedWorkerRecord {
pub(crate) request: CreateWorkerRequest, pub(crate) request: CreateWorkerRequest,
pub(crate) workspace_id: Option<String>, pub(crate) workspace_id: Option<String>,
pub(crate) working_directory: Option<WorkingDirectoryStatus>, pub(crate) working_directory: Option<WorkingDirectoryStatus>,
pub(crate) last_event_id: u64,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@@ -314,9 +269,7 @@ struct RuntimeSnapshot {
display_name: Option<String>, display_name: Option<String>,
backend: RuntimeBackendKind, backend: RuntimeBackendKind,
status: RuntimeStatus, status: RuntimeStatus,
limits: RuntimeLimits,
next_worker_sequence: u64, next_worker_sequence: u64,
next_event_id: u64,
next_diagnostic_id: u64, next_diagnostic_id: u64,
#[serde(default)] #[serde(default)]
config_bundles: BTreeMap<String, ConfigBundle>, config_bundles: BTreeMap<String, ConfigBundle>,
@@ -348,9 +301,7 @@ impl RuntimeSnapshot {
display_name: state.display_name.clone(), display_name: state.display_name.clone(),
backend: RuntimeBackendKind::FsStore, backend: RuntimeBackendKind::FsStore,
status: state.status, status: state.status,
limits: state.limits.clone(),
next_worker_sequence: state.next_worker_sequence, next_worker_sequence: state.next_worker_sequence,
next_event_id: state.next_event_id,
next_diagnostic_id: state.next_diagnostic_id, next_diagnostic_id: state.next_diagnostic_id,
config_bundles: state.config_bundles.clone(), config_bundles: state.config_bundles.clone(),
workspace_owners: state.workspace_owners.clone(), workspace_owners: state.workspace_owners.clone(),
@@ -381,20 +332,16 @@ impl RuntimeSnapshot {
fn into_persisted( fn into_persisted(
self, self,
events: Vec<RuntimeEvent>,
workers: BTreeMap<WorkerId, PersistedWorkerRecord>, workers: BTreeMap<WorkerId, PersistedWorkerRecord>,
) -> PersistedRuntimeState { ) -> PersistedRuntimeState {
PersistedRuntimeState { PersistedRuntimeState {
display_name: self.display_name, display_name: self.display_name,
status: self.status, status: self.status,
limits: self.limits,
next_worker_sequence: self.next_worker_sequence, next_worker_sequence: self.next_worker_sequence,
next_event_id: self.next_event_id,
next_diagnostic_id: self.next_diagnostic_id, next_diagnostic_id: self.next_diagnostic_id,
workers, workers,
config_bundles: self.config_bundles, config_bundles: self.config_bundles,
workspace_owners: self.workspace_owners, workspace_owners: self.workspace_owners,
events,
diagnostics: self.diagnostics, diagnostics: self.diagnostics,
} }
} }
@@ -414,7 +361,6 @@ struct WorkerSnapshot {
/// write the removed execution projection. /// write the removed execution projection.
#[serde(default, rename = "execution", skip_serializing)] #[serde(default, rename = "execution", skip_serializing)]
legacy_execution: Option<LegacyWorkerExecutionProjection>, legacy_execution: Option<LegacyWorkerExecutionProjection>,
last_event_id: u64,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@@ -433,7 +379,6 @@ impl WorkerSnapshot {
workspace_id: worker.workspace_id.clone(), workspace_id: worker.workspace_id.clone(),
working_directory: worker.working_directory.clone(), working_directory: worker.working_directory.clone(),
legacy_execution: None, legacy_execution: None,
last_event_id: worker.last_event_id,
} }
} }
@@ -477,7 +422,6 @@ impl WorkerSnapshot {
self.legacy_execution self.legacy_execution
.and_then(|execution| execution.working_directory) .and_then(|execution| execution.working_directory)
}), }),
last_event_id: self.last_event_id,
} }
} }
} }
@@ -525,11 +469,6 @@ fn migrate_legacy_single_runtime_layout(root: &Path) -> Result<(), RuntimeError>
&root.join(RUNTIME_FILE), &root.join(RUNTIME_FILE),
"migrate legacy runtime snapshot", "migrate legacy runtime snapshot",
)?; )?;
rename_if_exists(
&legacy_dir.join(EVENTS_FILE),
&root.join(EVENTS_FILE),
"migrate legacy runtime events",
)?;
rename_if_exists( rename_if_exists(
&legacy_dir.join(WORKERS_DIR), &legacy_dir.join(WORKERS_DIR),
&root.join(WORKERS_DIR), &root.join(WORKERS_DIR),
@@ -581,42 +520,6 @@ where
}) })
} }
fn read_json_lines<T>(path: &Path, operation: &'static str) -> Result<Vec<T>, RuntimeError>
where
T: for<'de> Deserialize<'de>,
{
let file = File::open(path).map_err(|source| match source.kind() {
std::io::ErrorKind::NotFound => RuntimeError::StoreMissing {
operation,
path: path.to_path_buf(),
},
_ => RuntimeError::StoreIo {
operation,
path: path.to_path_buf(),
source,
},
})?;
let reader = BufReader::new(file);
let mut items = Vec::new();
for (index, line) in reader.lines().enumerate() {
let line = line.map_err(|source| RuntimeError::StoreIo {
operation,
path: path.to_path_buf(),
source,
})?;
if line.trim().is_empty() {
continue;
}
let item = serde_json::from_str(&line).map_err(|source| RuntimeError::StoreCorrupt {
operation,
path: path.to_path_buf(),
message: format!("line {}: {source}", index + 1),
})?;
items.push(item);
}
Ok(items)
}
fn atomic_write_json<T>(path: &Path, value: &T, operation: &'static str) -> Result<(), RuntimeError> fn atomic_write_json<T>(path: &Path, value: &T, operation: &'static str) -> Result<(), RuntimeError>
where where
T: Serialize, T: Serialize,
@@ -676,44 +579,6 @@ where
write_result write_result
} }
fn append_json_line<T>(path: &Path, value: &T, operation: &'static str) -> Result<(), RuntimeError>
where
T: Serialize,
{
let parent = path.parent().ok_or_else(|| RuntimeError::StoreCorrupt {
operation,
path: path.to_path_buf(),
message: "path has no parent directory".to_string(),
})?;
fs::create_dir_all(parent).map_err(|source| RuntimeError::StoreIo {
operation,
path: parent.to_path_buf(),
source,
})?;
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(path)
.map_err(|source| RuntimeError::StoreIo {
operation,
path: path.to_path_buf(),
source,
})?;
serde_json::to_writer(&mut file, value).map_err(|source| RuntimeError::StoreCorrupt {
operation,
path: path.to_path_buf(),
message: format!("serialize json: {source}"),
})?;
file.write_all(b"\n")
.and_then(|()| file.flush())
.and_then(|()| file.sync_all())
.map_err(|source| RuntimeError::StoreIo {
operation,
path: path.to_path_buf(),
source,
})
}
fn tmp_path_for(path: &Path) -> PathBuf { fn tmp_path_for(path: &Path) -> PathBuf {
let sequence = NEXT_TMP_SEQUENCE.fetch_add(1, Ordering::Relaxed); let sequence = NEXT_TMP_SEQUENCE.fetch_add(1, Ordering::Relaxed);
let file_name = path let file_name = path
+1 -5
View File
@@ -18,7 +18,7 @@ use crate::config_bundle::{ConfigBundle, ConfigBundleAvailability, ConfigBundleS
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::identity::{WorkerId, WorkerRef}; use crate::identity::{WorkerId, WorkerRef};
use crate::interaction::{WorkerInput, WorkerInteractionAck}; use crate::interaction::{WorkerInput, WorkerInteractionAck};
use crate::management::{RuntimeLimits, RuntimeSummary, WorkerDeleteResult}; use crate::management::{RuntimeSummary, WorkerDeleteResult};
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
use crate::observation::WorkerObservationCursor; use crate::observation::WorkerObservationCursor;
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
@@ -68,8 +68,6 @@ pub struct RuntimeHttpServerConfig {
pub bind_addr: SocketAddr, pub bind_addr: SocketAddr,
/// Optional display label surfaced by `GET /v1/runtime`. /// Optional display label surfaced by `GET /v1/runtime`.
pub display_name: Option<String>, pub display_name: Option<String>,
/// Bounded Runtime API limits.
pub limits: RuntimeLimits,
/// v0 store selection for the Runtime process. /// v0 store selection for the Runtime process.
pub store: RuntimeHttpStoreSelection, pub store: RuntimeHttpStoreSelection,
/// Minimal local bearer token placeholder for backend-to-Runtime calls. /// Minimal local bearer token placeholder for backend-to-Runtime calls.
@@ -84,7 +82,6 @@ impl Default for RuntimeHttpServerConfig {
Self { Self {
bind_addr: default_runtime_http_bind_addr(), bind_addr: default_runtime_http_bind_addr(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
store: RuntimeHttpStoreSelection::Memory, store: RuntimeHttpStoreSelection::Memory,
local_token: None, local_token: None,
auth: None, auth: None,
@@ -97,7 +94,6 @@ impl fmt::Debug for RuntimeHttpServerConfig {
f.debug_struct("RuntimeHttpServerConfig") f.debug_struct("RuntimeHttpServerConfig")
.field("bind_addr", &self.bind_addr) .field("bind_addr", &self.bind_addr)
.field("display_name", &self.display_name) .field("display_name", &self.display_name)
.field("limits", &self.limits)
.field("store", &self.store) .field("store", &self.store)
.field( .field(
"local_token", "local_token",
-1
View File
@@ -52,5 +52,4 @@ impl WorkerInput {
pub struct WorkerInteractionAck { pub struct WorkerInteractionAck {
pub worker_ref: WorkerRef, pub worker_ref: WorkerRef,
pub status: WorkerStatus, pub status: WorkerStatus,
pub event_id: u64,
} }
+1 -1
View File
@@ -28,5 +28,5 @@ pub mod working_directory;
#[cfg(feature = "fs-store")] #[cfg(feature = "fs-store")]
pub use fs_store::{FsRuntimeStore, FsRuntimeStoreOptions}; pub use fs_store::{FsRuntimeStore, FsRuntimeStoreOptions};
pub use management::{RuntimeLimits, RuntimeOptions}; pub use management::RuntimeOptions;
pub use runtime::{Runtime, RuntimeWorkspaceScope}; pub use runtime::{Runtime, RuntimeWorkspaceScope};
-13
View File
@@ -107,7 +107,6 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
RuntimeHttpStoreSelection::Fs { root } => { RuntimeHttpStoreSelection::Fs { root } => {
let mut options = FsRuntimeStoreOptions::new(root.clone()); let mut options = FsRuntimeStoreOptions::new(root.clone());
options.display_name = config.http.display_name.clone(); options.display_name = config.http.display_name.clone();
options.limits = config.http.limits.clone();
Runtime::with_fs_store_and_execution_backend(options, backend) Runtime::with_fs_store_and_execution_backend(options, backend)
.map_err(ProcessError::Runtime) .map_err(ProcessError::Runtime)
} }
@@ -120,7 +119,6 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
fn runtime_options_from_http(config: &RuntimeHttpServerConfig) -> RuntimeOptions { fn runtime_options_from_http(config: &RuntimeHttpServerConfig) -> RuntimeOptions {
RuntimeOptions { RuntimeOptions {
display_name: config.display_name.clone(), display_name: config.display_name.clone(),
limits: config.limits.clone(),
} }
} }
@@ -207,10 +205,6 @@ where
} }
config.http.local_token = Some(value); config.http.local_token = Some(value);
} }
"--max-event-batch-items" => {
config.http.limits.max_event_batch_items =
parse_usize_flag(&flag, take_value(&flag, inline_value, &mut args)?)?;
}
_ => { _ => {
return Err(ProcessError::usage(format!("unknown argument `{flag}`"))); return Err(ProcessError::usage(format!("unknown argument `{flag}`")));
} }
@@ -255,12 +249,6 @@ fn ensure_no_inline_value(flag: &str, inline_value: Option<&str>) -> Result<(),
Ok(()) Ok(())
} }
fn parse_usize_flag(flag: &str, value: String) -> Result<usize, ProcessError> {
value
.parse::<usize>()
.map_err(|error| ProcessError::usage(format!("invalid {flag} value `{value}`: {error}")))
}
fn apply_store_selection(config: &mut ProcessConfig) { fn apply_store_selection(config: &mut ProcessConfig) {
if config.no_store { if config.no_store {
config.http.store = RuntimeHttpStoreSelection::Memory; config.http.store = RuntimeHttpStoreSelection::Memory;
@@ -806,7 +794,6 @@ Options:
--no-store Disable Runtime catalog persistence for ephemeral runs --no-store Disable Runtime catalog persistence for ephemeral runs
--local-token <TOKEN> Minimal local bearer token placeholder --local-token <TOKEN> Minimal local bearer token placeholder
--local-token-env <ENV> Read local bearer token placeholder from env --local-token-env <ENV> Read local bearer token placeholder from env
--max-event-batch-items <N> Override event batch limit
-h, --help Show this help -h, --help Show this help
Auth commands: Auth commands:
+1 -26
View File
@@ -18,34 +18,10 @@ pub enum RuntimeStatus {
Stopped, Stopped,
} }
/// Guardrails for bounded Runtime APIs.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeLimits {
pub max_event_batch_items: usize,
}
impl Default for RuntimeLimits {
fn default() -> Self {
Self {
max_event_batch_items: 256,
}
}
}
/// Options used to construct an embedded memory Runtime. /// Options used to construct an embedded memory Runtime.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeOptions { pub struct RuntimeOptions {
pub display_name: Option<String>, pub display_name: Option<String>,
pub limits: RuntimeLimits,
}
impl Default for RuntimeOptions {
fn default() -> Self {
Self {
display_name: None,
limits: RuntimeLimits::default(),
}
}
} }
fn unknown_platform_component() -> String { fn unknown_platform_component() -> String {
@@ -69,7 +45,6 @@ pub struct RuntimeSummary {
pub stopped_worker_count: usize, pub stopped_worker_count: usize,
pub cancelled_worker_count: usize, pub cancelled_worker_count: usize,
pub diagnostic_count: usize, pub diagnostic_count: usize,
pub limits: RuntimeLimits,
#[serde(default = "unknown_platform_component")] #[serde(default = "unknown_platform_component")]
pub os: String, pub os: String,
#[serde(default = "unknown_platform_component")] #[serde(default = "unknown_platform_component")]
-50
View File
@@ -1,56 +1,6 @@
use crate::identity::WorkerRef; use crate::identity::WorkerRef;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Event cursor. `next_event_id` is the first event id that should be returned
/// by the next poll.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EventCursor {
pub next_event_id: u64,
}
/// Placeholder subscription handle for future streaming APIs. v0 is explicit
/// poll-only so HTTP/WS/SSE dependencies are not pulled into this crate.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EventSubscription {
pub cursor: EventCursor,
pub mode: EventSubscriptionMode,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventSubscriptionMode {
PollOnly,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeEvent {
pub id: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub worker_ref: Option<WorkerRef>,
pub kind: RuntimeEventKind,
pub message: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum RuntimeEventKind {
RuntimeStarted,
RuntimeStopped,
WorkerCreated,
WorkerExecutionRestored,
WorkerInputAccepted,
WorkerStopped,
WorkerCancelled,
WorkerDeleted,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeEventBatch {
pub cursor: EventCursor,
pub events: Vec<RuntimeEvent>,
pub has_more: bool,
}
/// Runtime-local cursor for worker-scoped WebSocket observation. /// Runtime-local cursor for worker-scoped WebSocket observation.
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+43 -338
View File
@@ -22,12 +22,7 @@ use crate::fs_store::{
use crate::identity::{WorkerId, WorkerRef}; use crate::identity::{WorkerId, WorkerRef};
use crate::interaction::{WorkerInput, WorkerInputKind, WorkerInteractionAck}; use crate::interaction::{WorkerInput, WorkerInputKind, WorkerInteractionAck};
use crate::management::{ use crate::management::{
RuntimeBackendKind, RuntimeLimits, RuntimeOptions, RuntimeStatus, RuntimeSummary, RuntimeBackendKind, RuntimeOptions, RuntimeStatus, RuntimeSummary, WorkerDeleteResult,
WorkerDeleteResult,
};
use crate::observation::{
EventCursor, EventSubscription, EventSubscriptionMode, RuntimeEvent, RuntimeEventBatch,
RuntimeEventKind,
}; };
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
use crate::observation::{WorkerObservationCursor, WorkerObservationEvent}; use crate::observation::{WorkerObservationCursor, WorkerObservationEvent};
@@ -62,7 +57,7 @@ impl RuntimeWorkspaceScope {
} }
} }
const RUNTIME_EVENT_SUBSCRIPTION_QUEUE_CAPACITY: usize = 256; const SUBSCRIPTION_QUEUE_CAPACITY: usize = 256;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RuntimeSubscriptionUpdate { pub struct RuntimeSubscriptionUpdate {
@@ -130,7 +125,7 @@ impl Drop for RuntimeEventSelectorSubscription {
return; return;
}; };
if let Ok(mut state) = runtime.lock() { if let Ok(mut state) = runtime.lock() {
state.event_subscriptions.remove(&self.subscription_id); state.subscriptions.remove(&self.subscription_id);
} }
} }
} }
@@ -148,15 +143,14 @@ pub struct Runtime {
} }
impl Runtime { impl Runtime {
/// Create a memory-backed Runtime with generated identity and default limits. /// Create a memory-backed Runtime with generated identity.
pub fn new_memory() -> Self { pub fn new_memory() -> Self {
Self::with_options(RuntimeOptions::default()) Self::with_options(RuntimeOptions::default())
} }
/// Create a memory-backed Runtime with explicit options. /// Create a memory-backed Runtime with explicit options.
pub fn with_options(options: RuntimeOptions) -> Self { pub fn with_options(options: RuntimeOptions) -> Self {
let mut state = RuntimeState::new(options.display_name, options.limits); let state = RuntimeState::new(options.display_name);
state.push_event(None, RuntimeEventKind::RuntimeStarted, "runtime started");
Self { Self {
inner: Arc::new(Mutex::new(state)), inner: Arc::new(Mutex::new(state)),
} }
@@ -200,12 +194,8 @@ impl Runtime {
let mut state = if let Some(persisted) = opened.state { let mut state = if let Some(persisted) = opened.state {
RuntimeState::from_persisted(persisted, opened.store)? RuntimeState::from_persisted(persisted, opened.store)?
} else { } else {
let mut state = let state = RuntimeState::new_fs_backed(options.display_name, opened.store);
RuntimeState::new_fs_backed(options.display_name, options.limits, opened.store);
let event_id =
state.push_event(None, RuntimeEventKind::RuntimeStarted, "runtime started");
state.persist_runtime_snapshot()?; state.persist_runtime_snapshot()?;
state.persist_event_by_id(event_id)?;
state state
}; };
state.execution_backend = execution_backend; state.execution_backend = execution_backend;
@@ -241,7 +231,6 @@ impl Runtime {
stopped_worker_count, stopped_worker_count,
cancelled_worker_count, cancelled_worker_count,
diagnostic_count: state.diagnostics.len(), diagnostic_count: state.diagnostics.len(),
limits: state.limits.clone(),
os: std::env::consts::OS.to_string(), os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(), arch: std::env::consts::ARCH.to_string(),
worker_creation_available: state.execution_backend.is_some(), worker_creation_available: state.execution_backend.is_some(),
@@ -294,22 +283,25 @@ impl Runtime {
/// Stop the Runtime. v0 keeps data readable after stop, but rejects new /// Stop the Runtime. v0 keeps data readable after stop, but rejects new
/// create/send/worker lifecycle mutations. /// create/send/worker lifecycle mutations.
pub fn stop_runtime(&self) -> Result<u64, RuntimeError> { pub fn stop_runtime(&self) -> Result<(), RuntimeError> {
let mut state = self.lock()?; let mut state = self.lock()?;
if state.status == RuntimeStatus::Stopped { if state.status == RuntimeStatus::Stopped {
return Ok(state.last_event_id()); return Ok(());
} }
state.status = RuntimeStatus::Stopped; state.status = RuntimeStatus::Stopped;
for worker in state.workers.values_mut() { let mut stopped = Vec::new();
for (worker_id, worker) in &mut state.workers {
if worker.status.is_active() { if worker.status.is_active() {
worker.status = WorkerStatus::Stopped; worker.status = WorkerStatus::Stopped;
stopped.push(*worker_id);
} }
} }
let event_id = state.push_event(None, RuntimeEventKind::RuntimeStopped, "runtime stopped"); for worker_id in stopped {
state.publish_worker_upsert(worker_id)?;
}
state.persist_runtime_snapshot()?; state.persist_runtime_snapshot()?;
state.persist_workers()?; state.persist_workers()?;
state.persist_event_by_id(event_id)?; Ok(())
Ok(event_id)
} }
/// Create a Runtime-owned working directory through the attached execution backend. /// Create a Runtime-owned working directory through the attached execution backend.
@@ -483,11 +475,6 @@ impl Runtime {
let worker_id = WorkerId::generated(state.next_worker_sequence); let worker_id = WorkerId::generated(state.next_worker_sequence);
state.next_worker_sequence += 1; state.next_worker_sequence += 1;
let worker_ref = WorkerRef::new(worker_id.clone()); let worker_ref = WorkerRef::new(worker_id.clone());
let event_id = state.push_event(
Some(worker_ref.clone()),
RuntimeEventKind::WorkerCreated,
format!("worker {worker_id} created"),
);
let record = WorkerRecord { let record = WorkerRecord {
worker_ref: worker_ref.clone(), worker_ref: worker_ref.clone(),
@@ -497,7 +484,6 @@ impl Runtime {
request: request.clone(), request: request.clone(),
working_directory: None, working_directory: None,
execution_handle: None, execution_handle: None,
last_event_id: event_id,
}; };
state.workers.insert(worker_id, record); state.workers.insert(worker_id, record);
let spawn_request = WorkerExecutionSpawnRequest { let spawn_request = WorkerExecutionSpawnRequest {
@@ -617,11 +603,11 @@ impl Runtime {
let snapshot_revision = state.subscription_revision; let snapshot_revision = state.subscription_revision;
let subscription_id = state.next_event_subscription_id; let subscription_id = state.next_event_subscription_id;
state.next_event_subscription_id = state.next_event_subscription_id.saturating_add(1); state.next_event_subscription_id = state.next_event_subscription_id.saturating_add(1);
let (sender, receiver) = mpsc::channel(RUNTIME_EVENT_SUBSCRIPTION_QUEUE_CAPACITY); let (sender, receiver) = mpsc::channel(SUBSCRIPTION_QUEUE_CAPACITY);
let lagged = Arc::new(AtomicBool::new(false)); let lagged = Arc::new(AtomicBool::new(false));
state.event_subscriptions.insert( state.subscriptions.insert(
subscription_id, subscription_id,
RuntimeEventSubscriptionSink { SubscriptionSink {
selector: selector.clone(), selector: selector.clone(),
workspace_id: scope.map(|scope| scope.workspace_id.clone()), workspace_id: scope.map(|scope| scope.workspace_id.clone()),
sender, sender,
@@ -977,13 +963,7 @@ impl Runtime {
let mut state = self.lock()?; let mut state = self.lock()?;
state.ensure_running()?; state.ensure_running()?;
let event_id = state.push_event(
Some(worker_ref.clone()),
RuntimeEventKind::WorkerInputAccepted,
"worker input accepted",
);
let worker = state.worker_mut(worker_ref)?; let worker = state.worker_mut(worker_ref)?;
worker.last_event_id = event_id;
worker.status = worker_status_from_run_state(dispatch_result.run_state); worker.status = worker_status_from_run_state(dispatch_result.run_state);
let status = worker.status; let status = worker.status;
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
@@ -994,12 +974,10 @@ impl Runtime {
state.publish_worker_upsert(worker_ref.worker_id)?; state.publish_worker_upsert(worker_ref.worker_id)?;
state.persist_runtime_snapshot()?; state.persist_runtime_snapshot()?;
state.persist_worker(&worker_ref.worker_id)?; state.persist_worker(&worker_ref.worker_id)?;
state.persist_event_by_id(event_id)?;
Ok(WorkerInteractionAck { Ok(WorkerInteractionAck {
worker_ref: worker_ref.clone(), worker_ref: worker_ref.clone(),
status, status,
event_id,
}) })
} }
@@ -1128,7 +1106,6 @@ impl Runtime {
state.publish_worker_upsert(worker_ref.worker_id)?; state.publish_worker_upsert(worker_ref.worker_id)?;
state.persist_runtime_snapshot()?; state.persist_runtime_snapshot()?;
state.persist_worker(&worker_ref.worker_id)?; state.persist_worker(&worker_ref.worker_id)?;
state.persist_event_by_id(detail.last_event_id)?;
Ok(detail) Ok(detail)
} }
@@ -1139,9 +1116,6 @@ impl Runtime {
if let Some(workspace_id) = workspace_id.as_deref() { if let Some(workspace_id) = workspace_id.as_deref() {
state.forget_workspace_owner_if_unused(workspace_id); state.forget_workspace_owner_if_unused(workspace_id);
} }
state.events.retain(|event| {
event.id != record.last_event_id || event.worker_ref.as_ref() != Some(worker_ref)
});
state.publish_worker_removed(worker_ref.worker_id, workspace_id.as_deref())?; state.publish_worker_removed(worker_ref.worker_id, workspace_id.as_deref())?;
} }
Ok(()) Ok(())
@@ -1215,19 +1189,15 @@ impl Runtime {
self.stop_worker(worker_ref, reason) self.stop_worker(worker_ref, reason)
} }
/// Stop a Worker. Repeated stops are idempotent and return the last event id. /// Stop a Worker. Repeated stops are idempotent.
pub fn stop_worker( pub fn stop_worker(
&self, &self,
worker_ref: &WorkerRef, worker_ref: &WorkerRef,
reason: Option<String>, reason: Option<String>,
) -> Result<WorkerLifecycleAck, RuntimeError> { ) -> Result<WorkerLifecycleAck, RuntimeError> {
self.dispatch_lifecycle_to_backend(worker_ref, WorkerExecutionOperation::Stop)?; self.dispatch_lifecycle_to_backend(worker_ref, WorkerExecutionOperation::Stop)?;
self.transition_worker( let _ = reason;
worker_ref, self.transition_worker(worker_ref, WorkerStatus::Stopped)
WorkerStatus::Stopped,
RuntimeEventKind::WorkerStopped,
reason.unwrap_or_else(|| "worker stopped".to_string()),
)
} }
/// Cancel a Worker through a workspace-scoped Runtime authorization context. /// Cancel a Worker through a workspace-scoped Runtime authorization context.
@@ -1241,19 +1211,15 @@ impl Runtime {
self.cancel_worker(worker_ref, reason) self.cancel_worker(worker_ref, reason)
} }
/// Cancel a Worker. Repeated cancels are idempotent and return the last event id. /// Cancel a Worker. Repeated cancels are idempotent.
pub fn cancel_worker( pub fn cancel_worker(
&self, &self,
worker_ref: &WorkerRef, worker_ref: &WorkerRef,
reason: Option<String>, reason: Option<String>,
) -> Result<WorkerLifecycleAck, RuntimeError> { ) -> Result<WorkerLifecycleAck, RuntimeError> {
self.dispatch_lifecycle_to_backend(worker_ref, WorkerExecutionOperation::Cancel)?; self.dispatch_lifecycle_to_backend(worker_ref, WorkerExecutionOperation::Cancel)?;
self.transition_worker( let _ = reason;
worker_ref, self.transition_worker(worker_ref, WorkerStatus::Cancelled)
WorkerStatus::Cancelled,
RuntimeEventKind::WorkerCancelled,
reason.unwrap_or_else(|| "worker cancelled".to_string()),
)
} }
/// Delete a non-running Worker through a workspace-scoped Runtime authorization context. /// Delete a non-running Worker through a workspace-scoped Runtime authorization context.
@@ -1294,77 +1260,15 @@ impl Runtime {
state state
.observation_events .observation_events
.retain(|event| event.worker_ref != *worker_ref); .retain(|event| event.worker_ref != *worker_ref);
let event_id = state.push_event(
Some(worker_ref.clone()),
RuntimeEventKind::WorkerDeleted,
"worker deleted",
);
state.publish_worker_removed(worker_ref.worker_id, removed_workspace_id.as_deref())?; state.publish_worker_removed(worker_ref.worker_id, removed_workspace_id.as_deref())?;
state.persist_runtime_snapshot()?; state.persist_runtime_snapshot()?;
state.delete_worker_snapshot(&worker_ref.worker_id)?; state.delete_worker_snapshot(&worker_ref.worker_id)?;
state.persist_event_by_id(event_id)?;
Ok(WorkerDeleteResult { Ok(WorkerDeleteResult {
worker_id: removed.worker_id, worker_id: removed.worker_id,
deleted: true, deleted: true,
}) })
} }
/// Cursor pointing to the beginning of Runtime events.
pub fn event_cursor_from_start(&self) -> Result<EventCursor, RuntimeError> {
Ok(EventCursor { next_event_id: 1 })
}
/// Cursor pointing after the current last event.
pub fn event_cursor_now(&self) -> Result<EventCursor, RuntimeError> {
let state = self.lock()?;
Ok(EventCursor {
next_event_id: state.last_event_id() + 1,
})
}
/// Poll Runtime events from a cursor.
pub fn read_events(
&self,
cursor: &EventCursor,
limit: usize,
) -> Result<RuntimeEventBatch, RuntimeError> {
let state = self.lock()?;
if limit > state.limits.max_event_batch_items {
return Err(RuntimeError::LimitTooLarge {
requested: limit,
max: state.limits.max_event_batch_items,
});
}
let mut events = Vec::new();
for event in state
.events
.iter()
.filter(|event| event.id >= cursor.next_event_id)
.take(limit)
{
events.push(event.clone());
}
let next_event_id = events
.last()
.map(|event| event.id + 1)
.unwrap_or(cursor.next_event_id);
let has_more = state.events.iter().any(|event| event.id >= next_event_id);
Ok(RuntimeEventBatch {
cursor: EventCursor { next_event_id },
events,
has_more,
})
}
/// Create a poll-only placeholder subscription boundary for future streaming.
pub fn subscribe_events(&self, cursor: EventCursor) -> Result<EventSubscription, RuntimeError> {
Ok(EventSubscription {
cursor,
mode: EventSubscriptionMode::PollOnly,
})
}
/// Cursor pointing after the current worker-scoped protocol observation event. /// Cursor pointing after the current worker-scoped protocol observation event.
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
pub fn worker_observation_cursor_now( pub fn worker_observation_cursor_now(
@@ -1492,8 +1396,6 @@ impl Runtime {
&self, &self,
worker_ref: &WorkerRef, worker_ref: &WorkerRef,
status: WorkerStatus, status: WorkerStatus,
event_kind: RuntimeEventKind,
reason: String,
) -> Result<WorkerLifecycleAck, RuntimeError> { ) -> Result<WorkerLifecycleAck, RuntimeError> {
let mut state = self.lock()?; let mut state = self.lock()?;
state.ensure_running()?; state.ensure_running()?;
@@ -1505,25 +1407,20 @@ impl Runtime {
return Ok(WorkerLifecycleAck { return Ok(WorkerLifecycleAck {
worker_ref: worker_ref.clone(), worker_ref: worker_ref.clone(),
status: worker.status, status: worker.status,
event_id: worker.last_event_id,
}); });
} }
} }
let event_id = state.push_event(Some(worker_ref.clone()), event_kind, reason);
let worker = state.worker_mut(worker_ref)?; let worker = state.worker_mut(worker_ref)?;
worker.status = status; worker.status = status;
worker.execution_handle = None; worker.execution_handle = None;
worker.last_event_id = event_id;
let status = worker.status; let status = worker.status;
state.publish_worker_upsert(worker_ref.worker_id)?; state.publish_worker_upsert(worker_ref.worker_id)?;
state.persist_runtime_snapshot()?; state.persist_runtime_snapshot()?;
state.persist_worker(&worker_ref.worker_id)?; state.persist_worker(&worker_ref.worker_id)?;
state.persist_event_by_id(event_id)?;
Ok(WorkerLifecycleAck { Ok(WorkerLifecycleAck {
worker_ref: worker_ref.clone(), worker_ref: worker_ref.clone(),
status, status,
event_id,
}) })
} }
@@ -1633,22 +1530,15 @@ impl Runtime {
) -> Result<(), RuntimeError> { ) -> Result<(), RuntimeError> {
let mut state = self.lock()?; let mut state = self.lock()?;
state.ensure_worker_ref(worker_ref)?; state.ensure_worker_ref(worker_ref)?;
let event_id = state.push_event(
Some(worker_ref.clone()),
RuntimeEventKind::WorkerExecutionRestored,
format!("worker {} execution restored", worker_ref.worker_id),
);
{ {
let worker = state.worker_mut(worker_ref)?; let worker = state.worker_mut(worker_ref)?;
worker.execution_handle = Some(handle); worker.execution_handle = Some(handle);
worker.status = worker_status_from_run_state(run_state); worker.status = worker_status_from_run_state(run_state);
worker.working_directory = working_directory; worker.working_directory = working_directory;
worker.last_event_id = event_id;
} }
state.publish_worker_upsert(worker_ref.worker_id)?; state.publish_worker_upsert(worker_ref.worker_id)?;
state.persist_runtime_snapshot()?; state.persist_runtime_snapshot()?;
state.persist_worker(&worker_ref.worker_id)?; state.persist_worker(&worker_ref.worker_id)?;
state.persist_event_by_id(event_id)?;
Ok(()) Ok(())
} }
@@ -1666,7 +1556,7 @@ enum RuntimePersistence {
} }
#[derive(Debug)] #[derive(Debug)]
struct RuntimeEventSubscriptionSink { struct SubscriptionSink {
selector: EventSubscriptionSelector, selector: EventSubscriptionSelector,
workspace_id: Option<String>, workspace_id: Option<String>,
sender: mpsc::Sender<RuntimeSubscriptionUpdate>, sender: mpsc::Sender<RuntimeSubscriptionUpdate>,
@@ -1680,21 +1570,18 @@ struct RuntimeState {
#[cfg_attr(not(feature = "fs-store"), allow(dead_code))] #[cfg_attr(not(feature = "fs-store"), allow(dead_code))]
persistence: RuntimePersistence, persistence: RuntimePersistence,
status: RuntimeStatus, status: RuntimeStatus,
limits: RuntimeLimits,
execution_backend: Option<WorkerExecutionBackendRef>, execution_backend: Option<WorkerExecutionBackendRef>,
next_worker_sequence: u64, next_worker_sequence: u64,
next_event_id: u64,
#[cfg(feature = "fs-store")] #[cfg(feature = "fs-store")]
next_diagnostic_id: u64, next_diagnostic_id: u64,
workers: BTreeMap<WorkerId, WorkerRecord>, workers: BTreeMap<WorkerId, WorkerRecord>,
workspace_owners: BTreeMap<String, String>, workspace_owners: BTreeMap<String, String>,
config_bundles: BTreeMap<String, ConfigBundle>, config_bundles: BTreeMap<String, ConfigBundle>,
events: Vec<RuntimeEvent>,
diagnostics: Vec<RuntimeDiagnostic>, diagnostics: Vec<RuntimeDiagnostic>,
subscription_revision: u64, subscription_revision: u64,
worker_subject_revisions: BTreeMap<WorkerId, u64>, worker_subject_revisions: BTreeMap<WorkerId, u64>,
next_event_subscription_id: u64, next_event_subscription_id: u64,
event_subscriptions: BTreeMap<u64, RuntimeEventSubscriptionSink>, subscriptions: BTreeMap<u64, SubscriptionSink>,
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
next_observation_sequence: u64, next_observation_sequence: u64,
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
@@ -1704,27 +1591,24 @@ struct RuntimeState {
} }
impl RuntimeState { impl RuntimeState {
fn new(display_name: Option<String>, limits: RuntimeLimits) -> Self { fn new(display_name: Option<String>) -> Self {
Self { Self {
display_name, display_name,
backend: RuntimeBackendKind::Memory, backend: RuntimeBackendKind::Memory,
persistence: RuntimePersistence::Memory, persistence: RuntimePersistence::Memory,
status: RuntimeStatus::Running, status: RuntimeStatus::Running,
limits,
execution_backend: None, execution_backend: None,
next_worker_sequence: 1, next_worker_sequence: 1,
next_event_id: 1,
#[cfg(feature = "fs-store")] #[cfg(feature = "fs-store")]
next_diagnostic_id: 1, next_diagnostic_id: 1,
workers: BTreeMap::new(), workers: BTreeMap::new(),
workspace_owners: BTreeMap::new(), workspace_owners: BTreeMap::new(),
config_bundles: BTreeMap::new(), config_bundles: BTreeMap::new(),
events: Vec::new(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
subscription_revision: 0, subscription_revision: 0,
worker_subject_revisions: BTreeMap::new(), worker_subject_revisions: BTreeMap::new(),
next_event_subscription_id: 1, next_event_subscription_id: 1,
event_subscriptions: BTreeMap::new(), subscriptions: BTreeMap::new(),
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
next_observation_sequence: 1, next_observation_sequence: 1,
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
@@ -1735,31 +1619,24 @@ impl RuntimeState {
} }
#[cfg(feature = "fs-store")] #[cfg(feature = "fs-store")]
fn new_fs_backed( fn new_fs_backed(display_name: Option<String>, store: FsRuntimeStore) -> Self {
display_name: Option<String>,
limits: RuntimeLimits,
store: FsRuntimeStore,
) -> Self {
Self { Self {
display_name, display_name,
backend: RuntimeBackendKind::FsStore, backend: RuntimeBackendKind::FsStore,
persistence: RuntimePersistence::Fs(store), persistence: RuntimePersistence::Fs(store),
status: RuntimeStatus::Running, status: RuntimeStatus::Running,
limits,
execution_backend: None, execution_backend: None,
next_worker_sequence: 1, next_worker_sequence: 1,
next_event_id: 1,
#[cfg(feature = "fs-store")] #[cfg(feature = "fs-store")]
next_diagnostic_id: 1, next_diagnostic_id: 1,
workers: BTreeMap::new(), workers: BTreeMap::new(),
workspace_owners: BTreeMap::new(), workspace_owners: BTreeMap::new(),
config_bundles: BTreeMap::new(), config_bundles: BTreeMap::new(),
events: Vec::new(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
subscription_revision: 0, subscription_revision: 0,
worker_subject_revisions: BTreeMap::new(), worker_subject_revisions: BTreeMap::new(),
next_event_subscription_id: 1, next_event_subscription_id: 1,
event_subscriptions: BTreeMap::new(), subscriptions: BTreeMap::new(),
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
next_observation_sequence: 1, next_observation_sequence: 1,
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
@@ -1788,7 +1665,6 @@ impl RuntimeState {
request: worker.request, request: worker.request,
working_directory: worker.working_directory, working_directory: worker.working_directory,
execution_handle: None, execution_handle: None,
last_event_id: worker.last_event_id,
}, },
); );
} }
@@ -1798,20 +1674,17 @@ impl RuntimeState {
backend: RuntimeBackendKind::FsStore, backend: RuntimeBackendKind::FsStore,
persistence: RuntimePersistence::Fs(store), persistence: RuntimePersistence::Fs(store),
status: persisted.status, status: persisted.status,
limits: persisted.limits,
execution_backend: None, execution_backend: None,
next_worker_sequence: persisted.next_worker_sequence, next_worker_sequence: persisted.next_worker_sequence,
next_event_id: persisted.next_event_id,
next_diagnostic_id, next_diagnostic_id,
workers, workers,
config_bundles: persisted.config_bundles, config_bundles: persisted.config_bundles,
workspace_owners: persisted.workspace_owners, workspace_owners: persisted.workspace_owners,
events: persisted.events,
diagnostics, diagnostics,
subscription_revision: 0, subscription_revision: 0,
worker_subject_revisions: BTreeMap::new(), worker_subject_revisions: BTreeMap::new(),
next_event_subscription_id: 1, next_event_subscription_id: 1,
event_subscriptions: BTreeMap::new(), subscriptions: BTreeMap::new(),
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
next_observation_sequence: 1, next_observation_sequence: 1,
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
@@ -1826,9 +1699,7 @@ impl RuntimeState {
PersistedRuntimeState { PersistedRuntimeState {
display_name: self.display_name.clone(), display_name: self.display_name.clone(),
status: self.status, status: self.status,
limits: self.limits.clone(),
next_worker_sequence: self.next_worker_sequence, next_worker_sequence: self.next_worker_sequence,
next_event_id: self.next_event_id,
next_diagnostic_id: self.next_diagnostic_id, next_diagnostic_id: self.next_diagnostic_id,
workers: self workers: self
.workers .workers
@@ -1837,7 +1708,6 @@ impl RuntimeState {
.collect(), .collect(),
config_bundles: self.config_bundles.clone(), config_bundles: self.config_bundles.clone(),
workspace_owners: self.workspace_owners.clone(), workspace_owners: self.workspace_owners.clone(),
events: self.events.clone(),
diagnostics: self.diagnostics.clone(), diagnostics: self.diagnostics.clone(),
} }
} }
@@ -1880,23 +1750,6 @@ impl RuntimeState {
Ok(()) Ok(())
} }
#[cfg(feature = "fs-store")]
fn persist_event_by_id(&self, event_id: u64) -> Result<(), RuntimeError> {
if let Some(store) = self.fs_store() {
let event = self
.events
.iter()
.find(|event| event.id == event_id)
.ok_or_else(|| RuntimeError::StoreCorrupt {
operation: "persist event",
path: store.runtime_dir().to_path_buf(),
message: format!("event {event_id} is missing from runtime state"),
})?;
store.append_event(event)?;
}
Ok(())
}
#[cfg(feature = "fs-store")] #[cfg(feature = "fs-store")]
fn persist_workers(&self) -> Result<(), RuntimeError> { fn persist_workers(&self) -> Result<(), RuntimeError> {
if self.fs_store().is_some() { if self.fs_store().is_some() {
@@ -1922,11 +1775,6 @@ impl RuntimeState {
Ok(()) Ok(())
} }
#[cfg(not(feature = "fs-store"))]
fn persist_event_by_id(&self, _event_id: u64) -> Result<(), RuntimeError> {
Ok(())
}
#[cfg(not(feature = "fs-store"))] #[cfg(not(feature = "fs-store"))]
fn persist_workers(&self) -> Result<(), RuntimeError> { fn persist_workers(&self) -> Result<(), RuntimeError> {
Ok(()) Ok(())
@@ -2191,7 +2039,7 @@ impl RuntimeState {
update: RuntimeSubscriptionUpdate, update: RuntimeSubscriptionUpdate,
) { ) {
let mut closed = Vec::new(); let mut closed = Vec::new();
for (subscription_id, sink) in &self.event_subscriptions { for (subscription_id, sink) in &self.subscriptions {
if sink if sink
.workspace_id .workspace_id
.as_deref() .as_deref()
@@ -2221,7 +2069,7 @@ impl RuntimeState {
} }
} }
for subscription_id in closed { for subscription_id in closed {
self.event_subscriptions.remove(&subscription_id); self.subscriptions.remove(&subscription_id);
} }
} }
@@ -2270,27 +2118,6 @@ impl RuntimeState {
Ok(()) Ok(())
} }
fn push_event(
&mut self,
worker_ref: Option<WorkerRef>,
kind: RuntimeEventKind,
message: impl Into<String>,
) -> u64 {
let id = self.next_event_id;
self.next_event_id += 1;
self.events.push(RuntimeEvent {
id,
worker_ref,
kind,
message: message.into(),
});
id
}
fn last_event_id(&self) -> u64 {
self.next_event_id.saturating_sub(1)
}
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
fn validate_worker_observation_cursor( fn validate_worker_observation_cursor(
&self, &self,
@@ -2391,7 +2218,6 @@ struct WorkerRecord {
request: CreateWorkerRequest, request: CreateWorkerRequest,
working_directory: Option<CatalogWorkingDirectoryStatus>, working_directory: Option<CatalogWorkingDirectoryStatus>,
execution_handle: Option<WorkerExecutionHandle>, execution_handle: Option<WorkerExecutionHandle>,
last_event_id: u64,
} }
impl WorkerRecord { impl WorkerRecord {
@@ -2410,7 +2236,6 @@ impl WorkerRecord {
display_name: self.request.display_name.clone(), display_name: self.request.display_name.clone(),
profile_source: self.request.profile_source.reference(), profile_source: self.request.profile_source.reference(),
config_bundle: self.request.config_bundle.clone(), config_bundle: self.request.config_bundle.clone(),
last_event_id: self.last_event_id,
} }
} }
@@ -2425,7 +2250,6 @@ impl WorkerRecord {
display_name: self.request.display_name.clone(), display_name: self.request.display_name.clone(),
profile_source: self.request.profile_source.reference(), profile_source: self.request.profile_source.reference(),
config_bundle: self.request.config_bundle.clone(), config_bundle: self.request.config_bundle.clone(),
last_event_id: self.last_event_id,
} }
} }
@@ -2437,7 +2261,6 @@ impl WorkerRecord {
request: self.request.clone(), request: self.request.clone(),
workspace_id: self.workspace_id.clone(), workspace_id: self.workspace_id.clone(),
working_directory: self.working_directory.clone(), working_directory: self.working_directory.clone(),
last_event_id: self.last_event_id,
} }
} }
} }
@@ -2882,7 +2705,7 @@ mod tests {
payload => panic!("unexpected subscription payload: {payload:?}"), payload => panic!("unexpected subscription payload: {payload:?}"),
} }
runtime.stop_worker(&created.worker_ref, None).unwrap(); runtime.stop_runtime().unwrap();
let update = receive_subscription_update(&mut subscription).unwrap(); let update = receive_subscription_update(&mut subscription).unwrap();
assert_eq!(update.subject_revision, 2); assert_eq!(update.subject_revision, 2);
match update.payload { match update.payload {
@@ -2977,14 +2800,11 @@ mod tests {
.unwrap(); .unwrap();
{ {
let mut state = runtime.lock().unwrap(); let mut state = runtime.lock().unwrap();
for _ in 0..=RUNTIME_EVENT_SUBSCRIPTION_QUEUE_CAPACITY { for _ in 0..=SUBSCRIPTION_QUEUE_CAPACITY {
state.publish_worker_upsert(created.worker_id).unwrap(); state.publish_worker_upsert(created.worker_id).unwrap();
} }
} }
assert_eq!( assert_eq!(subscription.receiver.len(), SUBSCRIPTION_QUEUE_CAPACITY);
subscription.receiver.len(),
RUNTIME_EVENT_SUBSCRIPTION_QUEUE_CAPACITY
);
assert!(matches!( assert!(matches!(
receive_subscription_update(&mut subscription), receive_subscription_update(&mut subscription),
Err(RuntimeSubscriptionRecvError::Lagged) Err(RuntimeSubscriptionRecvError::Lagged)
@@ -2997,9 +2817,9 @@ mod tests {
let subscription = runtime let subscription = runtime
.subscribe_event_selector(EventSubscriptionSelector::RuntimeWorkers) .subscribe_event_selector(EventSubscriptionSelector::RuntimeWorkers)
.unwrap(); .unwrap();
assert_eq!(runtime.lock().unwrap().event_subscriptions.len(), 1); assert_eq!(runtime.lock().unwrap().subscriptions.len(), 1);
drop(subscription); drop(subscription);
assert!(runtime.lock().unwrap().event_subscriptions.is_empty()); assert!(runtime.lock().unwrap().subscriptions.is_empty());
} }
#[test] #[test]
@@ -3347,15 +3167,6 @@ mod tests {
RuntimeError::InvalidInitialInputKind { .. } RuntimeError::InvalidInitialInputKind { .. }
)); ));
assert!(runtime.list_workers().unwrap().is_empty()); assert!(runtime.list_workers().unwrap().is_empty());
let events = runtime
.read_events(&runtime.event_cursor_from_start().unwrap(), 16)
.unwrap();
assert!(
events
.events
.iter()
.all(|event| event.kind != RuntimeEventKind::WorkerCreated)
);
} }
#[test] #[test]
@@ -3550,14 +3361,6 @@ mod tests {
runtime.worker_detail(&detail.worker_ref).unwrap().status, runtime.worker_detail(&detail.worker_ref).unwrap().status,
WorkerStatus::Stopped WorkerStatus::Stopped
); );
let events = runtime
.read_events(&runtime.event_cursor_from_start().unwrap(), 16)
.unwrap()
.events;
assert!(events.iter().any(|event| {
event.kind == RuntimeEventKind::WorkerStopped
&& event.worker_ref.as_ref() == Some(&detail.worker_ref)
}));
} }
#[test] #[test]
@@ -3586,9 +3389,6 @@ mod tests {
fn send_input_records_protocol_observations() { fn send_input_records_protocol_observations() {
let runtime = Runtime::with_execution_backend( let runtime = Runtime::with_execution_backend(
RuntimeOptions { RuntimeOptions {
limits: RuntimeLimits {
max_event_batch_items: 16,
},
..RuntimeOptions::default() ..RuntimeOptions::default()
}, },
Arc::new(TestExecutionBackend::default()), Arc::new(TestExecutionBackend::default()),
@@ -3597,7 +3397,7 @@ mod tests {
runtime.store_config_bundle(test_bundle()).unwrap(); runtime.store_config_bundle(test_bundle()).unwrap();
let detail = runtime.create_worker(task_request("chat")).unwrap(); let detail = runtime.create_worker(task_request("chat")).unwrap();
let first = runtime runtime
.send_input(&detail.worker_ref, WorkerInput::user("hello")) .send_input(&detail.worker_ref, WorkerInput::user("hello"))
.unwrap(); .unwrap();
runtime runtime
@@ -3607,7 +3407,6 @@ mod tests {
let observations = runtime let observations = runtime
.read_worker_observation_events(&detail.worker_ref, WorkerObservationCursor::zero()) .read_worker_observation_events(&detail.worker_ref, WorkerObservationCursor::zero())
.unwrap(); .unwrap();
assert_eq!(first.event_id, 3);
assert_eq!(observations.len(), 2); assert_eq!(observations.len(), 2);
assert!(matches!( assert!(matches!(
observations[0].payload, observations[0].payload,
@@ -3665,7 +3464,6 @@ mod tests {
#[test] #[test]
fn stop_then_cancel_preserves_stopped_terminal_state() { fn stop_then_cancel_preserves_stopped_terminal_state() {
let runtime = runtime_with_backend(); let runtime = runtime_with_backend();
let cursor = runtime.event_cursor_from_start().unwrap();
let worker = runtime let worker = runtime
.create_worker(task_request("stable stopped")) .create_worker(task_request("stable stopped"))
.unwrap(); .unwrap();
@@ -3679,7 +3477,6 @@ mod tests {
assert_eq!(stop_ack.status, WorkerStatus::Stopped); assert_eq!(stop_ack.status, WorkerStatus::Stopped);
assert_eq!(cancel_ack.status, WorkerStatus::Stopped); assert_eq!(cancel_ack.status, WorkerStatus::Stopped);
assert_eq!(cancel_ack.event_id, stop_ack.event_id);
assert_eq!( assert_eq!(
runtime.worker_detail(&worker.worker_ref).unwrap().status, runtime.worker_detail(&worker.worker_ref).unwrap().status,
WorkerStatus::Stopped WorkerStatus::Stopped
@@ -3689,28 +3486,11 @@ mod tests {
assert_eq!(summary.active_worker_count, 0); assert_eq!(summary.active_worker_count, 0);
assert_eq!(summary.stopped_worker_count, 1); assert_eq!(summary.stopped_worker_count, 1);
assert_eq!(summary.cancelled_worker_count, 0); assert_eq!(summary.cancelled_worker_count, 0);
let events = runtime.read_events(&cursor, 10).unwrap().events;
assert_eq!(
events
.iter()
.filter(|event| event.kind == RuntimeEventKind::WorkerStopped)
.count(),
1
);
assert_eq!(
events
.iter()
.filter(|event| event.kind == RuntimeEventKind::WorkerCancelled)
.count(),
0
);
} }
#[test] #[test]
fn cancel_then_stop_preserves_cancelled_terminal_state() { fn cancel_then_stop_preserves_cancelled_terminal_state() {
let runtime = runtime_with_backend(); let runtime = runtime_with_backend();
let cursor = runtime.event_cursor_from_start().unwrap();
let worker = runtime let worker = runtime
.create_worker(task_request("stable cancelled")) .create_worker(task_request("stable cancelled"))
.unwrap(); .unwrap();
@@ -3724,7 +3504,6 @@ mod tests {
assert_eq!(cancel_ack.status, WorkerStatus::Cancelled); assert_eq!(cancel_ack.status, WorkerStatus::Cancelled);
assert_eq!(stop_ack.status, WorkerStatus::Cancelled); assert_eq!(stop_ack.status, WorkerStatus::Cancelled);
assert_eq!(stop_ack.event_id, cancel_ack.event_id);
assert_eq!( assert_eq!(
runtime.worker_detail(&worker.worker_ref).unwrap().status, runtime.worker_detail(&worker.worker_ref).unwrap().status,
WorkerStatus::Cancelled WorkerStatus::Cancelled
@@ -3734,46 +3513,6 @@ mod tests {
assert_eq!(summary.active_worker_count, 0); assert_eq!(summary.active_worker_count, 0);
assert_eq!(summary.stopped_worker_count, 0); assert_eq!(summary.stopped_worker_count, 0);
assert_eq!(summary.cancelled_worker_count, 1); assert_eq!(summary.cancelled_worker_count, 1);
let events = runtime.read_events(&cursor, 10).unwrap().events;
assert_eq!(
events
.iter()
.filter(|event| event.kind == RuntimeEventKind::WorkerCancelled)
.count(),
1
);
assert_eq!(
events
.iter()
.filter(|event| event.kind == RuntimeEventKind::WorkerStopped)
.count(),
0
);
}
#[test]
fn event_cursor_and_poll_only_subscription_are_bounded_placeholders() {
let runtime = runtime_with_backend();
let cursor = runtime.event_cursor_from_start().unwrap();
let subscription = runtime.subscribe_events(cursor.clone()).unwrap();
assert_eq!(subscription.mode, EventSubscriptionMode::PollOnly);
let worker = runtime.create_worker(task_request("events")).unwrap();
runtime
.send_input(&worker.worker_ref, WorkerInput::user("eventful"))
.unwrap();
let batch = runtime.read_events(&cursor, 2).unwrap();
assert_eq!(batch.events.len(), 2);
assert!(batch.has_more);
assert_eq!(batch.events[0].kind, RuntimeEventKind::RuntimeStarted);
assert_eq!(batch.events[1].kind, RuntimeEventKind::WorkerCreated);
let next = runtime.read_events(&batch.cursor, 2).unwrap();
assert_eq!(next.events.len(), 1);
assert_eq!(next.events[0].kind, RuntimeEventKind::WorkerInputAccepted);
assert!(!next.has_more);
} }
#[cfg(feature = "fs-store")] #[cfg(feature = "fs-store")]
@@ -3801,15 +3540,12 @@ mod tests {
#[cfg(feature = "fs-store")] #[cfg(feature = "fs-store")]
#[test] #[test]
fn fs_store_restores_workers_and_events_but_not_protocol_observations() { fn fs_store_restores_workers_without_legacy_event_or_protocol_observation_logs() {
let root = fs_store_root("restore"); let root = fs_store_root("restore");
let runtime = Runtime::with_fs_store_and_execution_backend( let runtime = Runtime::with_fs_store_and_execution_backend(
crate::fs_store::FsRuntimeStoreOptions { crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: Some("filesystem runtime".to_string()), display_name: Some("filesystem runtime".to_string()),
limits: RuntimeLimits {
max_event_batch_items: 2,
},
}, },
Arc::new(TestExecutionBackend::default()), Arc::new(TestExecutionBackend::default()),
) )
@@ -3836,22 +3572,23 @@ mod tests {
.unwrap(); .unwrap();
assert!(worker_snapshot.get("status").is_none()); assert!(worker_snapshot.get("status").is_none());
assert!(worker_snapshot.get("execution").is_none()); assert!(worker_snapshot.get("execution").is_none());
assert!(!root.join("events.jsonl").exists());
std::fs::write( std::fs::write(
worker_store_dir.join("observations.jsonl"), worker_store_dir.join("observations.jsonl"),
b"{\"legacy\":true}\n", b"{\"legacy\":true}\n",
) )
.unwrap(); .unwrap();
let store = runtime_store(&runtime); std::fs::write(root.join("events.jsonl"), b"obsolete runtime event\n").unwrap();
drop(runtime); drop(runtime);
let restored = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions { let restored = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}) })
.unwrap(); .unwrap();
let restored_worker = restored.worker_detail(&worker.worker_ref).unwrap(); let restored_worker = restored.worker_detail(&worker.worker_ref).unwrap();
assert_eq!(restored_worker.status, WorkerStatus::Stopped); assert_eq!(restored_worker.status, WorkerStatus::Stopped);
assert!(!root.join("events.jsonl").exists());
assert!(!worker_store_dir.join("observations.jsonl").exists()); assert!(!worker_store_dir.join("observations.jsonl").exists());
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
{ {
@@ -3861,15 +3598,6 @@ mod tests {
assert!(observations.is_empty()); assert!(observations.is_empty());
} }
let cursor = restored.event_cursor_from_start().unwrap();
let batch = restored.read_events(&cursor, 2).unwrap();
assert_eq!(batch.events.len(), 2);
assert!(batch.has_more);
assert_eq!(batch.events[0].kind, RuntimeEventKind::RuntimeStarted);
assert_eq!(batch.events[1].kind, RuntimeEventKind::WorkerCreated);
let direct_events = store.read_events(&cursor, 2, 2).unwrap();
assert_eq!(direct_events.events, batch.events);
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
{ {
let observation = restored let observation = restored
@@ -3899,7 +3627,6 @@ mod tests {
crate::fs_store::FsRuntimeStoreOptions { crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}, },
Arc::new(TestExecutionBackend::default()), Arc::new(TestExecutionBackend::default()),
) )
@@ -3925,7 +3652,6 @@ mod tests {
let restored = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions { let restored = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}) })
.unwrap(); .unwrap();
let restored_scoped = restored.worker_detail(&scoped.worker_ref).unwrap(); let restored_scoped = restored.worker_detail(&scoped.worker_ref).unwrap();
@@ -3975,7 +3701,6 @@ mod tests {
crate::fs_store::FsRuntimeStoreOptions { crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}, },
Arc::new(TestExecutionBackend::default()), Arc::new(TestExecutionBackend::default()),
) )
@@ -3989,7 +3714,6 @@ mod tests {
let backendless = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions { let backendless = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}) })
.unwrap(); .unwrap();
let stopped_worker = backendless.worker_detail(&worker.worker_ref).unwrap(); let stopped_worker = backendless.worker_detail(&worker.worker_ref).unwrap();
@@ -4001,7 +3725,6 @@ mod tests {
crate::fs_store::FsRuntimeStoreOptions { crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}, },
restoring_backend.clone(), restoring_backend.clone(),
) )
@@ -4013,18 +3736,6 @@ mod tests {
restored restored
.send_input(&worker.worker_ref, WorkerInput::user("after restart")) .send_input(&worker.worker_ref, WorkerInput::user("after restart"))
.unwrap(); .unwrap();
let cursor = restored.event_cursor_from_start().unwrap();
assert!(
restored
.read_events(&cursor, 16)
.unwrap()
.events
.iter()
.any(
|event| event.kind == RuntimeEventKind::WorkerExecutionRestored
&& event.worker_ref.as_ref() == Some(&worker.worker_ref)
)
);
let _ = std::fs::remove_dir_all(root); let _ = std::fs::remove_dir_all(root);
} }
@@ -4037,7 +3748,6 @@ mod tests {
crate::fs_store::FsRuntimeStoreOptions { crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}, },
Arc::new(TestExecutionBackend::default()), Arc::new(TestExecutionBackend::default()),
) )
@@ -4057,7 +3767,6 @@ mod tests {
crate::fs_store::FsRuntimeStoreOptions { crate::fs_store::FsRuntimeStoreOptions {
root: root.clone(), root: root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}, },
restoring_backend.clone(), restoring_backend.clone(),
) )
@@ -4094,7 +3803,6 @@ mod tests {
let corrupt_runtime = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions { let corrupt_runtime = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions {
root: corrupt_root.clone(), root: corrupt_root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}) })
.unwrap(); .unwrap();
let corrupt_store = runtime_store(&corrupt_runtime); let corrupt_store = runtime_store(&corrupt_runtime);
@@ -4107,7 +3815,6 @@ mod tests {
let err = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions { let err = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions {
root: corrupt_root.clone(), root: corrupt_root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}) })
.unwrap_err(); .unwrap_err();
assert!(matches!(err, RuntimeError::StoreCorrupt { .. })); assert!(matches!(err, RuntimeError::StoreCorrupt { .. }));
@@ -4118,7 +3825,6 @@ mod tests {
crate::fs_store::FsRuntimeStoreOptions { crate::fs_store::FsRuntimeStoreOptions {
root: missing_root.clone(), root: missing_root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}, },
Arc::new(TestExecutionBackend::default()), Arc::new(TestExecutionBackend::default()),
) )
@@ -4138,7 +3844,6 @@ mod tests {
let loaded = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions { let loaded = Runtime::with_fs_store(crate::fs_store::FsRuntimeStoreOptions {
root: missing_root.clone(), root: missing_root.clone(),
display_name: None, display_name: None,
limits: RuntimeLimits::default(),
}) })
.expect("invalid worker snapshot should not make runtime store unreadable"); .expect("invalid worker snapshot should not make runtime store unreadable");
assert!(loaded.list_workers().unwrap().is_empty()); assert!(loaded.list_workers().unwrap().is_empty());
+6 -26
View File
@@ -494,8 +494,6 @@ pub struct WorkerLifecycleResult {
pub state: WorkerOperationState, pub state: WorkerOperationState,
pub runtime_id: String, pub runtime_id: String,
pub worker_id: String, pub worker_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_id: Option<u64>,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
} }
@@ -549,8 +547,6 @@ pub struct WorkerInputResult {
pub state: WorkerOperationState, pub state: WorkerOperationState,
pub runtime_id: String, pub runtime_id: String,
pub worker_id: String, pub worker_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_id: Option<u64>,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
} }
@@ -795,7 +791,6 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
state: WorkerOperationState::Unsupported, state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(), runtime_id: self.runtime_id().to_string(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: None,
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"worker_stop_pending", "worker_stop_pending",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -815,7 +810,6 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
state: WorkerOperationState::Unsupported, state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(), runtime_id: self.runtime_id().to_string(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: None,
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"worker_cancel_pending", "worker_cancel_pending",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -852,7 +846,6 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
state: WorkerOperationState::Unsupported, state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(), runtime_id: self.runtime_id().to_string(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: None,
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"worker_input_pending", "worker_input_pending",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -1413,7 +1406,6 @@ impl EmbeddedWorkerRuntime {
FsRuntimeStoreOptions { FsRuntimeStoreOptions {
root: store_root.into(), root: store_root.into(),
display_name: Some("embedded".to_string()), display_name: Some("embedded".to_string()),
limits: EmbeddedRuntimeOptions::default().limits,
}, },
backend, backend,
)?; )?;
@@ -1944,11 +1936,10 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
); );
}; };
match self.runtime.stop_worker(&worker_ref, request.reason) { match self.runtime.stop_worker(&worker_ref, request.reason) {
Ok(ack) => WorkerLifecycleResult { Ok(_) => WorkerLifecycleResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), runtime_id: self.runtime_id.clone(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: Some(ack.event_id),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(error) => embedded_lifecycle_rejected( Err(error) => embedded_lifecycle_rejected(
@@ -1989,11 +1980,10 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
); );
}; };
match self.runtime.cancel_worker(&worker_ref, request.reason) { match self.runtime.cancel_worker(&worker_ref, request.reason) {
Ok(ack) => WorkerLifecycleResult { Ok(_) => WorkerLifecycleResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), runtime_id: self.runtime_id.clone(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: Some(ack.event_id),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(error) => embedded_lifecycle_rejected( Err(error) => embedded_lifecycle_rejected(
@@ -2120,11 +2110,10 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
segments: request.segments, segments: request.segments,
}; };
match self.runtime.send_input(&worker_ref, input) { match self.runtime.send_input(&worker_ref, input) {
Ok(ack) => WorkerInputResult { Ok(_) => WorkerInputResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), runtime_id: self.runtime_id.clone(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: Some(ack.event_id),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(error) => embedded_input_rejected( Err(error) => embedded_input_rejected(
@@ -2545,7 +2534,6 @@ impl RemoteWorkerRuntime {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), runtime_id: self.runtime_id.clone(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: Some(response.ack.event_id),
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"remote_runtime_lifecycle_accepted", "remote_runtime_lifecycle_accepted",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -2991,11 +2979,10 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
&format!("/v1/workers/{worker_id}/input"), &format!("/v1/workers/{worker_id}/input"),
&input, &input,
) { ) {
Ok(response) => WorkerInputResult { Ok(_) => WorkerInputResult {
state: WorkerOperationState::Accepted, state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(), runtime_id: self.runtime_id.clone(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: Some(response.ack.event_id),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(diagnostic) => remote_input_rejected(&self.runtime_id, worker_id, diagnostic), Err(diagnostic) => remote_input_rejected(&self.runtime_id, worker_id, diagnostic),
@@ -3390,7 +3377,6 @@ fn embedded_input_rejected(
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(), runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: None,
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
} }
} }
@@ -3404,7 +3390,6 @@ fn remote_input_rejected(
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(), runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: None,
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
} }
} }
@@ -3418,7 +3403,6 @@ fn embedded_lifecycle_rejected(
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(), runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: None,
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
} }
} }
@@ -3432,7 +3416,6 @@ fn remote_lifecycle_rejected(
state: WorkerOperationState::Rejected, state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(), runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(), worker_id: worker_id.to_string(),
event_id: None,
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
} }
} }
@@ -4689,8 +4672,7 @@ mod tests {
json!({ json!({
"ack": { "ack": {
"worker_ref": { "runtime_id": "remote:primary", "worker_id": 1 }, "worker_ref": { "runtime_id": "remote:primary", "worker_id": 1 },
"status": "running", "status": "running"
"event_id": 8
} }
}) })
.to_string(), .to_string(),
@@ -4749,7 +4731,6 @@ mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(input.state, WorkerOperationState::Accepted); assert_eq!(input.state, WorkerOperationState::Accepted);
assert_eq!(input.event_id, Some(8));
server.join().expect("mock remote server finished"); server.join().expect("mock remote server finished");
let browser_payload = serde_json::to_string(&(workers, input)).unwrap(); let browser_payload = serde_json::to_string(&(workers, input)).unwrap();
@@ -5023,8 +5004,7 @@ mod tests {
"size_bytes": 0, "size_bytes": 0,
"source_graph": { "source_count": 0, "total_source_bytes": 0, "entrypoints": {}, "import_count": 0 } "source_graph": { "source_count": 0, "total_source_bytes": 0, "entrypoints": {}, "import_count": 0 }
}, },
"config_bundle": { "id": "remote-bundle", "digest": "remote-digest" }, "config_bundle": { "id": "remote-bundle", "digest": "remote-digest" }
"last_event_id": 0
}) })
} }
} }
+5 -1
View File
@@ -4487,7 +4487,8 @@ CREATE TABLE ticket_worker_links (ticket_id TEXT, worker_ref_key TEXT);
#[tokio::test] #[tokio::test]
async fn worker_credential_binds_once_and_notification_outbox_is_durable() { async fn worker_credential_binds_once_and_notification_outbox_is_durable() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
let store = SqliteWorkspaceStore::open(dir.path().join("server.db")).unwrap(); let db = dir.path().join("server.db");
let store = SqliteWorkspaceStore::open(&db).unwrap();
store store
.upsert_workspace(&WorkspaceRecord { .upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-a".to_string(), workspace_id: "workspace-a".to_string(),
@@ -4592,6 +4593,9 @@ CREATE TABLE ticket_worker_links (ticket_id TEXT, worker_ref_key TEXT);
}], }],
) )
.unwrap(); .unwrap();
drop(store);
let store = SqliteWorkspaceStore::open(&db).unwrap();
let pending = store let pending = store
.list_pending_ticket_notification_deliveries("workspace-a", 10) .list_pending_ticket_notification_deliveries("workspace-a", 10)
.unwrap(); .unwrap();
@@ -253,7 +253,6 @@ export type WorkerInputResult = {
state: WorkerOperationState; state: WorkerOperationState;
runtime_id: string; runtime_id: string;
worker_id: string; worker_id: string;
event_id?: number | null;
diagnostics: Diagnostic[]; diagnostics: Diagnostic[];
}; };