fix(engine): bounded blocking sync push on exit #138

Merged
funman300 merged 1 commits from fix/exit-push-bounded into master 2026-07-07 03:05:17 +00:00
+25 -15
View File
@@ -272,12 +272,21 @@ fn poll_pull_result(
} }
} }
/// Last-schedule system: starts a best-effort push of the current local state /// Upper bound on how long [`push_on_exit`] may block the closing app.
/// on [`AppExit`] without blocking the Bevy main thread. /// Long enough for one healthy round-trip; short enough that quitting
/// never feels hung when the server is unreachable.
const EXIT_PUSH_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2);
/// Last-schedule system: pushes the current local state on [`AppExit`],
/// blocking the main thread for at most [`EXIT_PUSH_TIMEOUT`].
/// ///
/// The detached task may be cut short by process teardown, so local atomic /// This deliberately blocks: the previous detached-task version was almost
/// persistence remains the durable source of truth even if the final remote /// always cut short by process teardown, so the final session's push
/// push does not complete. /// silently never happened (2026-07-06 review, finding M3). A bounded wait
/// during the app's final frame is invisible to the player and lets the
/// round-trip actually complete on a healthy network. On timeout or error
/// the push is skipped — local atomic persistence remains the durable
/// source of truth and the next launch's pull/push converges.
fn push_on_exit( fn push_on_exit(
mut exit_events: MessageReader<AppExit>, mut exit_events: MessageReader<AppExit>,
provider: Res<SyncProviderResource>, provider: Res<SyncProviderResource>,
@@ -292,16 +301,17 @@ fn push_on_exit(
exit_events.clear(); exit_events.clear();
let payload = build_payload(&stats.0, &achievements.0, &progress.0); let payload = build_payload(&stats.0, &achievements.0, &progress.0);
let provider = provider.0.clone(); let result = rt
let rt = rt.0.clone(); .0
AsyncComputeTaskPool::get() .block_on(async { tokio::time::timeout(EXIT_PUSH_TIMEOUT, provider.0.push(&payload)).await });
.spawn(async move { match result {
match rt.block_on(provider.push(&payload)) { Ok(Ok(_)) | Ok(Err(SyncError::UnsupportedPlatform)) => {}
Ok(_) | Err(SyncError::UnsupportedPlatform) => {} Ok(Err(e)) => warn!("sync push on exit failed: {e}"),
Err(e) => warn!("sync push on exit failed: {e}"), Err(_) => warn!(
} "sync push on exit timed out after {}s; will sync on next launch",
}) EXIT_PUSH_TIMEOUT.as_secs()
.detach(); ),
}
} }
/// Update-schedule system: on each `GameWonEvent` push the just-completed /// Update-schedule system: on each `GameWonEvent` push the just-completed