refactor(worker-runtime): route by WorkerRef and reconcile owned execution cleanup
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
+1264
-408
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1114
-138
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保存領域の直接読取で観測制限を迂回することは行っていない。
|
||||||
Reference in New Issue
Block a user