← QueueSim home  ·  All models

Barbershop (Henriksen) — README

Barbershop

Henriksen's two-shutdown-mode barbershop — one barber, exponential arrivals, exponential service, run for N days. The canonical GPSS Example #1 (GENERATE / SEIZE / ADVANCE / RELEASE / TERMINATE). This is the reference model for the puck idiom; read it first.

Problem

Customers arrive at a one-chair barbershop with mean inter-arrival 15 minutes and mean service 12 minutes (utilization ≈ 0.8). The shop opens at t=0 and closes at closing_time (default 480 min = 8 hours).

Two shutdown modes are simulated:

The interesting output is the difference: average sojourn, max queue length, and the overtime the barber works under graceful close.

GPSS reference: see docs/gpss-examples.md example #1.

Model in this directory

Two puck types share a ^Shop pointer:

Shop (barbershop.odin:56-71) holds:

The customer pool is a sim.Pool(Customer, 256) allocated in run_day and destroyed via defer. Gen has its own RNG stream (index_key=2) for inter-arrivals.

Why this shape

The wait list is shop.wait_list, not barber.waiters. The barbershop bypasses sim.seize / sim.release and manipulates barber.busy and barber.owner directly (barbershop.odin:119-145). This is deliberate: the model wants peek-then-seize fairness — when an arriving customer finds the barber idle and the wait list empty, it cuts the line and starts service in the same tick. Going through seize would queue it on the Facility's internal waiters even when nobody else is there, costing a needless reactivate. See modeling guide §4.2 for the gotcha this avoids in the general case, and §7 for the state machine.

Two RNG streams, not one. svc_stream uses index_key=1, ia_stream uses index_key=2, both seeded from the same model seed. This means for any given seed, service times are reproducible independent of how many customers have arrived — useful for debugging a specific customer's behavior. See modeling guide §5.

Graceful close = eng.suspend on the generator, not end_time. At the closing instant the generator stops scheduling new arrivals but stays in the engine (barbershop.odin:220-225). The engine continues running until the process heap empties — i.e., until the last customer terminates. This is the GPSS / SLX equivalent of wait until (clock ≥ close and queue empty and barber idle) with no special predicate needed; quiescence detection is built into the engine loop. See modeling guide §8.

flush_queue is called at every queue change, not on a timer. The area-under-curve accumulator (barbershop.odin:74-86) is updated whenever a customer joins or leaves the wait list, never on a periodic sample. This is exact, not approximate. See modeling guide §9.

Alternatives considered

Use sim.seize / sim.release and the Facility's built-in waiters

Would shorten the customer tick by ~15 lines (no manual busy/owner juggling, no separate wait_list Set, no flush_queue since the Facility tracks waiters). The cost is losing the peek-then-seize fast path: every arrival pays a queue-and-reactivate even when the barber is idle and nobody is waiting. For a one-server shop this is mostly aesthetic, but the same pattern in the clinic (multi-physician, priority classes) becomes meaningful overhead, so we use the explicit form here for consistency with what production models need.

Single combined puck for arrivals + service

GPSS would write this as one transaction flow inside GENERATE. We could mirror that with a single self-cloning puck. We don't, because separating the generator from the customer makes the close condition (graceful vs hard) a property of the generator rather than embedded in every customer's tick. It also makes the customer struct reusable for variants that arrive via a different mechanism (e.g., scheduled arrivals from a roster).

Write flush_queue as a Notify_Var

If wait_list.count were a Notify_Var, wait_until-style waiters could trigger off it. We don't need that here — nothing waits on queue length — and the area-under-curve update is cheaper inline.

What this example teaches

This is the reference for:

When other docs reference any of these patterns, they should link back here.

CLI

./barbershop [options]

Add --json to emit the uniform envelope (metadata, execution_stats, metrics, details) instead of the default text output.

Flag Type Default Description
--closing-time float 480 Shop closing time in minutes.
--days int 30 Number of days to simulate.
--json bool false Emit uniform JSON envelope instead of text.

Example runs:

./barbershop                                    # default text run
./barbershop --json                             # uniform envelope
./barbershop --days=10 --closing-time=600 --json  # custom params, JSON output

Running it

odin run examples/barbershop                              # default 30 days
odin run examples/barbershop -- --days=10                 # 10 days
odin run examples/barbershop -- --closing-time=600        # 10-hour shop
odin run examples/barbershop -- --json                    # JSON output

Default output is a console table comparing hard vs graceful close across the seed set. JSON mode is what the qsimhealth.com viewer ingests.

See also