Class: SpreeCmCommissioner::WaitingRoom::Admission::CallGuests
- Inherits:
-
Object
- Object
- SpreeCmCommissioner::WaitingRoom::Admission::CallGuests
- Extended by:
- ServiceModuleThrowable
- Includes:
- Spree::ServiceModule::Base, FirestoreConnection
- Defined in:
- app/services/spree_cm_commissioner/waiting_room/admission/call_guests.rb
Overview
Selects and admits the next cohort of waiting guests each tick. Runs as the second step of
WaitingRoom::AdvanceQueue, immediately after Positions::Stamp, so in the common case every
eligible doc already has a position by the time #eligible_guests_in queries. That's a
throughput property, not a correctness guarantee though — Stamp's own per-tick budget
(STAMP_LIMIT) can fall behind a burst of new arrivals, so #eligible_guests_in still keeps its
own position != nil filter as the actual guard against calling a guest whose position is
still nil; see that method for detail.
Admitting a guest is split into two phases: this class only claims up to MAX_CALLS_PER_RUN guests (a fast, synchronous Firestore batch write of call_dispatched_at) and hands the actual session-precreation + grant write off to CallGuestsChunkJob in slices of CALL_CHUNK_SIZE — see #claim_and_dispatch. The claim is what keeps that split correct:
- it stops next tick's #eligible_guests_in from re-selecting the same not-yet-written guests
while their chunk jobs are still draining (which would otherwise re-dispatch duplicate
chunk jobs every tick, growing unbounded);
- #fetch_available_slots subtracts still-claimed docs from capacity, so a slow-draining
backlog throttles new claims instead of overcommitting;
- #reclaim_stale_claims self-heals a guest orphaned by a permanently-failed chunk job.
Capacity fields (lobby:) and run telemetry (called_count, logs:) are returned in the
result rather than written directly — WaitingRoom::AdvanceQueue owns the single combined
lobby write and single combined logs write (merged with Positions::Stamp's own fields).
records_path is likewise resolved once by AdvanceQueue and passed in, rather than this
class reading the lobby doc itself.
Constant Summary collapse
- FIRESTORE_BATCH_SIZE =
Firestore bounds a batch update by payload size (10 MiB); 500 ops/commit leaves us far under that.
(ENV['WAITING_ROOM_FIRESTORE_BATCH_SIZE'] || 500).to_i
- MAX_CALLS_PER_RUN =
Release at most this many guests per run, even when far more slots are free. A capacity spike (cold start / lull → active_sessions low → available_slots ≈ max_sessions, e.g. 900) would otherwise release every queued guest at once, dumping 900 simultaneous session-token requests on the server. Capping paces entry at ~MAX_CALLS_PER_RUN per run (10s, per cm-market-server/config/schedule.yml); the rest stay queued and drain over the following runs.
Keeping this <= Positions::Stamp::STAMP_LIMIT is a throughput tuning goal, not a correctness requirement (#eligible_guests_in's own
position != nilfilter is what enforces correctness): raising this past STAMP_LIMIT just means some ticks call fewer than MAX_CALLS_PER_RUN guests, because Stamp hasn't yet caught up on positioning the rest of that tick's oldest-waiting cohort. (ENV['WAITING_ROOM_MAX_CALLS_PER_RUN'] || 200).to_i
- CALL_CHUNK_SIZE =
How many guests each CallGuestsChunkJob writes. MAX_CALLS_PER_RUN's per-tick claim gets split into chunks this size so one tick's admission work is spread across several small background jobs instead of one inline loop over up to MAX_CALLS_PER_RUN guests.
(ENV['WAITING_ROOM_CALL_CHUNK_SIZE'] || 50).to_i
- CALL_CLAIM_TTL_SECONDS =
How long a claim (call_dispatched_at) may stand without allow_to_enter_room_at landing before #reclaim_stale_claims frees it back up — generous (~12 ticks), bounding worst-case recovery time for a permanently-failed chunk job without needing to be tight.
(ENV['WAITING_ROOM_CALL_CLAIM_TTL_SECONDS'] || 120).to_i
Instance Attribute Summary collapse
-
#records_path ⇒ Object
readonly
Returns the value of attribute records_path.
Instance Method Summary collapse
-
#call(records_path:, firestore: nil) ⇒ Object
records_path is resolved once by WaitingRoom::AdvanceQueue (one lobby-doc read shared across the tick) and handed in here, rather than this class reading the lobby doc itself — one less Firestore round trip, and this class no longer needs any lobby-doc mocking to test.
Methods included from ServiceModuleThrowable
Methods included from FirestoreConnection
#firestore, #firestore_available?, #service_account
Instance Attribute Details
#records_path ⇒ Object (readonly)
Returns the value of attribute records_path.
60 61 62 |
# File 'app/services/spree_cm_commissioner/waiting_room/admission/call_guests.rb', line 60 def records_path @records_path end |
Instance Method Details
#call(records_path:, firestore: nil) ⇒ Object
records_path is resolved once by WaitingRoom::AdvanceQueue (one lobby-doc read shared across the tick) and handed in here, rather than this class reading the lobby doc itself — one less Firestore round trip, and this class no longer needs any lobby-doc mocking to test.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'app/services/spree_cm_commissioner/waiting_room/admission/call_guests.rb', line 65 def call(records_path:, firestore: nil) started_at = Time.zone.now @firestore = firestore if firestore.present? @records_path = records_path reclaim_stale_claims available_slots = fetch_available_slots call_limit = [available_slots, MAX_CALLS_PER_RUN].min # Always run through to lobby_fields — even with no slots — so the lobby keeps a fresh # heartbeat during peak/full periods instead of going stale and looking like a dead cron. long_waiting_guests = call_limit.positive? ? fetch_long_waiting_guests(call_limit) : [] claim_and_dispatch(long_waiting_guests) if long_waiting_guests.any? full = long_waiting_guests.size >= available_slots remaining_slots = available_slots - long_waiting_guests.size has_queue = waiting_exists?([previous_records_path, records_path]) success( called_count: long_waiting_guests.size, lobby: lobby_fields( full: full, available_slots: remaining_slots, has_queue: has_queue, active_sessions: @active_sessions, max_sessions: @max_sessions ), logs: { called_count: long_waiting_guests.size, started_at: started_at, finished_at: Time.zone.now } ) rescue StandardError => e failure(nil, e.) end |