Skip to main content

steel_core/server/
packet_processor.rs

1//! Serverbound gameplay packet scheduling between game ticks.
2
3use std::{
4    cmp::Reverse,
5    collections::{BinaryHeap, VecDeque},
6    hash::Hash,
7    mem,
8    sync::Arc,
9};
10
11use crate::{
12    entity::Entity,
13    player::{
14        Player, PlayerSession, PlayerSessionId,
15        connection::NetworkConnection,
16        connection::{ScheduledPacketExecution, ScheduledPlayPacket},
17    },
18};
19use parking_lot::Condvar;
20use rustc_hash::{FxHashMap, FxHashSet};
21use steel_utils::{locks::SyncMutex, translations};
22use tokio::sync::Notify;
23use tokio::task::yield_now;
24
25use super::Server;
26
27// Per-session count limits bound tick-drain work; byte limits bound retained decoded payloads.
28// Vanilla's optional connection rate limit is disabled by default. This higher safety ceiling is
29// intended to catch unbounded backlogs without treating ordinary traffic during a long tick as spam.
30const MAX_OUTSTANDING_PACKETS_PER_PLAYER: usize = 8_192;
31const MAX_OUTSTANDING_BYTES_PER_PLAYER: usize = 32 * 1024 * 1024;
32// Approximate fixed queue, lane, and scheduling-index storage per admitted packet.
33const PACKET_ADMISSION_OVERHEAD: usize = 256;
34
35#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
36struct PlayerPacketLaneKey(PlayerSessionId);
37
38impl PlayerPacketLaneKey {
39    const fn new(session: &PlayerSession) -> Self {
40        Self(session.id())
41    }
42}
43
44/// Exact ownership of one paused player-session packet lane.
45#[derive(Clone, Copy)]
46pub(crate) struct PlayerPacketTransition(PacketLanePause<PlayerPacketLaneKey>);
47
48struct PendingPlayPacket {
49    session: Arc<PlayerSession>,
50    packet: ScheduledPlayPacket,
51}
52
53/// Gameplay packets submitted by network tasks.
54///
55/// The processor runs while the game tick is idle. At each tick boundary it drains every packet
56/// submitted before that boundary, while retaining later submissions for the next packet phase.
57/// A player-replacement transition temporarily withdraws that session's queued packets from the
58/// runnable global order and re-admits them in session FIFO order after the replacement is bound.
59pub(super) struct PacketProcessor {
60    queued: PacketQueue<PlayerPacketLaneKey, PendingPlayPacket>,
61}
62
63impl PacketProcessor {
64    pub(super) fn new() -> Self {
65        Self {
66            queued: PacketQueue::new(),
67        }
68    }
69
70    pub(super) fn schedule(
71        &self,
72        player: Arc<Player>,
73        packet: ScheduledPlayPacket,
74        payload_bytes: usize,
75    ) {
76        let player_id = player.gameprofile.id();
77        let lane_key = PlayerPacketLaneKey::new(&player.session);
78        if player.connection.closed() {
79            self.queued.discard_lane(lane_key);
80            return;
81        }
82
83        let execution = packet.execution();
84        let admission_bytes = payload_bytes.saturating_add(PACKET_ADMISSION_OVERHEAD);
85        let result = self.queued.try_submit(
86            lane_key,
87            execution,
88            admission_bytes,
89            PendingPlayPacket {
90                session: Arc::clone(&player.session),
91                packet,
92            },
93        );
94        let Err(error) = result else {
95            return;
96        };
97        if error == PacketAdmissionError::Stopped {
98            return;
99        }
100
101        tracing::warn!(
102            player_id = %player_id,
103            entity_id = player.id(),
104            ?error,
105            "Disconnecting player after inbound packet admission limit"
106        );
107        player.disconnect(translations::DISCONNECT_EXCEEDED_PACKET_RATE.msg());
108        self.queued.discard_lane(lane_key);
109    }
110
111    /// Runs the blocking packet worker until the processor is stopped.
112    pub(super) fn run(&self, server: &Arc<Server>) {
113        while let Some(mut work) = self.queued.next() {
114            let Some(pending) = work.take() else {
115                continue;
116            };
117            let Some(player) = pending.session.current_player() else {
118                continue;
119            };
120            if !Self::packet_is_runnable(
121                &player,
122                &pending.packet,
123                server.cancel_token.is_cancelled(),
124            ) {
125                continue;
126            }
127
128            pending.packet.handle(player, server);
129        }
130    }
131
132    /// Suspends one connection's FIFO lane while its player entity is replaced.
133    pub(super) fn pause_player_session(
134        &self,
135        session: &PlayerSession,
136    ) -> Option<PlayerPacketTransition> {
137        self.queued
138            .pause_lane(PlayerPacketLaneKey::new(session))
139            .map(PlayerPacketTransition)
140    }
141
142    /// Re-admits packets retained during player replacement into the runnable global order.
143    pub(super) fn resume_player_session(&self, transition: PlayerPacketTransition) -> bool {
144        self.queued.resume_lane(transition.0)
145    }
146
147    /// Drops all retained packets for a connection that can no longer own a player.
148    pub(super) fn discard_player_session(&self, session: &PlayerSession) {
149        self.queued.discard_lane(PlayerPacketLaneKey::new(session));
150    }
151
152    fn packet_is_runnable(
153        player: &Player,
154        packet: &ScheduledPlayPacket,
155        server_cancelled: bool,
156    ) -> bool {
157        !player.connection.closed()
158            && !server_cancelled
159            && player.gate_domain_switch_packet(
160                packet.is_domain_handshake_packet(),
161                packet.is_perform_respawn(),
162            )
163    }
164
165    /// Opens the inter-tick packet phase and wakes the worker.
166    pub(super) fn open_after_tick(&self) {
167        self.queued.open();
168    }
169
170    /// Guarantees packet progress when a late tick leaves no normal inter-tick window.
171    pub(super) async fn wait_for_overload_progress(&self) {
172        let Some(completed) = self.queued.progress_baseline() else {
173            return;
174        };
175        yield_now().await;
176        self.queued.wait_for_progress_since(completed).await;
177    }
178
179    /// Drains packets submitted before this tick boundary, then closes packet admission.
180    pub(super) async fn close_for_tick(&self) {
181        self.queued.drain_for_tick().await;
182    }
183
184    /// Stops the packet worker and discards queued work during server shutdown.
185    pub(super) fn stop(&self) {
186        self.queued.stop();
187    }
188}
189
190impl Default for PacketProcessor {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196#[derive(Clone, Copy, Eq, PartialEq)]
197enum PacketPhase {
198    Closed,
199    Open,
200    Draining(u64),
201    Stopped,
202}
203
204struct SequencedPacket<T> {
205    sequence: u64,
206    execution: ScheduledPacketExecution,
207    admission_bytes: usize,
208    value: T,
209}
210
211struct DeferredPacket<T> {
212    execution: ScheduledPacketExecution,
213    admission_bytes: usize,
214    value: T,
215}
216
217struct PacketLane<T> {
218    queued: VecDeque<SequencedPacket<T>>,
219    deferred: VecDeque<DeferredPacket<T>>,
220    active: bool,
221    pause_generation: Option<u64>,
222    outstanding_packets: usize,
223    outstanding_bytes: usize,
224}
225
226impl<T> PacketLane<T> {
227    const fn new() -> Self {
228        Self {
229            queued: VecDeque::new(),
230            deferred: VecDeque::new(),
231            active: false,
232            pause_generation: None,
233            outstanding_packets: 0,
234            outstanding_bytes: 0,
235        }
236    }
237}
238
239#[derive(Clone, Copy)]
240struct PacketAdmissionLimits {
241    per_player_packets: usize,
242    per_player_bytes: usize,
243}
244
245impl PacketAdmissionLimits {
246    const PRODUCTION: Self = Self {
247        per_player_packets: MAX_OUTSTANDING_PACKETS_PER_PLAYER,
248        per_player_bytes: MAX_OUTSTANDING_BYTES_PER_PLAYER,
249    };
250}
251
252#[derive(Clone, Copy, Debug, Eq, PartialEq)]
253enum PacketAdmissionError {
254    Stopped,
255    PlayerPacketLimit,
256    PlayerByteLimit,
257}
258
259struct PacketQueueState<K, T> {
260    phase: PacketPhase,
261    lanes: FxHashMap<K, PacketLane<T>>,
262    player_local_ready: BinaryHeap<Reverse<(u64, K)>>,
263    serialized_ready: BinaryHeap<Reverse<(u64, K)>>,
264    exclusive_ready: BinaryHeap<Reverse<(u64, K)>>,
265    serialized: BinaryHeap<Reverse<u64>>,
266    exclusive: BinaryHeap<Reverse<u64>>,
267    next_sequence: u64,
268    next_pause_generation: u64,
269    active: usize,
270    serialized_active: bool,
271    exclusive_active: bool,
272    completed: u64,
273}
274
275/// Bounded ordered multi-producer lanes with a finite snapshot drain at each tick boundary.
276struct PacketQueue<K, T> {
277    state: SyncMutex<PacketQueueState<K, T>>,
278    limits: PacketAdmissionLimits,
279    work_available: Condvar,
280    idle: Notify,
281    progress: Notify,
282}
283
284#[derive(Clone, Copy)]
285struct PacketLanePause<K> {
286    key: K,
287    generation: u64,
288}
289
290impl<K, T> PacketQueue<K, T>
291where
292    K: Copy + Eq + Hash + Ord,
293{
294    fn new() -> Self {
295        Self::with_limits(PacketAdmissionLimits::PRODUCTION)
296    }
297
298    fn with_limits(limits: PacketAdmissionLimits) -> Self {
299        Self {
300            state: SyncMutex::new(PacketQueueState {
301                phase: PacketPhase::Closed,
302                lanes: FxHashMap::default(),
303                player_local_ready: BinaryHeap::new(),
304                serialized_ready: BinaryHeap::new(),
305                exclusive_ready: BinaryHeap::new(),
306                serialized: BinaryHeap::new(),
307                exclusive: BinaryHeap::new(),
308                next_sequence: 0,
309                next_pause_generation: 0,
310                active: 0,
311                serialized_active: false,
312                exclusive_active: false,
313                completed: 0,
314            }),
315            limits,
316            work_available: Condvar::new(),
317            idle: Notify::new(),
318            progress: Notify::new(),
319        }
320    }
321
322    fn try_submit(
323        &self,
324        key: K,
325        execution: ScheduledPacketExecution,
326        admission_bytes: usize,
327        value: T,
328    ) -> Result<(), PacketAdmissionError> {
329        let mut state = self.state.lock();
330        if state.phase == PacketPhase::Stopped {
331            return Err(PacketAdmissionError::Stopped);
332        }
333
334        let (player_packets, player_bytes) = state
335            .lanes
336            .get(&key)
337            .map_or_default(|lane| (lane.outstanding_packets, lane.outstanding_bytes));
338        if player_packets >= self.limits.per_player_packets {
339            return Err(PacketAdmissionError::PlayerPacketLimit);
340        }
341        if admission_bytes > self.limits.per_player_bytes.saturating_sub(player_bytes) {
342            return Err(PacketAdmissionError::PlayerByteLimit);
343        }
344        let paused = state
345            .lanes
346            .get(&key)
347            .is_some_and(|lane| lane.pause_generation.is_some());
348        if paused {
349            let lane = state.lanes.entry(key).or_insert_with(PacketLane::new);
350            lane.outstanding_packets += 1;
351            lane.outstanding_bytes += admission_bytes;
352            lane.deferred.push_back(DeferredPacket {
353                execution,
354                admission_bytes,
355                value,
356            });
357            return Ok(());
358        }
359
360        let sequence = state.next_sequence;
361        assert!(sequence != u64::MAX, "packet submission sequence exhausted");
362        state.next_sequence = sequence + 1;
363
364        let lane = state.lanes.entry(key).or_insert_with(PacketLane::new);
365        let became_ready = !lane.active && lane.queued.is_empty();
366        lane.outstanding_packets += 1;
367        lane.outstanding_bytes += admission_bytes;
368        lane.queued.push_back(SequencedPacket {
369            sequence,
370            execution,
371            admission_bytes,
372            value,
373        });
374        if became_ready {
375            Self::mark_ready(&mut state, sequence, key, execution);
376        }
377        Self::mark_global_order(&mut state, sequence, execution);
378        let should_wake = became_ready && state.phase == PacketPhase::Open;
379        drop(state);
380        if should_wake {
381            self.work_available.notify_one();
382        }
383        Ok(())
384    }
385
386    #[cfg(test)]
387    fn submit(&self, key: K, execution: ScheduledPacketExecution, value: T) {
388        let result = self.try_submit(key, execution, 1, value);
389        assert!(
390            matches!(result, Ok(()) | Err(PacketAdmissionError::Stopped)),
391            "test packet exceeded admission limits: {result:?}"
392        );
393    }
394
395    fn open(&self) {
396        let mut state = self.state.lock();
397        if state.phase == PacketPhase::Stopped {
398            return;
399        }
400        state.phase = PacketPhase::Open;
401        let should_wake = Self::select_next(&state, None).is_some();
402        drop(state);
403        if should_wake {
404            self.work_available.notify_all();
405        }
406    }
407
408    #[cfg(test)]
409    fn close(&self) {
410        let mut state = self.state.lock();
411        if state.phase != PacketPhase::Stopped {
412            state.phase = PacketPhase::Closed;
413        }
414    }
415
416    async fn drain_for_tick(&self) {
417        let Some((before_sequence, should_wake)) = self.begin_tick_drain() else {
418            return;
419        };
420        if should_wake {
421            self.work_available.notify_all();
422        }
423
424        loop {
425            let idle = self.idle.notified();
426            if self.tick_drain_complete(before_sequence) {
427                break;
428            }
429            idle.await;
430        }
431
432        self.finish_tick_drain(before_sequence);
433    }
434
435    fn finish_tick_drain(&self, before_sequence: u64) {
436        let mut state = self.state.lock();
437        if state.phase == PacketPhase::Draining(before_sequence) {
438            state.phase = PacketPhase::Closed;
439        }
440    }
441
442    fn begin_tick_drain(&self) -> Option<(u64, bool)> {
443        let mut state = self.state.lock();
444        let before_sequence = match state.phase {
445            PacketPhase::Stopped => None,
446            PacketPhase::Draining(before_sequence) => Some(before_sequence),
447            PacketPhase::Closed | PacketPhase::Open => {
448                let before_sequence = state.next_sequence;
449                state.phase = PacketPhase::Draining(before_sequence);
450                Some(before_sequence)
451            }
452        }?;
453        let should_wake = Self::select_next(&state, Some(before_sequence)).is_some();
454        Some((before_sequence, should_wake))
455    }
456
457    fn tick_drain_complete(&self, before_sequence: u64) -> bool {
458        let state = self.state.lock();
459        if state.phase == PacketPhase::Stopped {
460            return true;
461        }
462        if state.active != 0 {
463            return false;
464        }
465        Self::next_ready_sequence(&state).is_none_or(|sequence| sequence >= before_sequence)
466    }
467
468    async fn wait_for_progress_since(&self, completed: u64) {
469        loop {
470            let progress = self.progress.notified();
471            if self.has_progress_since(completed) {
472                return;
473            }
474            progress.await;
475        }
476    }
477
478    fn progress_baseline(&self) -> Option<u64> {
479        let state = self.state.lock();
480        Self::has_work(&state).then_some(state.completed)
481    }
482
483    fn has_progress_since(&self, completed: u64) -> bool {
484        let state = self.state.lock();
485        state.completed != completed
486            || !Self::has_work(&state)
487            || state.phase == PacketPhase::Stopped
488    }
489
490    fn has_work(state: &PacketQueueState<K, T>) -> bool {
491        state.active != 0
492            || state
493                .lanes
494                .values()
495                .any(|lane| lane.pause_generation.is_none() && !lane.queued.is_empty())
496    }
497
498    fn pause_lane(&self, key: K) -> Option<PacketLanePause<K>> {
499        let mut state = self.state.lock();
500        if state.phase == PacketPhase::Stopped {
501            return None;
502        }
503        if state
504            .lanes
505            .get(&key)
506            .is_some_and(|lane| lane.pause_generation.is_some())
507        {
508            return None;
509        }
510
511        let generation = state.next_pause_generation;
512        assert!(
513            generation != u64::MAX,
514            "packet lane pause generation exhausted"
515        );
516        state.next_pause_generation = generation + 1;
517
518        let queued = {
519            let lane = state.lanes.entry(key).or_insert_with(PacketLane::new);
520            lane.pause_generation = Some(generation);
521            lane.queued.drain(..).collect::<Vec<_>>()
522        };
523
524        state.player_local_ready.retain(|entry| entry.0.1 != key);
525        state.serialized_ready.retain(|entry| entry.0.1 != key);
526        state.exclusive_ready.retain(|entry| entry.0.1 != key);
527        let queued_sequences = queued
528            .iter()
529            .map(|packet| packet.sequence)
530            .collect::<FxHashSet<_>>();
531        state
532            .serialized
533            .retain(|entry| !queued_sequences.contains(&entry.0));
534        state
535            .exclusive
536            .retain(|entry| !queued_sequences.contains(&entry.0));
537        let Some(lane) = state.lanes.get_mut(&key) else {
538            panic!("paused packet lane disappeared while deferring its packets");
539        };
540        lane.deferred
541            .extend(queued.into_iter().map(|packet| DeferredPacket {
542                execution: packet.execution,
543                admission_bytes: packet.admission_bytes,
544                value: packet.value,
545            }));
546
547        let is_idle = state.active == 0;
548        let should_wake = matches!(state.phase, PacketPhase::Open | PacketPhase::Draining(_))
549            && Self::next_ready_sequence(&state).is_some();
550        drop(state);
551        self.progress.notify_one();
552        if should_wake {
553            self.work_available.notify_all();
554        }
555        if is_idle {
556            self.idle.notify_one();
557        }
558        Some(PacketLanePause { key, generation })
559    }
560
561    fn resume_lane(&self, pause: PacketLanePause<K>) -> bool {
562        let mut state = self.state.lock();
563        if state.phase == PacketPhase::Stopped {
564            return false;
565        }
566
567        let Some(lane) = state.lanes.get_mut(&pause.key) else {
568            return false;
569        };
570        if lane.pause_generation != Some(pause.generation) {
571            return false;
572        }
573        lane.pause_generation = None;
574        let active = lane.active;
575        let deferred = mem::take(&mut lane.deferred);
576
577        let Ok(deferred_count) = u64::try_from(deferred.len()) else {
578            panic!("deferred packet count does not fit in the sequence space");
579        };
580        let first_sequence = state.next_sequence;
581        let Some(next_sequence) = first_sequence.checked_add(deferred_count) else {
582            panic!("packet submission sequence exhausted while resuming a lane");
583        };
584        state.next_sequence = next_sequence;
585
586        // Deferred packets deliberately receive fresh sequence numbers. Keeping their original
587        // numbers would let a resumed serialized/exclusive packet jump ahead of unrelated global
588        // work that was allowed to run while this lane was paused.
589        let mut queued = VecDeque::with_capacity(deferred.len());
590        for (sequence, packet) in (first_sequence..next_sequence).zip(deferred) {
591            Self::mark_global_order(&mut state, sequence, packet.execution);
592            queued.push_back(SequencedPacket {
593                sequence,
594                execution: packet.execution,
595                admission_bytes: packet.admission_bytes,
596                value: packet.value,
597            });
598        }
599        let first = queued
600            .front()
601            .map(|packet| (packet.sequence, packet.execution));
602        let Some(lane) = state.lanes.get_mut(&pause.key) else {
603            panic!("resumed packet lane disappeared before packet re-admission");
604        };
605        lane.queued.extend(queued);
606        if !active && let Some((sequence, execution)) = first {
607            Self::mark_ready(&mut state, sequence, pause.key, execution);
608        }
609        if !active && first.is_none() {
610            state.lanes.remove(&pause.key);
611        }
612
613        let should_wake = matches!(state.phase, PacketPhase::Open | PacketPhase::Draining(_))
614            && Self::next_ready_sequence(&state).is_some();
615        drop(state);
616        self.progress.notify_one();
617        if should_wake {
618            self.work_available.notify_all();
619        }
620        true
621    }
622
623    fn discard_lane(&self, key: K) {
624        let mut state = self.state.lock();
625        let (discarded_sequences, remove_lane) = {
626            let Some(lane) = state.lanes.get_mut(&key) else {
627                return;
628            };
629            let discarded_packets = lane.queued.len() + lane.deferred.len();
630            let discarded_bytes = lane
631                .queued
632                .iter()
633                .map(|packet| packet.admission_bytes)
634                .chain(lane.deferred.iter().map(|packet| packet.admission_bytes))
635                .sum::<usize>();
636            let discarded_sequences = lane
637                .queued
638                .iter()
639                .map(|packet| packet.sequence)
640                .collect::<FxHashSet<_>>();
641            lane.queued.clear();
642            lane.deferred.clear();
643            lane.pause_generation = None;
644            assert!(
645                lane.outstanding_packets >= discarded_packets,
646                "session packet admission accounting underflow while discarding"
647            );
648            lane.outstanding_packets -= discarded_packets;
649            assert!(
650                lane.outstanding_bytes >= discarded_bytes,
651                "session byte admission accounting underflow while discarding"
652            );
653            lane.outstanding_bytes -= discarded_bytes;
654            (discarded_sequences, !lane.active)
655        };
656
657        state.player_local_ready.retain(|entry| entry.0.1 != key);
658        state.serialized_ready.retain(|entry| entry.0.1 != key);
659        state.exclusive_ready.retain(|entry| entry.0.1 != key);
660        state
661            .serialized
662            .retain(|entry| !discarded_sequences.contains(&entry.0));
663        state
664            .exclusive
665            .retain(|entry| !discarded_sequences.contains(&entry.0));
666        if remove_lane {
667            state.lanes.remove(&key);
668        }
669
670        let is_idle = state.active == 0;
671        let should_wake = matches!(state.phase, PacketPhase::Open | PacketPhase::Draining(_))
672            && Self::next_ready_sequence(&state).is_some();
673        drop(state);
674        self.progress.notify_one();
675        if should_wake {
676            self.work_available.notify_all();
677        }
678        if is_idle {
679            self.idle.notify_one();
680        }
681    }
682
683    fn stop(&self) {
684        let mut state = self.state.lock();
685        state.phase = PacketPhase::Stopped;
686        state.player_local_ready.clear();
687        state.serialized_ready.clear();
688        state.exclusive_ready.clear();
689        state.serialized.clear();
690        state.exclusive.clear();
691        state.lanes.retain(|_, lane| {
692            let lane_discarded_packets = lane.queued.len() + lane.deferred.len();
693            let lane_discarded_bytes = lane
694                .queued
695                .iter()
696                .map(|packet| packet.admission_bytes)
697                .chain(lane.deferred.iter().map(|packet| packet.admission_bytes))
698                .sum::<usize>();
699            lane.queued.clear();
700            lane.deferred.clear();
701            assert!(
702                lane.outstanding_packets >= lane_discarded_packets,
703                "session packet admission accounting underflow while stopping"
704            );
705            lane.outstanding_packets -= lane_discarded_packets;
706            assert!(
707                lane.outstanding_bytes >= lane_discarded_bytes,
708                "session byte admission accounting underflow while stopping"
709            );
710            lane.outstanding_bytes -= lane_discarded_bytes;
711            lane.pause_generation = None;
712            lane.active
713        });
714        let is_idle = state.active == 0;
715        drop(state);
716        self.work_available.notify_all();
717        self.progress.notify_waiters();
718        if is_idle {
719            self.idle.notify_one();
720        }
721    }
722
723    fn next(&self) -> Option<PacketWork<'_, K, T>> {
724        let mut state = self.state.lock();
725        loop {
726            match state.phase {
727                PacketPhase::Stopped => return None,
728                PacketPhase::Open => {
729                    if let Some((key, execution, admission_bytes, value)) =
730                        Self::start_next(&mut state, None)
731                    {
732                        state.active += 1;
733                        drop(state);
734                        return Some(PacketWork {
735                            value: Some(value),
736                            key,
737                            execution,
738                            admission_bytes,
739                            queue: self,
740                        });
741                    }
742                }
743                PacketPhase::Draining(before_sequence) => {
744                    if let Some((key, execution, admission_bytes, value)) =
745                        Self::start_next(&mut state, Some(before_sequence))
746                    {
747                        state.active += 1;
748                        drop(state);
749                        return Some(PacketWork {
750                            value: Some(value),
751                            key,
752                            execution,
753                            admission_bytes,
754                            queue: self,
755                        });
756                    }
757                }
758                PacketPhase::Closed => {}
759            }
760            self.work_available.wait(&mut state);
761        }
762    }
763
764    #[cfg(test)]
765    fn try_next(&self) -> Option<PacketWork<'_, K, T>> {
766        let mut state = self.state.lock();
767        let before_sequence = match state.phase {
768            PacketPhase::Open => None,
769            PacketPhase::Draining(before_sequence) => Some(before_sequence),
770            PacketPhase::Closed | PacketPhase::Stopped => return None,
771        };
772        let (key, execution, admission_bytes, value) =
773            Self::start_next(&mut state, before_sequence)?;
774        state.active += 1;
775        drop(state);
776        Some(PacketWork {
777            value: Some(value),
778            key,
779            execution,
780            admission_bytes,
781            queue: self,
782        })
783    }
784
785    fn start_next(
786        state: &mut PacketQueueState<K, T>,
787        before_sequence: Option<u64>,
788    ) -> Option<(K, ScheduledPacketExecution, usize, T)> {
789        let (ready_sequence, key, execution) = Self::select_next(state, before_sequence)?;
790        let Some(lane) = state.lanes.get(&key) else {
791            panic!("ready packet lane disappeared before starting");
792        };
793        assert!(!lane.active, "ready packet lane is already active");
794        let Some(packet) = lane.queued.front() else {
795            panic!("ready packet lane has no queued packet");
796        };
797        assert_eq!(
798            ready_sequence, packet.sequence,
799            "ready packet sequence does not match lane front"
800        );
801        assert_eq!(
802            execution, packet.execution,
803            "packet is registered in the wrong ready queue"
804        );
805
806        match execution {
807            ScheduledPacketExecution::PlayerLocal => {
808                assert!(
809                    state.player_local_ready.pop() == Some(Reverse((ready_sequence, key))),
810                    "player-local ready queue changed while the queue lock was held"
811                );
812            }
813            ScheduledPacketExecution::Serialized => {
814                assert!(
815                    state.serialized_ready.pop() == Some(Reverse((ready_sequence, key))),
816                    "serialized ready queue changed while the queue lock was held"
817                );
818                assert_eq!(
819                    state.serialized.pop(),
820                    Some(Reverse(ready_sequence)),
821                    "serialized packet order changed while the queue lock was held"
822                );
823                assert!(
824                    !state.serialized_active,
825                    "serialized packet started while another was active"
826                );
827                state.serialized_active = true;
828            }
829            ScheduledPacketExecution::Exclusive => {
830                assert!(
831                    state.exclusive_ready.pop() == Some(Reverse((ready_sequence, key))),
832                    "exclusive ready queue changed while the queue lock was held"
833                );
834                assert_eq!(
835                    state.exclusive.pop(),
836                    Some(Reverse(ready_sequence)),
837                    "exclusive packet barrier changed while the queue lock was held"
838                );
839                state.exclusive_active = true;
840            }
841        }
842
843        let Some(lane) = state.lanes.get_mut(&key) else {
844            panic!("ready packet lane disappeared before removal");
845        };
846        let Some(packet) = lane.queued.pop_front() else {
847            panic!("ready packet lane has no queued packet during removal");
848        };
849        lane.active = true;
850        Some((key, execution, packet.admission_bytes, packet.value))
851    }
852
853    fn select_next(
854        state: &PacketQueueState<K, T>,
855        before_sequence: Option<u64>,
856    ) -> Option<(u64, K, ScheduledPacketExecution)> {
857        if state.exclusive_active {
858            return None;
859        }
860
861        let next_exclusive = state.exclusive.peek().map(|entry| entry.0);
862        let next_serialized = state.serialized.peek().map(|entry| entry.0);
863        let mut selected = None;
864        let mut consider = |entry: Option<&Reverse<(u64, K)>>,
865                            execution: ScheduledPacketExecution| {
866            let Some(Reverse((sequence, key))) = entry.copied() else {
867                return;
868            };
869            if before_sequence.is_some_and(|cutoff| sequence >= cutoff)
870                || next_exclusive.is_some_and(|exclusive| exclusive < sequence)
871            {
872                return;
873            }
874            if selected.is_none_or(|(selected_sequence, _, _)| sequence < selected_sequence) {
875                selected = Some((sequence, key, execution));
876            }
877        };
878
879        consider(
880            state.player_local_ready.peek(),
881            ScheduledPacketExecution::PlayerLocal,
882        );
883        if !state.serialized_active
884            && state
885                .serialized_ready
886                .peek()
887                .is_some_and(|entry| Some(entry.0.0) == next_serialized)
888        {
889            consider(
890                state.serialized_ready.peek(),
891                ScheduledPacketExecution::Serialized,
892            );
893        }
894        if state.active == 0
895            && state
896                .exclusive_ready
897                .peek()
898                .is_some_and(|entry| Some(entry.0.0) == next_exclusive)
899        {
900            consider(
901                state.exclusive_ready.peek(),
902                ScheduledPacketExecution::Exclusive,
903            );
904        }
905        selected
906    }
907
908    fn finish_one(&self, key: K, execution: ScheduledPacketExecution, admission_bytes: usize) {
909        let mut state = self.state.lock();
910        assert!(state.active > 0, "packet work accounting underflow");
911        state.active -= 1;
912        state.completed = state.completed.wrapping_add(1);
913        match execution {
914            ScheduledPacketExecution::PlayerLocal => {}
915            ScheduledPacketExecution::Serialized => {
916                assert!(state.serialized_active, "serialized packet is not active");
917                state.serialized_active = false;
918            }
919            ScheduledPacketExecution::Exclusive => {
920                assert!(state.exclusive_active, "exclusive packet is not active");
921                state.exclusive_active = false;
922            }
923        }
924
925        let (next_packet, paused) = {
926            let Some(lane) = state.lanes.get_mut(&key) else {
927                panic!("active packet lane disappeared before completion");
928            };
929            assert!(lane.active, "completed packet lane is not active");
930            lane.active = false;
931            assert!(
932                lane.outstanding_packets > 0,
933                "session packet admission accounting underflow on completion"
934            );
935            lane.outstanding_packets -= 1;
936            assert!(
937                lane.outstanding_bytes >= admission_bytes,
938                "session byte admission accounting underflow on completion"
939            );
940            lane.outstanding_bytes -= admission_bytes;
941            (
942                lane.queued
943                    .front()
944                    .map(|packet| (packet.sequence, packet.execution)),
945                lane.pause_generation.is_some(),
946            )
947        };
948        if let Some((sequence, next_execution)) = next_packet {
949            if !paused {
950                Self::mark_ready(&mut state, sequence, key, next_execution);
951            }
952        } else if !paused {
953            let Some(lane) = state.lanes.get(&key) else {
954                panic!("completed packet lane disappeared before removal");
955            };
956            assert_eq!(lane.outstanding_packets, 0);
957            assert_eq!(lane.outstanding_bytes, 0);
958            state.lanes.remove(&key);
959        }
960
961        let is_idle = state.active == 0;
962        let should_wake = matches!(state.phase, PacketPhase::Open | PacketPhase::Draining(_))
963            && Self::next_ready_sequence(&state).is_some();
964        drop(state);
965        self.progress.notify_one();
966        if should_wake {
967            match execution {
968                ScheduledPacketExecution::Exclusive => {
969                    self.work_available.notify_all();
970                }
971                ScheduledPacketExecution::PlayerLocal | ScheduledPacketExecution::Serialized => {
972                    self.work_available.notify_one();
973                }
974            }
975        }
976        if is_idle {
977            self.idle.notify_one();
978        }
979    }
980
981    fn mark_ready(
982        state: &mut PacketQueueState<K, T>,
983        sequence: u64,
984        key: K,
985        execution: ScheduledPacketExecution,
986    ) {
987        let entry = Reverse((sequence, key));
988        match execution {
989            ScheduledPacketExecution::PlayerLocal => state.player_local_ready.push(entry),
990            ScheduledPacketExecution::Serialized => state.serialized_ready.push(entry),
991            ScheduledPacketExecution::Exclusive => state.exclusive_ready.push(entry),
992        }
993    }
994
995    fn mark_global_order(
996        state: &mut PacketQueueState<K, T>,
997        sequence: u64,
998        execution: ScheduledPacketExecution,
999    ) {
1000        match execution {
1001            ScheduledPacketExecution::PlayerLocal => {}
1002            ScheduledPacketExecution::Serialized => state.serialized.push(Reverse(sequence)),
1003            ScheduledPacketExecution::Exclusive => state.exclusive.push(Reverse(sequence)),
1004        }
1005    }
1006
1007    fn next_ready_sequence(state: &PacketQueueState<K, T>) -> Option<u64> {
1008        [
1009            state.player_local_ready.peek().map(|entry| entry.0.0),
1010            state.serialized_ready.peek().map(|entry| entry.0.0),
1011            state.exclusive_ready.peek().map(|entry| entry.0.0),
1012        ]
1013        .into_iter()
1014        .flatten()
1015        .min()
1016    }
1017}
1018
1019struct PacketWork<'a, K, T>
1020where
1021    K: Copy + Eq + Hash + Ord,
1022{
1023    value: Option<T>,
1024    key: K,
1025    execution: ScheduledPacketExecution,
1026    admission_bytes: usize,
1027    queue: &'a PacketQueue<K, T>,
1028}
1029
1030impl<K, T> PacketWork<'_, K, T>
1031where
1032    K: Copy + Eq + Hash + Ord,
1033{
1034    const fn take(&mut self) -> Option<T> {
1035        self.value.take()
1036    }
1037}
1038
1039impl<K, T> Drop for PacketWork<'_, K, T>
1040where
1041    K: Copy + Eq + Hash + Ord,
1042{
1043    fn drop(&mut self) {
1044        self.queue
1045            .finish_one(self.key, self.execution, self.admission_bytes);
1046    }
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051    use std::{
1052        sync::{Arc, Weak, mpsc},
1053        thread,
1054        time::Duration,
1055    };
1056
1057    use steel_utils::TICKS_PER_SECOND;
1058    use tokio::time::timeout;
1059
1060    use crate::{
1061        entity::{Entity as _, LivingEntity as _},
1062        player::{ClientInformation, Player, connection::ScheduledPlayPacket},
1063        test_support::{TestPlayerBuilder, fresh_test_world},
1064    };
1065
1066    use super::{
1067        PACKET_ADMISSION_OVERHEAD, PacketAdmissionError, PacketAdmissionLimits, PacketProcessor,
1068        PacketQueue, PendingPlayPacket, ScheduledPacketExecution,
1069    };
1070
1071    const fn limits(per_player_packets: usize, per_player_bytes: usize) -> PacketAdmissionLimits {
1072        PacketAdmissionLimits {
1073            per_player_packets,
1074            per_player_bytes,
1075        }
1076    }
1077
1078    fn replacement_for(player: &Arc<Player>) -> Arc<Player> {
1079        Arc::new(Player::new(
1080            player.gameprofile.clone(),
1081            Arc::clone(&player.connection),
1082            Arc::clone(&player.session),
1083            player.get_world(),
1084            Weak::clone(&player.server),
1085            Arc::clone(&player.config),
1086            player.id(),
1087            ClientInformation::default(),
1088        ))
1089    }
1090
1091    #[test]
1092    fn per_player_packet_limit_rejects_before_assigning_a_sequence() {
1093        let queue = PacketQueue::with_limits(limits(2, 100));
1094        assert_eq!(
1095            queue.try_submit(1, ScheduledPacketExecution::PlayerLocal, 1, "first"),
1096            Ok(())
1097        );
1098        assert_eq!(
1099            queue.try_submit(1, ScheduledPacketExecution::Serialized, 1, "second"),
1100            Ok(())
1101        );
1102
1103        assert_eq!(
1104            queue.try_submit(1, ScheduledPacketExecution::Exclusive, 1, "rejected"),
1105            Err(PacketAdmissionError::PlayerPacketLimit)
1106        );
1107        assert_eq!(queue.state.lock().next_sequence, 2);
1108        assert_eq!(
1109            queue.try_submit(2, ScheduledPacketExecution::PlayerLocal, 1, "other player"),
1110            Ok(())
1111        );
1112    }
1113
1114    #[test]
1115    fn scheduled_respawn_is_retained_if_domain_switch_queues_before_worker_gate() {
1116        let world_fixture = fresh_test_world("scheduled_domain_switch_respawn_packet");
1117        let world = &world_fixture.world;
1118        let player = TestPlayerBuilder::new(Arc::clone(world), "RespawnTester", 1).build();
1119        let packet = ScheduledPlayPacket::perform_respawn_for_test();
1120        let Some(token) = player.begin_pending_world_change() else {
1121            panic!("test player should acquire a world-change token");
1122        };
1123        assert!(player.begin_domain_switch(token));
1124        player.set_health(0.0);
1125
1126        assert!(!PacketProcessor::packet_is_runnable(
1127            &player, &packet, false
1128        ));
1129        assert!(player.has_deferred_death_respawn_for_test());
1130
1131        assert!(player.finish_domain_switch(token));
1132        assert!(player.finish_pending_world_change(token));
1133    }
1134
1135    #[test]
1136    fn queued_packets_resolve_the_player_bound_when_they_start() {
1137        let world_fixture = fresh_test_world("packet_session_replacement_resolution");
1138        let world = &world_fixture.world;
1139        let original = TestPlayerBuilder::new(Arc::clone(world), "Original", 1).build();
1140        let replacement = replacement_for(&original);
1141        let session = Arc::clone(&original.session);
1142        let processor = PacketProcessor::new();
1143        let packet = ScheduledPlayPacket::perform_respawn_for_test();
1144
1145        processor.schedule(Arc::clone(&original), packet, 1);
1146        let Some(transition) = processor.pause_player_session(&session) else {
1147            panic!("session packet lane should pause");
1148        };
1149        assert!(session.replace_player(&original, &replacement));
1150
1151        // The listener may have captured the old player immediately before the bind. Scheduling
1152        // through that stale snapshot must still target the stable session and its replacement.
1153        processor.schedule(original, ScheduledPlayPacket::perform_respawn_for_test(), 1);
1154        assert!(processor.resume_player_session(transition));
1155        processor.queued.open();
1156
1157        for _ in 0..2 {
1158            let Some(mut work) = processor.queued.try_next() else {
1159                panic!("retained session packet should become runnable");
1160            };
1161            let Some(PendingPlayPacket { session, .. }) = work.take() else {
1162                panic!("retained packet should still carry its session");
1163            };
1164            let Some(current) = session.current_player() else {
1165                panic!("replacement should be bound before packet work resumes");
1166            };
1167            assert!(Arc::ptr_eq(&current, &replacement));
1168        }
1169    }
1170
1171    #[test]
1172    fn per_player_byte_limit_is_independent_of_packet_count() {
1173        let queue = PacketQueue::with_limits(limits(10, 6));
1174        assert_eq!(
1175            queue.try_submit(1, ScheduledPacketExecution::PlayerLocal, 4, "first"),
1176            Ok(())
1177        );
1178
1179        assert_eq!(
1180            queue.try_submit(1, ScheduledPacketExecution::PlayerLocal, 3, "rejected"),
1181            Err(PacketAdmissionError::PlayerByteLimit)
1182        );
1183        assert_eq!(
1184            queue.try_submit(2, ScheduledPacketExecution::PlayerLocal, 6, "other player"),
1185            Ok(())
1186        );
1187    }
1188
1189    #[test]
1190    fn production_limit_allows_a_watchdog_window_of_mounted_client_traffic() {
1191        const VANILLA_WATCHDOG_SECONDS: usize = 60;
1192        const MOUNTED_PACKETS_PER_CLIENT_TICK: usize = 4;
1193        const EXPECTED_PACKETS: usize =
1194            VANILLA_WATCHDOG_SECONDS * TICKS_PER_SECOND as usize * MOUNTED_PACKETS_PER_CLIENT_TICK;
1195
1196        let queue = PacketQueue::new();
1197        for packet in 0..EXPECTED_PACKETS {
1198            assert!(
1199                queue
1200                    .try_submit(
1201                        1,
1202                        ScheduledPacketExecution::PlayerLocal,
1203                        PACKET_ADMISSION_OVERHEAD,
1204                        packet,
1205                    )
1206                    .is_ok()
1207            );
1208        }
1209
1210        let state = queue.state.lock();
1211        let lane = state.lanes.get(&1).expect("session lane should exist");
1212        assert_eq!(lane.outstanding_packets, EXPECTED_PACKETS);
1213    }
1214
1215    #[test]
1216    fn independent_session_limits_do_not_accumulate_globally() {
1217        const SESSION_COUNT: usize = 64;
1218        const PACKETS_PER_SESSION: usize = 33;
1219
1220        let queue = PacketQueue::new();
1221        for session_id in 1_u64..=64 {
1222            for packet in 0..PACKETS_PER_SESSION {
1223                assert!(
1224                    queue
1225                        .try_submit(
1226                            session_id,
1227                            ScheduledPacketExecution::PlayerLocal,
1228                            PACKET_ADMISSION_OVERHEAD,
1229                            packet,
1230                        )
1231                        .is_ok()
1232                );
1233            }
1234        }
1235
1236        let state = queue.state.lock();
1237        assert_eq!(state.lanes.len(), SESSION_COUNT);
1238        assert_eq!(
1239            usize::try_from(state.next_sequence),
1240            Ok(SESSION_COUNT * PACKETS_PER_SESSION)
1241        );
1242    }
1243
1244    #[test]
1245    fn discarding_stale_session_keeps_replacement_session_work() {
1246        let stale_key = 1;
1247        let replacement_key = 2;
1248        let queue = PacketQueue::with_limits(limits(10, 100));
1249        queue.submit(
1250            stale_key,
1251            ScheduledPacketExecution::Exclusive,
1252            "stale barrier",
1253        );
1254        queue.submit(
1255            stale_key,
1256            ScheduledPacketExecution::Serialized,
1257            "stale serialized",
1258        );
1259        queue.submit(
1260            replacement_key,
1261            ScheduledPacketExecution::PlayerLocal,
1262            "replacement",
1263        );
1264
1265        queue.discard_lane(stale_key);
1266
1267        {
1268            let queue_state = queue.state.lock();
1269            assert!(!queue_state.lanes.contains_key(&stale_key));
1270            let replacement_lane = queue_state
1271                .lanes
1272                .get(&replacement_key)
1273                .expect("replacement session lane should remain");
1274            assert_eq!(replacement_lane.outstanding_packets, 1);
1275            assert_eq!(replacement_lane.outstanding_bytes, 1);
1276            assert!(queue_state.serialized.is_empty());
1277            assert!(queue_state.exclusive.is_empty());
1278        }
1279
1280        queue.open();
1281        let Some(mut work) = queue.try_next() else {
1282            panic!("replacement session packet should remain runnable");
1283        };
1284        assert_eq!(work.take(), Some("replacement"));
1285    }
1286
1287    #[test]
1288    fn paused_fifo_is_re_admitted_after_unrelated_global_work() {
1289        let queue = PacketQueue::with_limits(limits(10, 100));
1290        queue.submit(
1291            1,
1292            ScheduledPacketExecution::Serialized,
1293            "deferred serialized",
1294        );
1295        queue.submit(1, ScheduledPacketExecution::Exclusive, "deferred exclusive");
1296        let Some(pause) = queue.pause_lane(1) else {
1297            panic!("session lane should pause");
1298        };
1299
1300        queue.submit(
1301            2,
1302            ScheduledPacketExecution::Serialized,
1303            "unrelated serialized",
1304        );
1305        queue.submit(
1306            3,
1307            ScheduledPacketExecution::Exclusive,
1308            "unrelated exclusive",
1309        );
1310        queue.open();
1311
1312        let Some(mut unrelated_serialized) = queue.try_next() else {
1313            panic!("paused serialized work must not block another session");
1314        };
1315        assert_eq!(unrelated_serialized.take(), Some("unrelated serialized"));
1316        drop(unrelated_serialized);
1317
1318        let Some(mut unrelated_exclusive) = queue.try_next() else {
1319            panic!("paused exclusive work must not remain a global barrier");
1320        };
1321        assert_eq!(unrelated_exclusive.take(), Some("unrelated exclusive"));
1322        drop(unrelated_exclusive);
1323        assert!(queue.try_next().is_none());
1324
1325        assert!(queue.resume_lane(pause));
1326        let Some(mut deferred_serialized) = queue.try_next() else {
1327            panic!("resumed FIFO front should become runnable");
1328        };
1329        assert_eq!(deferred_serialized.take(), Some("deferred serialized"));
1330        drop(deferred_serialized);
1331
1332        let Some(mut deferred_exclusive) = queue.try_next() else {
1333            panic!("resumed FIFO tail should follow its front");
1334        };
1335        assert_eq!(deferred_exclusive.take(), Some("deferred exclusive"));
1336    }
1337
1338    #[test]
1339    fn pausing_active_player_session_defers_its_tail_until_exact_resume() {
1340        let world_fixture = fresh_test_world("packet_active_session_pause");
1341        let world = &world_fixture.world;
1342        let player = TestPlayerBuilder::new(Arc::clone(world), "Paused", 1).build();
1343        let unrelated_player = TestPlayerBuilder::new(Arc::clone(world), "Unrelated", 2).build();
1344        let processor = PacketProcessor::new();
1345        processor.schedule(
1346            Arc::clone(&player),
1347            ScheduledPlayPacket::perform_respawn_for_test(),
1348            1,
1349        );
1350        processor.schedule(
1351            Arc::clone(&player),
1352            ScheduledPlayPacket::perform_respawn_for_test(),
1353            2,
1354        );
1355        processor.open_after_tick();
1356
1357        let Some(mut active) = processor.queued.try_next() else {
1358            panic!("respawn packet should be active before its lane pauses");
1359        };
1360        assert_eq!(active.admission_bytes, PACKET_ADMISSION_OVERHEAD + 1);
1361        let Some(PendingPlayPacket {
1362            session: active_session,
1363            ..
1364        }) = active.take()
1365        else {
1366            panic!("active respawn should carry its player session");
1367        };
1368        assert!(Arc::ptr_eq(&active_session, &player.session));
1369
1370        let Some(transition) = processor.pause_player_session(&player.session) else {
1371            panic!("active session lane should pause");
1372        };
1373        processor.schedule(
1374            Arc::clone(&player),
1375            ScheduledPlayPacket::perform_respawn_for_test(),
1376            3,
1377        );
1378        processor.schedule(
1379            Arc::clone(&unrelated_player),
1380            ScheduledPlayPacket::perform_respawn_for_test(),
1381            4,
1382        );
1383
1384        let Some(mut unrelated) = processor.queued.try_next() else {
1385            panic!("paused lane must not block unrelated player work");
1386        };
1387        assert_eq!(unrelated.admission_bytes, PACKET_ADMISSION_OVERHEAD + 4);
1388        let Some(PendingPlayPacket {
1389            session: unrelated_session,
1390            ..
1391        }) = unrelated.take()
1392        else {
1393            panic!("unrelated packet should carry its player session");
1394        };
1395        assert!(Arc::ptr_eq(&unrelated_session, &unrelated_player.session));
1396        drop(unrelated);
1397        assert!(processor.queued.try_next().is_none());
1398
1399        assert!(processor.resume_player_session(transition));
1400        assert!(processor.queued.try_next().is_none());
1401        drop(active);
1402
1403        let Some(mut queued_before_pause) = processor.queued.try_next() else {
1404            panic!("exact pause owner should re-admit the original lane tail");
1405        };
1406        assert_eq!(
1407            queued_before_pause.admission_bytes,
1408            PACKET_ADMISSION_OVERHEAD + 2
1409        );
1410        let Some(PendingPlayPacket {
1411            session: queued_before_pause_session,
1412            ..
1413        }) = queued_before_pause.take()
1414        else {
1415            panic!("packet queued before the pause should retain its session");
1416        };
1417        assert!(Arc::ptr_eq(&queued_before_pause_session, &player.session));
1418        drop(queued_before_pause);
1419
1420        let Some(mut queued_during_pause) = processor.queued.try_next() else {
1421            panic!("packets submitted while paused should retain session FIFO order");
1422        };
1423        assert_eq!(
1424            queued_during_pause.admission_bytes,
1425            PACKET_ADMISSION_OVERHEAD + 3
1426        );
1427        let Some(PendingPlayPacket {
1428            session: queued_during_pause_session,
1429            ..
1430        }) = queued_during_pause.take()
1431        else {
1432            panic!("packet queued during the pause should retain its session");
1433        };
1434        assert!(Arc::ptr_eq(&queued_during_pause_session, &player.session));
1435    }
1436
1437    #[test]
1438    fn stale_pause_token_cannot_resume_a_later_pause() {
1439        let queue = PacketQueue::with_limits(limits(10, 100));
1440        let Some(first_pause) = queue.pause_lane(1) else {
1441            panic!("first lane pause should succeed");
1442        };
1443        assert!(queue.resume_lane(first_pause));
1444
1445        let Some(second_pause) = queue.pause_lane(1) else {
1446            panic!("second lane pause should succeed");
1447        };
1448        queue.submit(1, ScheduledPacketExecution::PlayerLocal, "deferred");
1449        queue.open();
1450
1451        assert!(!queue.resume_lane(first_pause));
1452        assert!(queue.try_next().is_none());
1453        assert!(queue.resume_lane(second_pause));
1454
1455        let Some(mut resumed) = queue.try_next() else {
1456            panic!("current pause owner should resume the lane");
1457        };
1458        assert_eq!(resumed.take(), Some("deferred"));
1459    }
1460
1461    #[tokio::test]
1462    async fn paused_packets_do_not_hold_tick_drain_open() {
1463        let queue = PacketQueue::new();
1464        queue.submit(1, ScheduledPacketExecution::Exclusive, "deferred");
1465        let Some(pause) = queue.pause_lane(1) else {
1466            panic!("session lane should pause");
1467        };
1468        queue.open();
1469
1470        assert!(
1471            timeout(Duration::from_secs(1), queue.drain_for_tick())
1472                .await
1473                .is_ok()
1474        );
1475        assert!(queue.resume_lane(pause));
1476        queue.open();
1477
1478        let Some(mut resumed) = queue.try_next() else {
1479            panic!("deferred packet should remain retained after the tick drain");
1480        };
1481        assert_eq!(resumed.take(), Some("deferred"));
1482    }
1483
1484    #[test]
1485    fn active_packet_remains_charged_until_completion() {
1486        let queue = PacketQueue::with_limits(limits(1, 4));
1487        assert_eq!(
1488            queue.try_submit(1, ScheduledPacketExecution::PlayerLocal, 4, "active"),
1489            Ok(())
1490        );
1491        queue.open();
1492        let Some(work) = queue.try_next() else {
1493            panic!("accepted packet should start");
1494        };
1495
1496        assert_eq!(
1497            queue.try_submit(1, ScheduledPacketExecution::PlayerLocal, 1, "rejected"),
1498            Err(PacketAdmissionError::PlayerPacketLimit)
1499        );
1500        {
1501            let state = queue.state.lock();
1502            let lane = state.lanes.get(&1).expect("active lane should remain");
1503            assert_eq!(lane.outstanding_packets, 1);
1504            assert_eq!(lane.outstanding_bytes, 4);
1505        }
1506
1507        drop(work);
1508        {
1509            let state = queue.state.lock();
1510            assert!(state.lanes.is_empty());
1511        }
1512        assert_eq!(
1513            queue.try_submit(1, ScheduledPacketExecution::PlayerLocal, 4, "next"),
1514            Ok(())
1515        );
1516    }
1517
1518    #[test]
1519    fn discarding_lane_removes_hidden_barriers_and_keeps_active_work_charged() {
1520        let queue = PacketQueue::with_limits(limits(10, 100));
1521        queue.submit(1, ScheduledPacketExecution::PlayerLocal, "active attacker");
1522        queue.submit(1, ScheduledPacketExecution::Serialized, "hidden serialized");
1523        queue.submit(1, ScheduledPacketExecution::Exclusive, "hidden exclusive");
1524        queue.submit(2, ScheduledPacketExecution::PlayerLocal, "other player");
1525        queue.open();
1526        let Some(mut active) = queue.try_next() else {
1527            panic!("attacker's first packet should start");
1528        };
1529        assert_eq!(active.take(), Some("active attacker"));
1530
1531        queue.discard_lane(1);
1532
1533        {
1534            let state = queue.state.lock();
1535            assert!(state.serialized.is_empty());
1536            assert!(state.exclusive.is_empty());
1537            assert!(state.serialized_ready.is_empty());
1538            assert!(state.exclusive_ready.is_empty());
1539            let Some(lane) = state.lanes.get(&1) else {
1540                panic!("active attacker lane should remain");
1541            };
1542            assert!(lane.active);
1543            assert!(lane.queued.is_empty());
1544            assert_eq!(lane.outstanding_packets, 1);
1545            assert_eq!(lane.outstanding_bytes, 1);
1546        }
1547
1548        let Some(mut other) = queue.try_next() else {
1549            panic!("purged barriers should no longer block another player");
1550        };
1551        assert_eq!(other.take(), Some("other player"));
1552        drop(other);
1553        drop(active);
1554
1555        let state = queue.state.lock();
1556        assert!(state.lanes.is_empty());
1557    }
1558
1559    #[test]
1560    fn discarding_idle_lane_removes_ready_exclusive_barrier() {
1561        let queue = PacketQueue::with_limits(limits(10, 100));
1562        queue.submit(1, ScheduledPacketExecution::Exclusive, "attacker barrier");
1563        queue.submit(1, ScheduledPacketExecution::Serialized, "hidden serialized");
1564        queue.submit(2, ScheduledPacketExecution::PlayerLocal, "other player");
1565
1566        queue.discard_lane(1);
1567
1568        {
1569            let state = queue.state.lock();
1570            assert!(!state.lanes.contains_key(&1));
1571            let other = state.lanes.get(&2).expect("other lane should remain");
1572            assert_eq!(other.outstanding_packets, 1);
1573            assert_eq!(other.outstanding_bytes, 1);
1574            assert!(state.exclusive_ready.is_empty());
1575            assert!(state.exclusive.is_empty());
1576            assert!(state.serialized.is_empty());
1577        }
1578
1579        queue.open();
1580        let Some(mut other) = queue.try_next() else {
1581            panic!("discarded exclusive barrier should not block another player");
1582        };
1583        assert_eq!(other.take(), Some("other player"));
1584    }
1585
1586    #[test]
1587    fn queued_packets_start_in_submission_order_when_opened() {
1588        let queue = PacketQueue::new();
1589        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 1);
1590        queue.submit(2, ScheduledPacketExecution::PlayerLocal, 2);
1591        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 3);
1592        assert!(queue.try_next().is_none());
1593
1594        queue.open();
1595        let mut processed = Vec::new();
1596        while let Some(mut work) = queue.try_next() {
1597            if let Some(value) = work.take() {
1598                processed.push(value);
1599            }
1600        }
1601
1602        assert_eq!(processed, [1, 2, 3]);
1603    }
1604
1605    #[test]
1606    fn packet_lane_only_allows_one_active_handler() {
1607        let queue = PacketQueue::new();
1608        queue.submit(
1609            1,
1610            ScheduledPacketExecution::PlayerLocal,
1611            "first player packet",
1612        );
1613        queue.submit(
1614            1,
1615            ScheduledPacketExecution::PlayerLocal,
1616            "second player packet",
1617        );
1618        queue.submit(
1619            2,
1620            ScheduledPacketExecution::PlayerLocal,
1621            "other player packet",
1622        );
1623        queue.open();
1624
1625        let Some(mut first) = queue.try_next() else {
1626            panic!("first packet should start");
1627        };
1628        assert_eq!(first.take(), Some("first player packet"));
1629
1630        let Some(mut other_player) = queue.try_next() else {
1631            panic!("another player's packet should be able to start");
1632        };
1633        assert_eq!(other_player.take(), Some("other player packet"));
1634        assert!(queue.try_next().is_none());
1635
1636        drop(first);
1637        let Some(mut second) = queue.try_next() else {
1638            panic!("the next packet should start after its lane becomes idle");
1639        };
1640        assert_eq!(second.take(), Some("second player packet"));
1641    }
1642
1643    #[test]
1644    fn serialized_packet_overlaps_player_local_work_but_not_another_serialized_packet() {
1645        let queue = PacketQueue::new();
1646        queue.submit(1, ScheduledPacketExecution::PlayerLocal, "first local");
1647        queue.submit(2, ScheduledPacketExecution::Serialized, "first serialized");
1648        queue.submit(3, ScheduledPacketExecution::Serialized, "second serialized");
1649        queue.submit(4, ScheduledPacketExecution::PlayerLocal, "later local");
1650        queue.open();
1651
1652        let Some(mut first_local) = queue.try_next() else {
1653            panic!("first player-local packet should start");
1654        };
1655        assert_eq!(first_local.take(), Some("first local"));
1656
1657        let Some(mut first_serialized) = queue.try_next() else {
1658            panic!("serialized packet should overlap player-local work");
1659        };
1660        assert_eq!(first_serialized.take(), Some("first serialized"));
1661
1662        let Some(mut later_local) = queue.try_next() else {
1663            panic!("player-local work should bypass a blocked serialized packet");
1664        };
1665        assert_eq!(later_local.take(), Some("later local"));
1666        assert!(queue.try_next().is_none());
1667
1668        drop(first_serialized);
1669        let Some(mut second_serialized) = queue.try_next() else {
1670            panic!("next serialized packet should start after its predecessor finishes");
1671        };
1672        assert_eq!(second_serialized.take(), Some("second serialized"));
1673    }
1674
1675    #[test]
1676    fn serialized_packet_hidden_in_an_active_lane_preserves_serialized_order() {
1677        let queue = PacketQueue::new();
1678        queue.submit(1, ScheduledPacketExecution::PlayerLocal, "active lane");
1679        queue.submit(1, ScheduledPacketExecution::Serialized, "hidden serialized");
1680        queue.submit(2, ScheduledPacketExecution::Serialized, "later serialized");
1681        queue.submit(
1682            3,
1683            ScheduledPacketExecution::PlayerLocal,
1684            "independent local",
1685        );
1686        queue.open();
1687
1688        let Some(active_lane) = queue.try_next() else {
1689            panic!("first lane packet should start");
1690        };
1691        let Some(mut independent_local) = queue.try_next() else {
1692            panic!("player-local work should bypass serialized ordering contention");
1693        };
1694        assert_eq!(independent_local.take(), Some("independent local"));
1695        assert!(queue.try_next().is_none());
1696
1697        drop(active_lane);
1698        let Some(mut hidden_serialized) = queue.try_next() else {
1699            panic!("earliest serialized packet should start when its lane becomes idle");
1700        };
1701        assert_eq!(hidden_serialized.take(), Some("hidden serialized"));
1702        assert!(queue.try_next().is_none());
1703
1704        drop(hidden_serialized);
1705        let Some(mut later_serialized) = queue.try_next() else {
1706            panic!("later serialized packet should preserve global submission order");
1707        };
1708        assert_eq!(later_serialized.take(), Some("later serialized"));
1709    }
1710
1711    #[test]
1712    fn blocking_worker_bypasses_active_serialized_work_for_player_local_work() {
1713        let queue = Arc::new(PacketQueue::new());
1714        queue.submit(1, ScheduledPacketExecution::Serialized, "active serialized");
1715        queue.submit(2, ScheduledPacketExecution::Serialized, "queued serialized");
1716        queue.submit(3, ScheduledPacketExecution::PlayerLocal, "player local");
1717        queue.open();
1718
1719        let Some(active_serialized) = queue.try_next() else {
1720            panic!("first serialized packet should start");
1721        };
1722        let worker_queue = Arc::clone(&queue);
1723        let (sender, receiver) = mpsc::channel();
1724        let worker = thread::spawn(move || {
1725            for _ in 0..2 {
1726                let Some(mut work) = worker_queue.next() else {
1727                    return;
1728                };
1729                if let Some(value) = work.take() {
1730                    let _ = sender.send(value);
1731                }
1732            }
1733        });
1734
1735        assert_eq!(
1736            receiver.recv_timeout(Duration::from_secs(1)),
1737            Ok("player local")
1738        );
1739        assert!(receiver.recv_timeout(Duration::from_millis(10)).is_err());
1740
1741        drop(active_serialized);
1742        assert_eq!(
1743            receiver.recv_timeout(Duration::from_secs(1)),
1744            Ok("queued serialized")
1745        );
1746        assert!(worker.join().is_ok());
1747    }
1748
1749    #[test]
1750    fn exclusive_packet_waits_for_active_work_and_blocks_later_packets() {
1751        let queue = PacketQueue::new();
1752        queue.submit(1, ScheduledPacketExecution::PlayerLocal, "before barrier");
1753        queue.submit(2, ScheduledPacketExecution::Exclusive, "barrier");
1754        queue.submit(3, ScheduledPacketExecution::PlayerLocal, "after barrier");
1755        queue.open();
1756
1757        let Some(mut before) = queue.try_next() else {
1758            panic!("packet before the barrier should start");
1759        };
1760        assert_eq!(before.take(), Some("before barrier"));
1761        assert!(queue.try_next().is_none());
1762
1763        drop(before);
1764        let Some(mut barrier) = queue.try_next() else {
1765            panic!("exclusive packet should start after active work finishes");
1766        };
1767        assert_eq!(barrier.take(), Some("barrier"));
1768        assert!(queue.try_next().is_none());
1769
1770        drop(barrier);
1771        let Some(mut after) = queue.try_next() else {
1772            panic!("packet after the barrier should start after it finishes");
1773        };
1774        assert_eq!(after.take(), Some("after barrier"));
1775    }
1776
1777    #[test]
1778    fn exclusive_packet_waits_for_serialized_work_and_blocks_player_local_work() {
1779        let queue = PacketQueue::new();
1780        queue.submit(1, ScheduledPacketExecution::Serialized, "before barrier");
1781        queue.submit(2, ScheduledPacketExecution::Exclusive, "barrier");
1782        queue.submit(3, ScheduledPacketExecution::PlayerLocal, "after barrier");
1783        queue.open();
1784
1785        let Some(mut before) = queue.try_next() else {
1786            panic!("serialized packet before the barrier should start");
1787        };
1788        assert_eq!(before.take(), Some("before barrier"));
1789        assert!(queue.try_next().is_none());
1790
1791        drop(before);
1792        let Some(mut barrier) = queue.try_next() else {
1793            panic!("exclusive packet should wait for serialized work");
1794        };
1795        assert_eq!(barrier.take(), Some("barrier"));
1796        assert!(queue.try_next().is_none());
1797
1798        drop(barrier);
1799        let Some(mut after) = queue.try_next() else {
1800            panic!("player-local packet should wait for the exclusive barrier");
1801        };
1802        assert_eq!(after.take(), Some("after barrier"));
1803    }
1804
1805    #[test]
1806    fn exclusive_packet_hidden_in_an_active_lane_still_blocks_later_lanes() {
1807        let queue = PacketQueue::new();
1808        queue.submit(1, ScheduledPacketExecution::PlayerLocal, "active");
1809        queue.submit(1, ScheduledPacketExecution::Exclusive, "barrier");
1810        queue.submit(2, ScheduledPacketExecution::PlayerLocal, "later lane");
1811        queue.open();
1812
1813        let Some(active) = queue.try_next() else {
1814            panic!("first packet should start");
1815        };
1816        assert!(queue.try_next().is_none());
1817
1818        drop(active);
1819        let Some(mut barrier) = queue.try_next() else {
1820            panic!("hidden exclusive packet should become runnable");
1821        };
1822        assert_eq!(barrier.take(), Some("barrier"));
1823    }
1824
1825    #[test]
1826    fn closed_phase_retains_new_packets_for_the_next_open_phase() {
1827        let queue = PacketQueue::new();
1828        queue.open();
1829        queue.close();
1830        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 1);
1831
1832        assert!(queue.try_next().is_none());
1833        queue.open();
1834        let Some(mut work) = queue.try_next() else {
1835            panic!("queued packet should become available when the packet phase opens");
1836        };
1837        assert_eq!(work.take(), Some(1));
1838    }
1839
1840    #[test]
1841    fn tick_drain_only_processes_packets_submitted_before_its_cutoff() {
1842        let queue = PacketQueue::new();
1843        queue.submit(1, ScheduledPacketExecution::PlayerLocal, "before cutoff");
1844        queue.open();
1845        let Some((before_sequence, should_wake)) = queue.begin_tick_drain() else {
1846            panic!("running queue should begin a tick drain");
1847        };
1848        assert!(should_wake);
1849        queue.submit(2, ScheduledPacketExecution::PlayerLocal, "after cutoff");
1850
1851        let Some(mut before) = queue.try_next() else {
1852            panic!("packet before the cutoff should drain");
1853        };
1854        assert_eq!(before.take(), Some("before cutoff"));
1855        drop(before);
1856
1857        assert!(queue.try_next().is_none());
1858        assert!(queue.tick_drain_complete(before_sequence));
1859        queue.finish_tick_drain(before_sequence);
1860        queue.open();
1861        let Some(mut after) = queue.try_next() else {
1862            panic!("packet after the cutoff should wait for the next open phase");
1863        };
1864        assert_eq!(after.take(), Some("after cutoff"));
1865    }
1866
1867    #[test]
1868    fn tick_drain_orders_hidden_serialized_and_exclusive_work_before_its_cutoff() {
1869        let queue = PacketQueue::new();
1870        queue.submit(1, ScheduledPacketExecution::PlayerLocal, "active local");
1871        queue.submit(1, ScheduledPacketExecution::Serialized, "hidden serialized");
1872        queue.submit(2, ScheduledPacketExecution::Exclusive, "exclusive");
1873        queue.open();
1874
1875        let Some(active_local) = queue.try_next() else {
1876            panic!("player-local packet should start before draining");
1877        };
1878        let Some((before_sequence, should_wake)) = queue.begin_tick_drain() else {
1879            panic!("running queue should begin a tick drain");
1880        };
1881        assert!(!should_wake);
1882        queue.submit(
1883            3,
1884            ScheduledPacketExecution::Serialized,
1885            "post-cutoff serialized",
1886        );
1887        queue.submit(
1888            4,
1889            ScheduledPacketExecution::PlayerLocal,
1890            "post-cutoff local",
1891        );
1892
1893        assert!(!queue.tick_drain_complete(before_sequence));
1894        assert!(queue.try_next().is_none());
1895        drop(active_local);
1896
1897        let Some(mut hidden_serialized) = queue.try_next() else {
1898            panic!("hidden pre-cutoff serialized packet should drain");
1899        };
1900        assert_eq!(hidden_serialized.take(), Some("hidden serialized"));
1901        assert!(!queue.tick_drain_complete(before_sequence));
1902        drop(hidden_serialized);
1903
1904        let Some(mut exclusive) = queue.try_next() else {
1905            panic!("pre-cutoff exclusive packet should drain after earlier work");
1906        };
1907        assert_eq!(exclusive.take(), Some("exclusive"));
1908        assert!(!queue.tick_drain_complete(before_sequence));
1909        drop(exclusive);
1910
1911        assert!(queue.try_next().is_none());
1912        assert!(queue.tick_drain_complete(before_sequence));
1913        queue.finish_tick_drain(before_sequence);
1914        queue.open();
1915
1916        let Some(mut post_cutoff_serialized) = queue.try_next() else {
1917            panic!("post-cutoff serialized packet should remain for the next phase");
1918        };
1919        assert_eq!(
1920            post_cutoff_serialized.take(),
1921            Some("post-cutoff serialized")
1922        );
1923        let Some(mut post_cutoff_local) = queue.try_next() else {
1924            panic!("post-cutoff player-local packet should remain for the next phase");
1925        };
1926        assert_eq!(post_cutoff_local.take(), Some("post-cutoff local"));
1927    }
1928
1929    #[tokio::test]
1930    async fn tick_drain_waits_for_active_packet_work() {
1931        let queue = Arc::new(PacketQueue::new());
1932        queue.open();
1933        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 1);
1934        let Some(work) = queue.try_next() else {
1935            panic!("open packet phase should start queued work");
1936        };
1937        let drain = queue.drain_for_tick();
1938        tokio::pin!(drain);
1939
1940        assert!(
1941            timeout(Duration::from_millis(10), drain.as_mut())
1942                .await
1943                .is_err()
1944        );
1945        drop(work);
1946        assert!(
1947            timeout(Duration::from_secs(1), drain.as_mut())
1948                .await
1949                .is_ok()
1950        );
1951    }
1952
1953    #[tokio::test]
1954    async fn overload_progress_waits_for_one_active_packet_to_finish() {
1955        let queue = Arc::new(PacketQueue::new());
1956        queue.open();
1957        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 1);
1958        let Some(work) = queue.try_next() else {
1959            panic!("open packet phase should start queued work");
1960        };
1961        let Some(completed) = queue.progress_baseline() else {
1962            panic!("active packet work should require progress");
1963        };
1964        let progress = queue.wait_for_progress_since(completed);
1965        tokio::pin!(progress);
1966
1967        assert!(
1968            timeout(Duration::from_millis(10), progress.as_mut())
1969                .await
1970                .is_err()
1971        );
1972        drop(work);
1973        assert!(
1974            timeout(Duration::from_secs(1), progress.as_mut())
1975                .await
1976                .is_ok()
1977        );
1978    }
1979
1980    #[test]
1981    fn stopped_queue_discards_pending_and_future_work() {
1982        let queue = PacketQueue::new();
1983        queue.open();
1984        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 1);
1985        queue.stop();
1986        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 2);
1987
1988        assert!(queue.try_next().is_none());
1989        let state = queue.state.lock();
1990        assert!(state.lanes.is_empty());
1991    }
1992
1993    #[test]
1994    fn stopping_with_active_work_keeps_completion_accounting_valid() {
1995        let queue = PacketQueue::new();
1996        queue.submit(1, ScheduledPacketExecution::Exclusive, 1);
1997        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 2);
1998        queue.open();
1999        let Some(work) = queue.try_next() else {
2000            panic!("open packet phase should start queued work");
2001        };
2002
2003        queue.stop();
2004        {
2005            let state = queue.state.lock();
2006            let lane = state.lanes.get(&1).expect("active lane should remain");
2007            assert!(lane.active);
2008            assert!(lane.queued.is_empty());
2009            assert_eq!(lane.outstanding_packets, 1);
2010            assert_eq!(lane.outstanding_bytes, 1);
2011        }
2012        drop(work);
2013
2014        assert!(queue.try_next().is_none());
2015        let state = queue.state.lock();
2016        assert_eq!(state.active, 0);
2017        assert!(state.lanes.is_empty());
2018    }
2019
2020    #[tokio::test]
2021    async fn stopping_with_active_serialized_and_local_work_clears_all_queue_state() {
2022        let queue = PacketQueue::new();
2023        queue.submit(1, ScheduledPacketExecution::Serialized, "active serialized");
2024        queue.submit(2, ScheduledPacketExecution::PlayerLocal, "active local");
2025        queue.submit(3, ScheduledPacketExecution::Serialized, "queued serialized");
2026        queue.submit(4, ScheduledPacketExecution::Exclusive, "queued exclusive");
2027        queue.open();
2028
2029        let Some(active_serialized) = queue.try_next() else {
2030            panic!("serialized packet should start");
2031        };
2032        let Some(active_local) = queue.try_next() else {
2033            panic!("player-local packet should overlap serialized work");
2034        };
2035        queue.stop();
2036        queue.drain_for_tick().await;
2037
2038        {
2039            let state = queue.state.lock();
2040            assert_eq!(state.active, 2);
2041            assert!(state.serialized_active);
2042            assert!(state.player_local_ready.is_empty());
2043            assert!(state.serialized_ready.is_empty());
2044            assert!(state.exclusive_ready.is_empty());
2045            assert!(state.serialized.is_empty());
2046            assert!(state.exclusive.is_empty());
2047            assert_eq!(state.lanes.len(), 2);
2048            for key in [1, 2] {
2049                let lane = state.lanes.get(&key).expect("active lane should remain");
2050                assert!(lane.active);
2051                assert!(lane.queued.is_empty());
2052                assert_eq!(lane.outstanding_packets, 1);
2053                assert_eq!(lane.outstanding_bytes, 1);
2054            }
2055        }
2056
2057        drop(active_serialized);
2058        drop(active_local);
2059
2060        let state = queue.state.lock();
2061        assert_eq!(state.active, 0);
2062        assert!(!state.serialized_active);
2063        assert!(state.lanes.is_empty());
2064    }
2065
2066    #[test]
2067    fn blocking_worker_only_starts_work_during_the_open_phase() {
2068        let queue = Arc::new(PacketQueue::new());
2069        let worker_queue = Arc::clone(&queue);
2070        let (sender, receiver) = mpsc::channel();
2071        let worker = thread::spawn(move || {
2072            while let Some(mut work) = worker_queue.next() {
2073                if let Some(value) = work.take() {
2074                    let _ = sender.send(value);
2075                }
2076            }
2077        });
2078
2079        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 1);
2080        assert!(receiver.recv_timeout(Duration::from_millis(10)).is_err());
2081        queue.open();
2082        assert_eq!(receiver.recv_timeout(Duration::from_secs(1)), Ok(1));
2083        queue.close();
2084        queue.submit(1, ScheduledPacketExecution::PlayerLocal, 2);
2085        assert!(receiver.recv_timeout(Duration::from_millis(10)).is_err());
2086        queue.stop();
2087
2088        assert!(worker.join().is_ok());
2089    }
2090}