Async Command Pipeline

Last updated: July 5, 2026

dMUD Async Command Pipeline & Structured Logging

Added: June 2026 — commits 29de0c56 (slog) and 0e155400 (async pipeline) Core module: dmud/pipeline.go (new), dmud/logger.go (new)

Two refactors of the dMUD command path landed back-to-back in June 2026: a structured-logging migration across 17 files and an asynchronous per-session command pipeline that eliminates ambient interleaving and protects long-running commands.

🚦 Async Command Pipeline (pipeline.go)

Prior behaviour: client read loops called processSessionInput synchronously. A long-running command (heavy Lua, mini-game redraw) blocked the read loop, so ambient frames and chat broadcasts could interleave with command output mid-render, producing garbled terminals.

Commit 0e155400 introduced a per-session worker model:

Design

Component Behaviour
Per-session worker goroutine One worker per connected session
Bounded buffered channel (cap 16) Commands queue without blocking the reader
Non-blocking enqueue If the queue is full, the command is dropped with a structured warning (no deadlock)
30s per-command context timeout Long-running commands abort cleanly instead of hanging the session
Atomic response + prompt render The worker emits the response and the next prompt together — ambient frames cannot slip between them

Wiring

  • Telnet read loop (server.go) and WebSocket read loop reduced to simple Enqueue() calls.
  • processSessionInput signature gained a context.Context so per-command timeout flows through command handlers.
  • dmud/input_test.go updated for the context-aware signature.

File touch list

File Change
dmud/pipeline.go New — worker + channel + enqueue
dmud/server.go Both read loops simplified; enqueue instead of synchronous dispatch
dmud/user.go UserSession gains pipeline channel + worker handle
dmud/input_test.go Test signature update

📋 Structured Logging (logger.go)

Commit 29de0c56 migrated dMUD off log.Printf/Println to Go's standard log/slog. JSON handler provides machine-parseable output with typed key-value pairs.

Components

File Purpose
dmud/logger.go Logger package var, per-component sub-loggers (server, room, user, npc, ...), domain helpers

Domain helpers introduced

  • logRoomEnter, logConnect, logDisconnect, logChat, logNPCCall, ... — give every log line a stable shape keyed by component, room_id, user_id, event.
  • Replaces ~100 log.* calls across 17 files with Logger.* / sub-logger calls.

Library safety

  • log.Fatalf was replaced with slog.Error everywhere — library code in dMUD never fatal-exits the parent process.

Initialisation caveat (commit c3810acd)

A follow-up panic fix moved Logger initialisation to a package-level var with a no-op init() stub. The original placement (logServer = logWith("server") at line 36) ran before Logger was assigned, causing slog.Logger.With() to receive a nil receiver. Go initialises package-level vars before init(), so the early logWith call panicked on every cold start.

📁 Key Files

File Purpose
dmud/pipeline.go Per-session worker goroutine, bounded channel, enqueue + drop semantics
dmud/logger.go Structured logger + per-component sub-loggers + domain helpers
dmud/server.go Reader loops, connection lifecycle logging
dmud/dmud.go Engine-level log migration
dmud/room.go, dmud/user.go, dmud/npc_script.go, ... All call sites migrated

🔗 Related

  • dMUD Overview
  • [System Logging (assistant)]](../assistant/system-logging.md) — Companion assistant-side structured logging
  • dMUD Ops & Debugging — Where pipeline + log output shows up