fix(engine): bounded blocking sync push on exit #138
@@ -272,12 +272,21 @@ fn poll_pull_result(
|
||||
}
|
||||
}
|
||||
|
||||
/// Last-schedule system: starts a best-effort push of the current local state
|
||||
/// on [`AppExit`] without blocking the Bevy main thread.
|
||||
/// Upper bound on how long [`push_on_exit`] may block the closing app.
|
||||
/// 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
|
||||
/// persistence remains the durable source of truth even if the final remote
|
||||
/// push does not complete.
|
||||
/// This deliberately blocks: the previous detached-task version was almost
|
||||
/// always cut short by process teardown, so the final session's push
|
||||
/// 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(
|
||||
mut exit_events: MessageReader<AppExit>,
|
||||
provider: Res<SyncProviderResource>,
|
||||
@@ -292,16 +301,17 @@ fn push_on_exit(
|
||||
exit_events.clear();
|
||||
|
||||
let payload = build_payload(&stats.0, &achievements.0, &progress.0);
|
||||
let provider = provider.0.clone();
|
||||
let rt = rt.0.clone();
|
||||
AsyncComputeTaskPool::get()
|
||||
.spawn(async move {
|
||||
match rt.block_on(provider.push(&payload)) {
|
||||
Ok(_) | Err(SyncError::UnsupportedPlatform) => {}
|
||||
Err(e) => warn!("sync push on exit failed: {e}"),
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
let result = rt
|
||||
.0
|
||||
.block_on(async { tokio::time::timeout(EXIT_PUSH_TIMEOUT, provider.0.push(&payload)).await });
|
||||
match result {
|
||||
Ok(Ok(_)) | Ok(Err(SyncError::UnsupportedPlatform)) => {}
|
||||
Ok(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()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update-schedule system: on each `GameWonEvent` push the just-completed
|
||||
|
||||
Reference in New Issue
Block a user