[wip] workflow cancellation policy
Some checks failed
CI / Rustfmt (push) Successful in 21s
CI / Cargo Audit & Deny (push) Successful in 32s
CI / Web Blocking Checks (push) Successful in 50s
CI / Security Blocking Checks (push) Successful in 9s
CI / Clippy (push) Failing after 1m58s
CI / Web Advisory Checks (push) Successful in 34s
CI / Security Advisory Checks (push) Successful in 1m26s
CI / Tests (push) Successful in 8m47s
Some checks failed
CI / Rustfmt (push) Successful in 21s
CI / Cargo Audit & Deny (push) Successful in 32s
CI / Web Blocking Checks (push) Successful in 50s
CI / Security Blocking Checks (push) Successful in 9s
CI / Clippy (push) Failing after 1m58s
CI / Web Advisory Checks (push) Successful in 34s
CI / Security Advisory Checks (push) Successful in 1m26s
CI / Tests (push) Successful in 8m47s
This commit is contained in:
@@ -15,9 +15,9 @@ pub use pack_service::{
|
||||
PackSyncResult, PackValidationResult, PackWorkflowService, PackWorkflowServiceConfig,
|
||||
};
|
||||
pub use parser::{
|
||||
parse_workflow_file, parse_workflow_yaml, workflow_to_json, BackoffStrategy, DecisionBranch,
|
||||
ParseError, ParseResult, PublishDirective, RetryConfig, Task, TaskTransition, TaskType,
|
||||
WorkflowDefinition,
|
||||
parse_workflow_file, parse_workflow_yaml, workflow_to_json, BackoffStrategy,
|
||||
CancellationPolicy, DecisionBranch, ParseError, ParseResult, PublishDirective, RetryConfig,
|
||||
Task, TaskTransition, TaskType, WorkflowDefinition,
|
||||
};
|
||||
pub use registrar::{RegistrationOptions, RegistrationResult, WorkflowRegistrar};
|
||||
pub use validator::{ValidationError, ValidationResult, WorkflowValidator};
|
||||
|
||||
@@ -127,6 +127,17 @@ pub struct WorkflowDefinition {
|
||||
/// Tags for categorization
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
|
||||
/// Cancellation policy for the workflow.
|
||||
///
|
||||
/// Controls what happens to running tasks when the workflow is cancelled:
|
||||
/// - `allow_finish` (default): Running tasks are allowed to complete naturally.
|
||||
/// Only pending/requested tasks are cancelled. The workflow waits for running
|
||||
/// tasks to finish but does not dispatch any new tasks.
|
||||
/// - `cancel_running`: All running and pending tasks are forcefully cancelled.
|
||||
/// Running processes receive SIGINT → SIGTERM → SIGKILL via the worker.
|
||||
#[serde(default, skip_serializing_if = "CancellationPolicy::is_default")]
|
||||
pub cancellation_policy: CancellationPolicy,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -411,6 +422,27 @@ fn default_task_type() -> TaskType {
|
||||
TaskType::Action
|
||||
}
|
||||
|
||||
/// Policy controlling how running tasks are handled when a workflow is cancelled.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CancellationPolicy {
|
||||
/// Running tasks are allowed to complete naturally; only pending tasks are
|
||||
/// cancelled and no new tasks are dispatched. This is the default.
|
||||
#[default]
|
||||
AllowFinish,
|
||||
/// All running and pending tasks are forcefully cancelled. Running
|
||||
/// processes receive SIGINT → SIGTERM → SIGKILL via the worker.
|
||||
CancelRunning,
|
||||
}
|
||||
|
||||
impl CancellationPolicy {
|
||||
/// Returns `true` when the value is the default ([`AllowFinish`]).
|
||||
/// Used by `#[serde(skip_serializing_if)]` to keep stored JSON compact.
|
||||
pub fn is_default(&self) -> bool {
|
||||
matches!(self, Self::AllowFinish)
|
||||
}
|
||||
}
|
||||
|
||||
/// Task type enumeration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -1509,4 +1541,93 @@ tasks:
|
||||
panic!("Expected Simple publish directive");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cancellation_policy_defaults_to_allow_finish() {
|
||||
let yaml = r#"
|
||||
version: "1.0.0"
|
||||
tasks:
|
||||
- name: task1
|
||||
action: core.echo
|
||||
input:
|
||||
message: hello
|
||||
"#;
|
||||
let workflow = parse_workflow_yaml(yaml).unwrap();
|
||||
assert_eq!(
|
||||
workflow.cancellation_policy,
|
||||
CancellationPolicy::AllowFinish
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cancellation_policy_cancel_running() {
|
||||
let yaml = r#"
|
||||
version: "1.0.0"
|
||||
cancellation_policy: cancel_running
|
||||
tasks:
|
||||
- name: task1
|
||||
action: core.echo
|
||||
input:
|
||||
message: hello
|
||||
"#;
|
||||
let workflow = parse_workflow_yaml(yaml).unwrap();
|
||||
assert_eq!(
|
||||
workflow.cancellation_policy,
|
||||
CancellationPolicy::CancelRunning
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cancellation_policy_allow_finish_explicit() {
|
||||
let yaml = r#"
|
||||
version: "1.0.0"
|
||||
cancellation_policy: allow_finish
|
||||
tasks:
|
||||
- name: task1
|
||||
action: core.echo
|
||||
input:
|
||||
message: hello
|
||||
"#;
|
||||
let workflow = parse_workflow_yaml(yaml).unwrap();
|
||||
assert_eq!(
|
||||
workflow.cancellation_policy,
|
||||
CancellationPolicy::AllowFinish
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cancellation_policy_json_roundtrip() {
|
||||
let yaml = r#"
|
||||
version: "1.0.0"
|
||||
cancellation_policy: cancel_running
|
||||
tasks:
|
||||
- name: step1
|
||||
action: core.echo
|
||||
input:
|
||||
message: hello
|
||||
"#;
|
||||
let workflow = parse_workflow_yaml(yaml).unwrap();
|
||||
let json = workflow_to_json(&workflow).unwrap();
|
||||
let restored: WorkflowDefinition = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(
|
||||
restored.cancellation_policy,
|
||||
CancellationPolicy::CancelRunning
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cancellation_policy_absent_in_json_defaults() {
|
||||
// Simulate a definition stored in the DB before this field existed
|
||||
let json = serde_json::json!({
|
||||
"ref": "test.wf",
|
||||
"label": "Test",
|
||||
"version": "1.0.0",
|
||||
"tasks": [{"name": "t1", "action": "core.echo", "input": {"message": "hi"}}]
|
||||
});
|
||||
let workflow: WorkflowDefinition = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(
|
||||
workflow.cancellation_policy,
|
||||
CancellationPolicy::AllowFinish
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user