huskies: merge 1062
This commit is contained in:
@@ -533,4 +533,48 @@ mod tests {
|
|||||||
let args = serde_json::json!({ "mode": "persistent" });
|
let args = serde_json::json!({ "mode": "persistent" });
|
||||||
assert_eq!(parse_mode(&args), FireMode::Persistent);
|
assert_eq!(parse_mode(&args), FireMode::Persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression test: once-mode triggers with a server-restarting action (e.g.
|
||||||
|
/// rebuild_and_restart) must be removed from the store BEFORE the action is
|
||||||
|
/// dispatched. If cancellation happens after dispatch, a server restart
|
||||||
|
/// caused by the action reloads the persisted store and replays the trigger.
|
||||||
|
#[test]
|
||||||
|
fn once_mode_rebuild_trigger_cancelled_before_action() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let path = dir.path().join("triggers.json");
|
||||||
|
let store = EventTriggerStore::load(path.clone());
|
||||||
|
|
||||||
|
let trigger = store
|
||||||
|
.add(
|
||||||
|
TriggerPredicate {
|
||||||
|
story_id: None,
|
||||||
|
from_stage: None,
|
||||||
|
to_stage: Some("Current".to_string()),
|
||||||
|
event_kind: None,
|
||||||
|
},
|
||||||
|
TriggerAction::Mcp {
|
||||||
|
method: "rebuild_and_restart".to_string(),
|
||||||
|
args: serde_json::json!({}),
|
||||||
|
},
|
||||||
|
FireMode::Once,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let id = trigger.id.clone();
|
||||||
|
|
||||||
|
// Simulate the subscriber: cancel BEFORE the action runs.
|
||||||
|
store.cancel(&id);
|
||||||
|
|
||||||
|
// Trigger must be absent immediately (simulates "right after match").
|
||||||
|
assert!(
|
||||||
|
store.list().is_empty(),
|
||||||
|
"once-mode trigger must be absent before its action fires"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulate server restart: reload from persisted store.
|
||||||
|
let reloaded = EventTriggerStore::load(path);
|
||||||
|
assert!(
|
||||||
|
reloaded.list().is_empty(),
|
||||||
|
"trigger must not survive a server restart caused by its own action"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,8 +346,6 @@ pub(crate) fn spawn_event_trigger_subscriber(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut to_cancel: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
for trigger in &triggers {
|
for trigger in &triggers {
|
||||||
if !trigger.predicate.matches(&fired) {
|
if !trigger.predicate.matches(&fired) {
|
||||||
continue;
|
continue;
|
||||||
@@ -361,6 +359,13 @@ pub(crate) fn spawn_event_trigger_subscriber(
|
|||||||
crate::pipeline_state::stage_label(&fired.after),
|
crate::pipeline_state::stage_label(&fired.after),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Cancel once-mode triggers before dispatching the action so
|
||||||
|
// that a server restart triggered by the action (e.g.
|
||||||
|
// rebuild_and_restart) cannot find and replay the trigger.
|
||||||
|
if trigger.mode == FireMode::Once {
|
||||||
|
store.cancel(&trigger.id);
|
||||||
|
}
|
||||||
|
|
||||||
match &trigger.action {
|
match &trigger.action {
|
||||||
TriggerAction::Mcp { method, args } => {
|
TriggerAction::Mcp { method, args } => {
|
||||||
execute_mcp_action(method, args.clone(), &ctx).await;
|
execute_mcp_action(method, args.clone(), &ctx).await;
|
||||||
@@ -375,14 +380,6 @@ pub(crate) fn spawn_event_trigger_subscriber(
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if trigger.mode == FireMode::Once {
|
|
||||||
to_cancel.push(trigger.id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !to_cancel.is_empty() {
|
|
||||||
store.cancel_batch(&to_cancel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user