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
|
/// 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
|
||||||
|
|||||||
Reference in New Issue
Block a user