mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
parent
b5a7627b43
commit
a664892fe6
4 changed files with 514 additions and 41 deletions
397
run.json
397
run.json
File diff suppressed because one or more lines are too long
147
stages/007-simplify_gpt@1/diff.patch
Normal file
147
stages/007-simplify_gpt@1/diff.patch
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
diff --git a/lib/crates/fabro-core/src/executor.rs b/lib/crates/fabro-core/src/executor.rs
|
||||
index 454636e92..19cf142a1 100644
|
||||
--- a/lib/crates/fabro-core/src/executor.rs
|
||||
+++ b/lib/crates/fabro-core/src/executor.rs
|
||||
@@ -297,9 +297,9 @@ impl<G: Graph + 'static> Executor<G> {
|
||||
graph: &G,
|
||||
) -> Result<NodeResult<G::Meta>> {
|
||||
let policy = self.handler.retry_policy(node, graph);
|
||||
- let start = Instant::now();
|
||||
|
||||
for attempt in 1..=policy.max_attempts {
|
||||
+ let attempt_start = Instant::now();
|
||||
let attempt_ctx = AttemptContext {
|
||||
node,
|
||||
attempt,
|
||||
@@ -318,7 +318,7 @@ impl<G: Graph + 'static> Executor<G> {
|
||||
let delay = policy.backoff.delay_for_attempt(attempt);
|
||||
let result = node_result_from_outcome(
|
||||
outcome,
|
||||
- start.elapsed(),
|
||||
+ attempt_start.elapsed(),
|
||||
attempt,
|
||||
policy.max_attempts,
|
||||
);
|
||||
@@ -336,7 +336,7 @@ impl<G: Graph + 'static> Executor<G> {
|
||||
let final_outcome = self.handler.on_retries_exhausted(node, outcome);
|
||||
let result = node_result_from_outcome(
|
||||
final_outcome,
|
||||
- start.elapsed(),
|
||||
+ attempt_start.elapsed(),
|
||||
attempt,
|
||||
policy.max_attempts,
|
||||
);
|
||||
@@ -353,7 +353,7 @@ impl<G: Graph + 'static> Executor<G> {
|
||||
Ok(outcome) => {
|
||||
let result = node_result_from_outcome(
|
||||
outcome,
|
||||
- start.elapsed(),
|
||||
+ attempt_start.elapsed(),
|
||||
attempt,
|
||||
policy.max_attempts,
|
||||
);
|
||||
@@ -369,8 +369,12 @@ impl<G: Graph + 'static> Executor<G> {
|
||||
}
|
||||
Err(e) if can_retry && e.is_retryable() => {
|
||||
let delay = policy.backoff.delay_for_attempt(attempt);
|
||||
- let fail_result =
|
||||
- NodeResult::from_error(&e, start.elapsed(), attempt, policy.max_attempts);
|
||||
+ let fail_result = NodeResult::from_error(
|
||||
+ &e,
|
||||
+ attempt_start.elapsed(),
|
||||
+ attempt,
|
||||
+ policy.max_attempts,
|
||||
+ );
|
||||
let ctx = AttemptResultContext {
|
||||
node,
|
||||
result: &fail_result,
|
||||
@@ -386,7 +390,7 @@ impl<G: Graph + 'static> Executor<G> {
|
||||
let outcome = e.to_fail_outcome();
|
||||
let result = node_result_from_outcome(
|
||||
outcome,
|
||||
- start.elapsed(),
|
||||
+ attempt_start.elapsed(),
|
||||
attempt,
|
||||
policy.max_attempts,
|
||||
);
|
||||
@@ -1329,6 +1333,80 @@ mod tests {
|
||||
assert_eq!(log, vec![(1, true), (2, false)]);
|
||||
}
|
||||
|
||||
+ #[tokio::test]
|
||||
+ async fn executor_retry_attempt_wall_time_excludes_prior_attempts_and_backoff() {
|
||||
+ let wall_times = Arc::new(Mutex::new(Vec::<Duration>::new()));
|
||||
+
|
||||
+ struct WallTimeTracker(Arc<Mutex<Vec<Duration>>>);
|
||||
+ #[async_trait]
|
||||
+ impl RunLifecycle<TestGraph> for WallTimeTracker {
|
||||
+ async fn after_attempt(
|
||||
+ &self,
|
||||
+ ctx: &AttemptResultContext<'_, TestGraph>,
|
||||
+ _s: &ExecutionState,
|
||||
+ ) -> Result<()> {
|
||||
+ self.0.lock().unwrap().push(ctx.result.wall_time);
|
||||
+ Ok(())
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ struct SlowRetryThenSuccess(AtomicU32);
|
||||
+ #[async_trait]
|
||||
+ impl NodeHandler<TestGraph> for SlowRetryThenSuccess {
|
||||
+ async fn execute(
|
||||
+ &self,
|
||||
+ _n: &TestNode,
|
||||
+ _c: &Context,
|
||||
+ _g: &TestGraph,
|
||||
+ ) -> Result<Outcome> {
|
||||
+ sleep(Duration::from_millis(5)).await;
|
||||
+ let call = self.0.fetch_add(1, Ordering::Relaxed);
|
||||
+ if call == 0 {
|
||||
+ Ok(Outcome {
|
||||
+ status: StageOutcome::Failed {
|
||||
+ retry_requested: true,
|
||||
+ },
|
||||
+ ..Outcome::default()
|
||||
+ })
|
||||
+ } else {
|
||||
+ Ok(Outcome::success())
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ fn retry_policy(&self, _n: &TestNode, _g: &TestGraph) -> RetryPolicy {
|
||||
+ RetryPolicy {
|
||||
+ max_attempts: 2,
|
||||
+ backoff: BackoffPolicy {
|
||||
+ initial_delay: Duration::from_millis(500),
|
||||
+ factor: 1.0,
|
||||
+ max_delay: Duration::from_millis(500),
|
||||
+ jitter: false,
|
||||
+ },
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ let g = linear_graph(&["start", "end"]);
|
||||
+ let state = ExecutionState::new(&g).unwrap();
|
||||
+ let executor =
|
||||
+ ExecutorBuilder::new(Arc::new(SlowRetryThenSuccess(AtomicU32::new(0)))
|
||||
+ as Arc<dyn NodeHandler<TestGraph>>)
|
||||
+ .lifecycle(Box::new(WallTimeTracker(Arc::clone(&wall_times))))
|
||||
+ .build();
|
||||
+
|
||||
+ executor.run(&g, state).await.unwrap();
|
||||
+
|
||||
+ let wall_times = wall_times.lock().unwrap().clone();
|
||||
+ assert_eq!(wall_times.len(), 2);
|
||||
+ for wall_time in wall_times {
|
||||
+ assert!(wall_time >= Duration::from_millis(5));
|
||||
+ assert!(
|
||||
+ wall_time < Duration::from_millis(300),
|
||||
+ "attempt wall time should not include retry backoff or prior attempts: {wall_time:?}"
|
||||
+ );
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
#[tokio::test]
|
||||
async fn executor_retry_lifecycle_before_attempt_skip_stops_retry() {
|
||||
let call_count = Arc::new(std::sync::atomic::AtomicU32::new(0));
|
||||
6
stages/007-simplify_gpt@1/status.json
Normal file
6
stages/007-simplify_gpt@1/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"outcome": "succeeded",
|
||||
"notes": "Stage completed: simplify_gpt",
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-05-21T20:36:21.174781Z"
|
||||
}
|
||||
5
stages/008-verify@1/script_invocation.json
Normal file
5
stages/008-verify@1/script_invocation.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"script": "cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1 && cargo dev docs refresh 2>&1 && cargo dev docs check 2>&1",
|
||||
"command": "exec 2>&1\ncargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1 && cargo dev docs refresh 2>&1 && cargo dev docs check 2>&1",
|
||||
"language": "shell"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue