rgoussu@goussu: ~/library/rust/exercises
~/library/rust/exercises cat tokio-mini-redis-subject.md

Tokio mini-redis — subject

# The stage-by-stage assignment for the Tokio mini-redis build — accept loop, RESP framing, shared state, channels, graceful shutdown — with acceptance per stage and the tutorial keeping the prose.

Subjectsaved 2026-08-08source #exercise#rust#async#tokio#concurrency#redis#subject

Brief

You are building a Redis subset — server and client — on Tokio, speaking real RESP over TCP, supporting GET/SET and pub/sub, shutting down gracefully. The official Tokio tutorial is the guide; this subject states what each stage must deliver and how to prove it, so the tutorial keeps the prose and you keep the checklist. Every line is yours: the tutorial's code is reference, not a paste source.

Instructions

Work the stages in order; each leaves a binary that runs.

Stage 1 — Hello Tokio

Set up the runtime (#[tokio::main]), write your first async fn and .await, and use the stock mini-redis client crate against the reference server to observe the target behavior (SET/GET round trip) you are about to reimplement.

Stage 2 — Accept loop

A TcpListener accept loop that tokio::spawns a task per connection. Fight and win the compiler argument about what may cross into a spawned task: move the socket in, own your data, understand why the future must be Send + 'static. Serve a hardcoded response to prove connections are handled concurrently.

Stage 3 — Shared state

A real GET/SET store: HashMap behind Arc<Mutex<…>> (std mutex), cloned into every connection task. Rules to internalize and honor: the lock guard must not be held across an .await (structure the code so it can't be); keep critical sections short. Then shard the map (a vector of mutexed maps, keyed by hash) to cut contention.

Stage 4 — Framing

A Connection type that turns the byte stream into RESP frames and back: read into a BytesMut cursor, attempt a frame parse, and treat frame-not-complete-yet as the normal case that waits for more bytes — never an error. Handle partial reads and leftover bytes after a frame. Write side goes through a BufWriter, flushed at frame boundaries.

Stage 5 — Channels (client side)

A client that multiplexes many callers over one connection: callers send commands to a manager task through an mpsc channel, each carrying a oneshot sender for its reply; the manager owns the connection and pairs responses to waiters. Message passing as the alternative to locking the connection.

Stage 6 — Select & shutdown (plus pub/sub)

Implement SUBSCRIBE/PUBLISH so a client can wait on messages, then wire graceful shutdown: select! over connection I/O and a shutdown broadcast channel; on Ctrl-C, stop accepting, let in-flight connections drain, and observe cancellation-by-drop doing its quiet work — a branch not taken in select! is a future dropped mid-poll. Note one place where an unexpected cancellation point surprised you.

Constraints

  • Rust with Tokio, bytes, and the mini-redis crate only as the reference client/protocol types where the tutorial uses it — the server logic, connection handling, framing, and state are written by you.
  • No unwrap() on I/O paths in the final version; connection errors terminate that connection, never the server.
  • The std-mutex rule stands: no lock guard alive across an .await (clippy's await_holding_lock clean).

Acceptance

Mapped one-to-one onto the exercise's milestones:

  1. Hello Tokio — a SET/GET round trip against the reference server runs from your async main; you can say what .await yields to and what #[tokio::main] generates.
  2. Accept loop — two simultaneous clients are served concurrently (verify with an interleaved test); the spawned-task ownership error was hit and fixed via move, not by cloning blindly.
  3. Shared state — concurrent SETs and GETs from many tasks are correct; await_holding_lock is clean; the sharded version passes the same tests and shows reduced contention under a parallel benchmark.
  4. Framing — a frame delivered one byte at a time parses correctly (partial-read test); two frames in one read both parse; writes are buffered and flushed per frame.
  5. Channels — N concurrent callers through one multiplexed client each get their own correct response, matched via their oneshot; dropping a caller doesn't wedge the manager.
  6. Select & shutdown — a subscriber receives published messages; Ctrl-C drains cleanly (no task leaks, listener closed, in-flight requests finish); the cancellation-surprise note exists.

Related