refactor(worker-runtime): route by WorkerRef and reconcile owned execution cleanup

This commit is contained in:
2026-10-06 09:13:46 +09:00
parent 0e2a6f2860
commit b96bd110c6
9 changed files with 6876 additions and 1276 deletions
+119 -79
View File
@@ -207,34 +207,6 @@ impl WorkerExecutionResult {
} }
} }
/// Opaque per-Worker execution handle returned by a backend.
///
/// The handle is a typed token for routing calls back into the same backend. It
/// intentionally contains no socket path, process id, credential, manifest path,
/// or session path.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkerExecutionHandle {
worker_ref: WorkerRef,
backend_id: String,
}
impl WorkerExecutionHandle {
pub fn new(worker_ref: WorkerRef, backend_id: impl Into<String>) -> Self {
Self {
worker_ref,
backend_id: backend_id.into(),
}
}
pub fn worker_ref(&self) -> &WorkerRef {
&self.worker_ref
}
pub fn backend_id(&self) -> &str {
&self.backend_id
}
}
/// Runtime hooks available to an execution backend for one Worker. /// Runtime hooks available to an execution backend for one Worker.
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
type WorkerObservationPublisher = Arc< type WorkerObservationPublisher = Arc<
@@ -348,7 +320,17 @@ impl WorkerExecutionContext {
let Some(event) = event else { let Some(event) = event else {
return Ok(()); return Ok(());
}; };
(self.observation_publisher)(self.worker_ref.clone(), event)?; if let Err(error) = (self.observation_publisher)(self.worker_ref.clone(), event.clone())
{
let mut state = self
.publication_state
.lock()
.map_err(|_| RuntimeError::StatePoisoned)?;
if let WorkerExecutionPublicationState::Activating(events) = &mut *state {
events.push_front(event);
}
return Err(error);
}
} }
} }
@@ -443,7 +425,6 @@ pub struct WorkerExecutionRestoreRequest {
pub struct WorkerExecutionStopRequest { pub struct WorkerExecutionStopRequest {
pub operation_id: WorkerLifecycleOperationId, pub operation_id: WorkerLifecycleOperationId,
pub worker_ref: WorkerRef, pub worker_ref: WorkerRef,
pub handle: Option<WorkerExecutionHandle>,
} }
/// Runtime-side request to refresh the latest Workspace Config before Worker creation. /// Runtime-side request to refresh the latest Workspace Config before Worker creation.
@@ -466,7 +447,6 @@ pub enum WorkspaceConfigFetchResult {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum WorkerExecutionSpawnResult { pub enum WorkerExecutionSpawnResult {
Connected { Connected {
handle: WorkerExecutionHandle,
worker_state: protocol::WorkerStateSnapshot, worker_state: protocol::WorkerStateSnapshot,
workdir_attachments: Vec<crate::catalog::WorkingDirectoryAttachmentStatus>, workdir_attachments: Vec<crate::catalog::WorkingDirectoryAttachmentStatus>,
}, },
@@ -475,11 +455,10 @@ pub enum WorkerExecutionSpawnResult {
/// Live work started, failed, and all operation-owned resources were joined/released. /// Live work started, failed, and all operation-owned resources were joined/released.
RolledBack(WorkerExecutionResult), RolledBack(WorkerExecutionResult),
/// The operation crossed the live side-effect boundary and could not prove /// The operation crossed the live side-effect boundary and could not prove
/// either commit or complete cleanup. Optional handle/state fields preserve /// either commit or complete cleanup. Optional state fields preserve
/// concrete execution evidence for retry reconciliation. /// concrete execution evidence for retry reconciliation.
ReconciliationRequired { ReconciliationRequired {
result: WorkerExecutionResult, result: WorkerExecutionResult,
handle: Option<WorkerExecutionHandle>,
worker_state: Option<protocol::WorkerStateSnapshot>, worker_state: Option<protocol::WorkerStateSnapshot>,
workdir_attachments: Vec<crate::catalog::WorkingDirectoryAttachmentStatus>, workdir_attachments: Vec<crate::catalog::WorkingDirectoryAttachmentStatus>,
}, },
@@ -490,12 +469,10 @@ pub enum WorkerExecutionSpawnResult {
impl WorkerExecutionSpawnResult { impl WorkerExecutionSpawnResult {
pub fn connected( pub fn connected(
handle: WorkerExecutionHandle,
worker_state: protocol::WorkerStateSnapshot, worker_state: protocol::WorkerStateSnapshot,
workdir_attachments: Vec<crate::catalog::WorkingDirectoryAttachmentStatus>, workdir_attachments: Vec<crate::catalog::WorkingDirectoryAttachmentStatus>,
) -> Self { ) -> Self {
Self::Connected { Self::Connected {
handle,
worker_state, worker_state,
workdir_attachments, workdir_attachments,
} }
@@ -521,6 +498,57 @@ pub struct WorkerSessionAttachmentRequest {
pub attachment_id: String, pub attachment_id: String,
} }
/// One real protocol connection, bound to the execution that was attached.
/// This is not a routing token and is never persisted as Worker authority.
#[cfg(feature = "ws-server")]
pub struct WorkerProtocolTransport {
pub(crate) worker_ref: WorkerRef,
pub snapshot: protocol::Event,
pub events: tokio::sync::mpsc::Receiver<protocol::Event>,
dispatch:
Arc<dyn Fn(Method) -> Result<Vec<protocol::Event>, WorkerExecutionResult> + Send + Sync>,
validate: Arc<dyn Fn() -> Result<(), WorkerExecutionResult> + Send + Sync>,
}
#[cfg(feature = "ws-server")]
impl WorkerProtocolTransport {
pub fn new(
worker_ref: WorkerRef,
snapshot: protocol::Event,
events: tokio::sync::mpsc::Receiver<protocol::Event>,
dispatch: Arc<
dyn Fn(Method) -> Result<Vec<protocol::Event>, WorkerExecutionResult> + Send + Sync,
>,
validate: Arc<dyn Fn() -> Result<(), WorkerExecutionResult> + Send + Sync>,
) -> Self {
Self {
worker_ref,
snapshot,
events,
dispatch,
validate,
}
}
pub fn dispatch(&self, method: Method) -> Result<Vec<protocol::Event>, WorkerExecutionResult> {
(self.dispatch)(method)
}
pub fn validate(&self) -> Result<(), WorkerExecutionResult> {
(self.validate)()
}
}
#[cfg(feature = "ws-server")]
impl fmt::Debug for WorkerProtocolTransport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WorkerProtocolTransport")
.finish_non_exhaustive()
}
}
/// The connection selects this backend; WorkerRef identifies an execution,
/// never a capability or authorization token.
pub trait WorkerExecutionBackend: Send + Sync + 'static { pub trait WorkerExecutionBackend: Send + Sync + 'static {
fn backend_id(&self) -> &str; fn backend_id(&self) -> &str;
@@ -580,8 +608,9 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
fn activate_restored_worker( fn activate_restored_worker(
&self, &self,
_operation_id: WorkerLifecycleOperationId, _operation_id: WorkerLifecycleOperationId,
_handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
) -> Result<(), String> { ) -> Result<(), String> {
let _ = worker_ref;
Ok(()) Ok(())
} }
@@ -673,20 +702,17 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
Ok(()) Ok(())
} }
fn dispatch_input( fn dispatch_input(&self, worker_ref: &WorkerRef, input: WorkerInput) -> WorkerExecutionResult;
&self,
handle: &WorkerExecutionHandle,
input: WorkerInput,
) -> WorkerExecutionResult;
fn upload_file( fn upload_file(
&self, &self,
_handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
_file_name: &str, _file_name: &str,
_media_type: &str, _media_type: &str,
_content: &[u8], _content: &[u8],
_context: Option<&session_store::UploadedFileUploadContext>, _context: Option<&session_store::UploadedFileUploadContext>,
) -> Result<UploadedFileRef, WorkerExecutionResult> { ) -> Result<UploadedFileRef, WorkerExecutionResult> {
let _ = worker_ref;
Err(WorkerExecutionResult::unsupported( Err(WorkerExecutionResult::unsupported(
WorkerExecutionOperation::UploadFile, WorkerExecutionOperation::UploadFile,
"execution backend does not support file upload", "execution backend does not support file upload",
@@ -695,20 +721,18 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
fn delete_uploaded_file( fn delete_uploaded_file(
&self, &self,
_handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
_artifact_id: &str, _artifact_id: &str,
) -> WorkerExecutionResult { ) -> WorkerExecutionResult {
let _ = worker_ref;
WorkerExecutionResult::unsupported( WorkerExecutionResult::unsupported(
WorkerExecutionOperation::DeleteUploadedFile, WorkerExecutionOperation::DeleteUploadedFile,
"execution backend does not support uploaded-file deletion", "execution backend does not support uploaded-file deletion",
) )
} }
fn dispatch_method( fn dispatch_method(&self, worker_ref: &WorkerRef, _method: Method) -> WorkerExecutionResult {
&self, let _ = worker_ref;
_handle: &WorkerExecutionHandle,
_method: Method,
) -> WorkerExecutionResult {
WorkerExecutionResult::unsupported( WorkerExecutionResult::unsupported(
WorkerExecutionOperation::ProtocolMethod, WorkerExecutionOperation::ProtocolMethod,
"execution backend does not support direct Worker protocol methods", "execution backend does not support direct Worker protocol methods",
@@ -717,32 +741,29 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
fn worker_completions( fn worker_completions(
&self, &self,
_handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
_kind: protocol::CompletionKind, _kind: protocol::CompletionKind,
_prefix: &str, _prefix: &str,
_context: Option<&protocol::CompletionContext>, _context: Option<&protocol::CompletionContext>,
) -> Vec<protocol::CompletionEntry> { ) -> Vec<protocol::CompletionEntry> {
let _ = worker_ref;
Vec::new() Vec::new()
} }
fn stop_worker_operation(&self, request: WorkerExecutionStopRequest) -> WorkerExecutionResult { fn stop_worker_operation(&self, request: WorkerExecutionStopRequest) -> WorkerExecutionResult {
let Some(handle) = request.handle.as_ref() else { self.stop_worker(&request.worker_ref)
return WorkerExecutionResult::unsupported(
WorkerExecutionOperation::Stop,
"execution backend cannot reconcile a stopped Worker without a live handle",
);
};
self.stop_worker(handle)
} }
fn stop_worker(&self, _handle: &WorkerExecutionHandle) -> WorkerExecutionResult { fn stop_worker(&self, worker_ref: &WorkerRef) -> WorkerExecutionResult {
let _ = worker_ref;
WorkerExecutionResult::unsupported( WorkerExecutionResult::unsupported(
WorkerExecutionOperation::Stop, WorkerExecutionOperation::Stop,
"execution backend does not support stopping workers", "execution backend does not support stopping workers",
) )
} }
fn cancel_worker(&self, _handle: &WorkerExecutionHandle) -> WorkerExecutionResult { fn cancel_worker(&self, worker_ref: &WorkerRef) -> WorkerExecutionResult {
let _ = worker_ref;
WorkerExecutionResult::unsupported( WorkerExecutionResult::unsupported(
WorkerExecutionOperation::Cancel, WorkerExecutionOperation::Cancel,
"execution backend does not support cancelling workers", "execution backend does not support cancelling workers",
@@ -750,7 +771,20 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
} }
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
fn worker_snapshot(&self, _handle: &WorkerExecutionHandle) -> Option<protocol::Event> { fn attach_worker_protocol(
self: Arc<Self>,
worker_ref: &WorkerRef,
) -> Result<WorkerProtocolTransport, WorkerExecutionResult> {
let _ = worker_ref;
Err(WorkerExecutionResult::unsupported(
WorkerExecutionOperation::ProtocolMethod,
"execution backend does not support bound Worker protocol connections",
))
}
#[cfg(feature = "ws-server")]
fn worker_snapshot(&self, worker_ref: &WorkerRef) -> Option<protocol::Event> {
let _ = worker_ref;
None None
} }
} }
@@ -828,9 +862,10 @@ impl WorkerExecutionBackendRef {
pub(crate) fn activate_restored_worker( pub(crate) fn activate_restored_worker(
&self, &self,
operation_id: WorkerLifecycleOperationId, operation_id: WorkerLifecycleOperationId,
handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
) -> Result<(), String> { ) -> Result<(), String> {
self.backend.activate_restored_worker(operation_id, handle) self.backend
.activate_restored_worker(operation_id, worker_ref)
} }
pub(crate) fn restore_worker( pub(crate) fn restore_worker(
@@ -896,57 +931,62 @@ impl WorkerExecutionBackendRef {
pub(crate) fn dispatch_input( pub(crate) fn dispatch_input(
&self, &self,
handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
input: WorkerInput, input: WorkerInput,
) -> WorkerExecutionResult { ) -> WorkerExecutionResult {
self.backend.dispatch_input(handle, input) self.backend.dispatch_input(worker_ref, input)
} }
pub(crate) fn upload_file( pub(crate) fn upload_file(
&self, &self,
handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
file_name: &str, file_name: &str,
media_type: &str, media_type: &str,
content: &[u8], content: &[u8],
context: Option<&session_store::UploadedFileUploadContext>, context: Option<&session_store::UploadedFileUploadContext>,
) -> Result<UploadedFileRef, WorkerExecutionResult> { ) -> Result<UploadedFileRef, WorkerExecutionResult> {
self.backend self.backend
.upload_file(handle, file_name, media_type, content, context) .upload_file(worker_ref, file_name, media_type, content, context)
} }
pub(crate) fn delete_uploaded_file( pub(crate) fn delete_uploaded_file(
&self, &self,
handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
artifact_id: &str, artifact_id: &str,
) -> WorkerExecutionResult { ) -> WorkerExecutionResult {
self.backend.delete_uploaded_file(handle, artifact_id) self.backend.delete_uploaded_file(worker_ref, artifact_id)
} }
pub(crate) fn dispatch_method( pub(crate) fn dispatch_method(
&self, &self,
handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
method: Method, method: Method,
) -> WorkerExecutionResult { ) -> WorkerExecutionResult {
self.backend.dispatch_method(handle, method) self.backend.dispatch_method(worker_ref, method)
} }
#[cfg(feature = "ws-server")] #[cfg(feature = "ws-server")]
pub(crate) fn worker_snapshot( pub(crate) fn attach_worker_protocol(
&self, &self,
handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
) -> Option<protocol::Event> { ) -> Result<WorkerProtocolTransport, WorkerExecutionResult> {
self.backend.worker_snapshot(handle) Arc::clone(&self.backend).attach_worker_protocol(worker_ref)
}
#[cfg(feature = "ws-server")]
pub(crate) fn worker_snapshot(&self, worker_ref: &WorkerRef) -> Option<protocol::Event> {
self.backend.worker_snapshot(worker_ref)
} }
pub(crate) fn worker_completions( pub(crate) fn worker_completions(
&self, &self,
handle: &WorkerExecutionHandle, worker_ref: &WorkerRef,
kind: protocol::CompletionKind, kind: protocol::CompletionKind,
prefix: &str, prefix: &str,
context: Option<&protocol::CompletionContext>, context: Option<&protocol::CompletionContext>,
) -> Vec<protocol::CompletionEntry> { ) -> Vec<protocol::CompletionEntry> {
self.backend self.backend
.worker_completions(handle, kind, prefix, context) .worker_completions(worker_ref, kind, prefix, context)
} }
pub(crate) fn stop_worker_operation( pub(crate) fn stop_worker_operation(
@@ -956,12 +996,12 @@ impl WorkerExecutionBackendRef {
self.backend.stop_worker_operation(request) self.backend.stop_worker_operation(request)
} }
pub(crate) fn stop_worker(&self, handle: &WorkerExecutionHandle) -> WorkerExecutionResult { pub(crate) fn stop_worker(&self, worker_ref: &WorkerRef) -> WorkerExecutionResult {
self.backend.stop_worker(handle) self.backend.stop_worker(worker_ref)
} }
pub(crate) fn cancel_worker(&self, handle: &WorkerExecutionHandle) -> WorkerExecutionResult { pub(crate) fn cancel_worker(&self, worker_ref: &WorkerRef) -> WorkerExecutionResult {
self.backend.cancel_worker(handle) self.backend.cancel_worker(worker_ref)
} }
} }
+27 -10
View File
@@ -493,7 +493,12 @@ pub(crate) struct PersistedWorkerRestoreOperation {
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub(crate) struct PersistedWorkerStopOperation { pub(crate) struct PersistedWorkerStopOperation {
pub(crate) operation_id: WorkerLifecycleOperationId, pub(crate) operation_id: WorkerLifecycleOperationId,
pub(crate) request: CreateWorkerRequest, /// Restore resources remain operation-owned until stop confirms cleanup.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) pending_restore: Option<PersistedWorkerRestoreOperation>,
/// Stop can reconcile an identity whose execution metadata is unavailable.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) request: Option<CreateWorkerRequest>,
pub(crate) binding: Option<PersistedWorkerExecutionBinding>, pub(crate) binding: Option<PersistedWorkerExecutionBinding>,
pub(crate) restore_intent: WorkerRestoreIntent, pub(crate) restore_intent: WorkerRestoreIntent,
pub(crate) last_settled_status: WorkerStatus, pub(crate) last_settled_status: WorkerStatus,
@@ -1942,7 +1947,7 @@ impl WorkerAggregateRecord {
}); });
} }
( (
&restore.request, Some(&restore.request),
&restore.binding, &restore.binding,
if restore.last_settled_status.is_active() { if restore.last_settled_status.is_active() {
WorkerRestoreIntent::Automatic WorkerRestoreIntent::Automatic
@@ -1953,7 +1958,7 @@ impl WorkerAggregateRecord {
) )
} }
PersistedWorkerLifecycleOperation::Stop(stop) => { PersistedWorkerLifecycleOperation::Stop(stop) => {
if !stop.last_settled_status.is_active() { if !stop.last_settled_status.is_active() && stop.pending_restore.is_none() {
return Err(RuntimeError::StoreCorrupt { return Err(RuntimeError::StoreCorrupt {
operation: "read Worker stop reconciliation", operation: "read Worker stop reconciliation",
path: path.to_path_buf(), path: path.to_path_buf(),
@@ -1961,8 +1966,18 @@ impl WorkerAggregateRecord {
.to_string(), .to_string(),
}); });
} }
if stop.request.is_none()
&& (stop.binding.is_some() || stop.pending_restore.is_some())
{
return Err(RuntimeError::StoreCorrupt {
operation: "read Worker stop reconciliation",
path: path.to_path_buf(),
message: "metadata-free stop cannot carry restore authority"
.to_string(),
});
}
( (
&stop.request, stop.request.as_ref(),
&stop.binding, &stop.binding,
stop.restore_intent, stop.restore_intent,
stop.last_settled_status, stop.last_settled_status,
@@ -1978,13 +1993,15 @@ impl WorkerAggregateRecord {
.to_string(), .to_string(),
}); });
} }
WorkerExecutionRecord { if let Some(request) = request {
schema_version: SCHEMA_VERSION, WorkerExecutionRecord {
request: request.clone(), schema_version: SCHEMA_VERSION,
binding: binding.clone(), request: request.clone(),
restore_intent, binding: binding.clone(),
restore_intent,
}
.validate_for_schema(&identity, path, SCHEMA_VERSION)?;
} }
.validate_for_schema(&identity, path, SCHEMA_VERSION)?;
PersistedWorkerExecutionState::ReconciliationRequired(operation) PersistedWorkerExecutionState::ReconciliationRequired(operation)
} }
WorkerExecutionStateRecord::Unavailable => PersistedWorkerExecutionState::Unavailable, WorkerExecutionStateRecord::Unavailable => PersistedWorkerExecutionState::Unavailable,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -13
View File
@@ -6869,7 +6869,7 @@ mod tests {
fn dispatch_input( fn dispatch_input(
&self, &self,
_handle: &worker_runtime::execution::WorkerExecutionHandle, _worker_ref: &EmbeddedWorkerRef,
_input: EmbeddedWorkerInput, _input: EmbeddedWorkerInput,
) -> worker_runtime::execution::WorkerExecutionResult { ) -> worker_runtime::execution::WorkerExecutionResult {
worker_runtime::execution::WorkerExecutionResult::rejected( worker_runtime::execution::WorkerExecutionResult::rejected(
@@ -6898,7 +6898,7 @@ mod tests {
fn dispatch_input( fn dispatch_input(
&self, &self,
_handle: &worker_runtime::execution::WorkerExecutionHandle, _worker_ref: &EmbeddedWorkerRef,
_input: EmbeddedWorkerInput, _input: EmbeddedWorkerInput,
) -> worker_runtime::execution::WorkerExecutionResult { ) -> worker_runtime::execution::WorkerExecutionResult {
unreachable!("Repository observation test does not dispatch Worker input") unreachable!("Repository observation test does not dispatch Worker input")
@@ -6978,10 +6978,6 @@ mod tests {
.unwrap() .unwrap()
.insert(request.worker_ref.clone(), request.context); .insert(request.worker_ref.clone(), request.context);
worker_runtime::execution::WorkerExecutionSpawnResult::Connected { worker_runtime::execution::WorkerExecutionSpawnResult::Connected {
handle: worker_runtime::execution::WorkerExecutionHandle::new(
request.worker_ref,
self.backend_id(),
),
worker_state: protocol::WorkerStateSnapshot { worker_state: protocol::WorkerStateSnapshot {
..protocol::WorkerStatus::Idle.into() ..protocol::WorkerStatus::Idle.into()
}, },
@@ -7000,15 +6996,10 @@ mod tests {
fn dispatch_input( fn dispatch_input(
&self, &self,
handle: &worker_runtime::execution::WorkerExecutionHandle, worker_ref: &EmbeddedWorkerRef,
input: EmbeddedWorkerInput, input: EmbeddedWorkerInput,
) -> worker_runtime::execution::WorkerExecutionResult { ) -> worker_runtime::execution::WorkerExecutionResult {
let context = self let context = self.contexts.lock().unwrap().get(worker_ref).cloned();
.contexts
.lock()
.unwrap()
.get(handle.worker_ref())
.cloned();
let Some(context) = context else { let Some(context) = context else {
return worker_runtime::execution::WorkerExecutionResult::rejected( return worker_runtime::execution::WorkerExecutionResult::rejected(
worker_runtime::execution::WorkerExecutionOperation::Input, worker_runtime::execution::WorkerExecutionOperation::Input,
@@ -3,10 +3,10 @@ use protocol::subscription::{SubscriptionWorkerIds, SubscriptionWorkerState};
use worker_runtime::Runtime; use worker_runtime::Runtime;
use worker_runtime::catalog::{CreateWorkerRequest, ProfileSelector, ProfileSourceArchiveSource}; use worker_runtime::catalog::{CreateWorkerRequest, ProfileSelector, ProfileSourceArchiveSource};
use worker_runtime::execution::{ use worker_runtime::execution::{
WorkerExecutionBackend, WorkerExecutionHandle, WorkerExecutionOperation, WorkerExecutionResult, WorkerExecutionBackend, WorkerExecutionOperation, WorkerExecutionResult,
WorkerExecutionSpawnRequest, WorkerExecutionSpawnResult, WorkerExecutionSpawnRequest, WorkerExecutionSpawnResult,
}; };
use worker_runtime::identity::WorkerId; use worker_runtime::identity::{WorkerId, WorkerRef};
#[derive(Debug)] #[derive(Debug)]
struct TestExecutionBackend; struct TestExecutionBackend;
@@ -16,9 +16,8 @@ impl WorkerExecutionBackend for TestExecutionBackend {
"runtime-subscription-test" "runtime-subscription-test"
} }
fn spawn_worker(&self, request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult { fn spawn_worker(&self, _request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult {
WorkerExecutionSpawnResult::connected( WorkerExecutionSpawnResult::connected(
WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
protocol::WorkerStateSnapshot { protocol::WorkerStateSnapshot {
..protocol::WorkerStatus::Idle.into() ..protocol::WorkerStatus::Idle.into()
}, },
@@ -28,7 +27,7 @@ impl WorkerExecutionBackend for TestExecutionBackend {
fn dispatch_input( fn dispatch_input(
&self, &self,
_handle: &WorkerExecutionHandle, _worker_ref: &WorkerRef,
input: worker_runtime::interaction::WorkerInput, input: worker_runtime::interaction::WorkerInput,
) -> WorkerExecutionResult { ) -> WorkerExecutionResult {
if let Some(submission_request_id) = input.submission_request_id { if let Some(submission_request_id) = input.submission_request_id {
@@ -43,7 +42,7 @@ impl WorkerExecutionBackend for TestExecutionBackend {
} }
} }
fn stop_worker(&self, _handle: &WorkerExecutionHandle) -> WorkerExecutionResult { fn stop_worker(&self, _worker_ref: &WorkerRef) -> WorkerExecutionResult {
WorkerExecutionResult::accepted(WorkerExecutionOperation::Stop) WorkerExecutionResult::accepted(WorkerExecutionOperation::Stop)
} }
} }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,15 @@
# SubWorker follow-up後のcommitted sessionを確認できなかった
T-700の実装中、既存のIdleなdirect child `backend`へ`WorkerSendInput`で追加修正を依頼した。toolは成功を返し、その後Idle通知と`WorkerList`のIdle表示を確認した。
一方、`ViewSessionOverview`と`SearchSessionEntries`が返した最後のentryは、follow-up前の保存Manifest fixture修正報告(entry range 744)だった。今回送った追加依頼の文字列の検索は0件で、offset 744からもその旧報告1件しか見えなかった。したがって、通知だけから追加作業の完了は認定できなかった。これは最新turnの実行失敗、captureの遅延、観測面のfreshnessのいずれかを切り分ける証拠ではない。
親は現在のdiffを直接確認し、子をStopしてdelegated scopeを解放した後、残りのShutdown admission修正を新しいdirect childへ限定して委譲した。新しい子では最初の依頼と修正報告をsession observationから確認できた。
改善案:
- Idle通知に、対象turnとcommitted captureの末尾entryの対応が分かる情報を含める。
- follow-upの開始またはcaptureに失敗した場合、成功した旧turnだけを返す代わりにbounded diagnosticを表示する。
- observationで最新のcommitted captureが未取得の場合、古いsnapshotであることを明示する。
credential、raw prompt、Backend保存領域の直接読取で観測制限を迂回することは行っていない。