mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
fix(multiplayer): defer reconnect recovery during mutations
This commit is contained in:
@@ -466,10 +466,14 @@ func _set_websocket_status(status: String) -> void:
|
||||
websocket_status_changed.emit(status)
|
||||
if status == "CONNECTED":
|
||||
if not state.ticket_id.is_empty() and state.phase not in [MatchmakingState.CANCELLED, MatchmakingState.EXPIRED, MatchmakingState.FAILED, MatchmakingState.LIVE]:
|
||||
if not state.proposal_id.is_empty():
|
||||
recover_proposal(state.proposal_id)
|
||||
var resource_id := state.proposal_id if not state.proposal_id.is_empty() else state.ticket_id
|
||||
if _operation.is_empty():
|
||||
_run_resync(resource_id)
|
||||
else:
|
||||
recover_queue(state.ticket_id)
|
||||
# A reconnect must not lose its authoritative recovery merely because
|
||||
# the previous mutation has not acknowledged yet. The deferred path
|
||||
# runs after that request completes and avoids an ERR_BUSY drop.
|
||||
_pending_resync_resource_id = resource_id
|
||||
|
||||
|
||||
func _idempotency_key(prefix: String) -> String:
|
||||
|
||||
@@ -55,6 +55,16 @@ func test_websocket_event_validation_requires_contract_specific_fields() -> void
|
||||
assert_true(not ControlPlaneClient._valid_websocket_event(assignment), "incomplete assignment event is rejected")
|
||||
|
||||
|
||||
func test_websocket_reconnect_defers_recovery_while_http_mutation_is_in_flight() -> void:
|
||||
var client := ControlPlaneClient.new()
|
||||
client._ready()
|
||||
assert_true(client.state.begin_queue("ticket-reconnect", "casual"), "queue setup succeeds")
|
||||
client._operation = "queue_heartbeat"
|
||||
client._set_websocket_status("CONNECTED")
|
||||
assert_eq(client._pending_resync_resource_id, "ticket-reconnect", "reconnect recovery is retained until the mutation completes")
|
||||
client.free()
|
||||
|
||||
|
||||
func test_retryable_mutation_policy_only_retries_safe_failures() -> void:
|
||||
assert_true(ControlPlaneClient.is_retryable_mutation_response(0), "transport failure is retryable")
|
||||
assert_true(ControlPlaneClient.is_retryable_mutation_response(408), "request timeout is retryable")
|
||||
|
||||
@@ -1532,3 +1532,5 @@ The client matchmaking projection now preserves the server's `enqueued_at` times
|
||||
The ranked profile projection now also carries the active season's authoritative end timestamp from PostgreSQL through the API and Godot client. Ranked matchmaking displays a bounded days-remaining countdown, while providers without an active season remain compatible and omit the countdown.
|
||||
|
||||
The versioned OpenAPI contract now declares the implemented `/profile/ranked` surface and its server-authoritative ranked profile schema, including optional active-season metadata. Contract tests reject omission of this operation, extra response fields, and credential leakage.
|
||||
|
||||
The Godot client now defers reconnect-triggered authoritative recovery when an HTTP mutation is still in flight, closing the `ERR_BUSY` recovery-drop race. An adversarial client test verifies that the active ticket remains queued for recovery rather than silently staying stale.
|
||||
|
||||
Reference in New Issue
Block a user