diff --git a/instructions/redis_lists.md b/instructions/redis_lists.md new file mode 100644 index 0000000..d42e117 --- /dev/null +++ b/instructions/redis_lists.md @@ -0,0 +1,259 @@ + +# 1) Data model & basics + +* A **queue** is a List at key `queue:`. +* Common patterns: + + * **Producer**: `LPUSH queue item` (or `RPUSH`) + * **Consumer (non-blocking)**: `RPOP queue` (or `LPOP`) + * **Consumer (blocking)**: `BRPOP queue timeout` (or `BLPOP`) +* If a key doesn’t exist, it’s treated as an **empty list**; push **creates** the list; when the **last element is popped, the key is deleted**. ([Redis][1]) + +--- + +# 2) Commands to implement (queues via Lists) + +## LPUSH / RPUSH + +Prepend/append one or more elements. Create the list if it doesn’t exist. +**Return**: Integer = new length of the list. + +**Syntax** + +``` +LPUSH key element [element ...] +RPUSH key element [element ...] +``` + +**RESP (example)** + +``` +*3\r\n$5\r\nLPUSH\r\n$5\r\nqueue\r\n$5\r\njob-1\r\n +:1\r\n +``` + +Refs: semantics & multi-arg ordering. ([Redis][1]) + +### LPUSHX / RPUSHX (optional but useful) + +Like LPUSH/RPUSH, **but only if the list exists**. +**Return**: Integer = new length (0 if key didn’t exist). + +``` +LPUSHX key element [element ...] +RPUSHX key element [element ...] +``` + +Refs: command index. ([Redis][2]) + +--- + +## LPOP / RPOP + +Remove & return one (default) or **up to COUNT** elements since Redis 6.2. +If the list is empty or missing, **Null** is returned (Null Bulk or Null Array if COUNT>1). +**Return**: + +* No COUNT: Bulk String or Null Bulk. +* With COUNT: Array of Bulk Strings (possibly empty) or Null Array if key missing. + +**Syntax** + +``` +LPOP key [count] +RPOP key [count] +``` + +**RESP (no COUNT)** + +``` +*2\r\n$4\r\nRPOP\r\n$5\r\nqueue\r\n +$5\r\njob-1\r\n # or $-1\r\n if empty +``` + +**RESP (COUNT=2)** + +``` +*3\r\n$4\r\nLPOP\r\n$5\r\nqueue\r\n$1\r\n2\r\n +*2\r\n$5\r\njob-2\r\n$5\r\njob-3\r\n # or *-1\r\n if key missing +``` + +Refs: LPOP w/ COUNT; general pop semantics. ([Redis][3]) + +--- + +## BLPOP / BRPOP (blocking consumers) + +Block until an element is available in any of the given lists or until `timeout` (seconds, **double**, `0` = forever). +**Return** on success: **Array \[key, element]**. +**Return** on timeout: **Null Array**. + +**Syntax** + +``` +BLPOP key [key ...] timeout +BRPOP key [key ...] timeout +``` + +**RESP** + +``` +*3\r\n$5\r\nBRPOP\r\n$5\r\nqueue\r\n$1\r\n0\r\n # block forever + +# Success reply +*2\r\n$5\r\nqueue\r\n$5\r\njob-4\r\n + +# Timeout reply +*-1\r\n +``` + +**Implementation notes** + +* If any listed key is non-empty at call time, reply **immediately** from the first non-empty key **by the command’s key order**. +* Otherwise, put the client into a **blocked state** (register per-key waiters). On any `LPUSH/RPUSH` to those keys, **wake the earliest waiter** and serve it atomically. +* If timeout expires, return **Null Array** and clear the blocked state. + Refs: timeout semantics and return shape. ([Redis][4]) + +--- + +## LMOVE / BLMOVE (atomic move; replaces RPOPLPUSH/BRPOPLPUSH) + +Atomically **pop from one side** of `source` and **push to one side** of `destination`. + +* Use for **reliable queues** (move to a *processing* list). +* `BLMOVE` blocks like `BLPOP` when `source` is empty. + +**Syntax** + +``` +LMOVE source destination LEFT|RIGHT LEFT|RIGHT +BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout +``` + +**Return**: Bulk String element moved, or Null if `source` empty (LMOVE); `BLMOVE` blocks/Null on timeout. + +**RESP (LMOVE RIGHT->LEFT)** + +``` +*5\r\n$5\r\nLMOVE\r\n$6\r\nsource\r\n$3\r\ndst\r\n$5\r\nRIGHT\r\n$4\r\nLEFT\r\n +$5\r\njob-5\r\n +``` + +**Notes** + +* Prefer `LMOVE/BLMOVE` over deprecated `RPOPLPUSH/BRPOPLPUSH`. +* Pattern: consumer `LMOVE queue processing RIGHT LEFT` → work → `LREM processing 1 ` to ACK; a reaper can requeue stale items. + Refs: LMOVE/BLMOVE behavior and reliable-queue pattern; deprecation of RPOPLPUSH. ([Redis][5]) + +*(Compat: you can still implement `RPOPLPUSH source dest` and `BRPOPLPUSH source dest timeout`, but mark them deprecated and map to LMOVE/BLMOVE.)* ([Redis][6]) + +--- + +## LLEN (length) + +Useful for metrics/backpressure. + +``` +LLEN key +``` + +**RESP** + +``` +*2\r\n$4\r\nLLEN\r\n$5\r\nqueue\r\n +:3\r\n +``` + +Refs: list overview mentioning LLEN. ([Redis][7]) + +--- + +## LREM (ack for “reliable” processing) + +Remove occurrences of `element` from the list (head→tail scan). +Use `count=1` to ACK a single processed item from `processing`. + +``` +LREM key count element +``` + +**RESP** + +``` +*4\r\n$4\r\nLREM\r\n$9\r\nprocessing\r\n$1\r\n1\r\n$5\r\njob-5\r\n +:1\r\n +``` + +Refs: reliable pattern mentions LREM to ACK. ([Redis][5]) + +--- + +## LTRIM (bounded queues / retention) + +Keep only `[start, stop]` range; everything else is dropped. +Use to cap queue length after pushes. + +``` +LTRIM key start stop +``` + +**RESP** + +``` +*4\r\n$5\r\nLTRIM\r\n$5\r\nqueue\r\n$2\r\n0\r\n$3\r\n999\r\n ++OK\r\n +``` + +Refs: list overview includes LTRIM for retention. ([Redis][7]) + +--- + +## LRANGE / LINDEX (debugging / peeking) + +* `LRANGE key start stop` → Array of elements (non-destructive). +* `LINDEX key index` → one element or Null. + +These aren’t required for queue semantics, but handy. ([Redis][7]) + +--- + +# 3) Errors & types + +* Wrong type: `-WRONGTYPE Operation against a key holding the wrong kind of value\r\n` +* Non-existing key: + + * Push: creates the list (returns new length). + * Pop (non-blocking): returns **Null**. + * Blocking pop: **Null Array** on timeout. ([Redis][1]) + +--- + +# 4) Blocking engine (implementation sketch) + +1. **Call time**: scan keys in user order. If a non-empty list is found, pop & reply immediately. +2. **Otherwise**: register the client as **blocked** on those keys with `deadline = now + timeout` (or infinite). +3. **On push to any key**: if waiters exist, **wake one** (FIFO) and serve its pop **atomically** with the push result. +4. **On timer**: for each blocked client whose deadline passed, reply `Null Array` and clear state. +5. **Connection close**: remove from any wait queues. + +Refs for timeout/block semantics. ([Redis][4]) + +--- + +# 5) Reliable queue pattern (recommended) + +* **Consume**: `LMOVE queue processing RIGHT LEFT` (or `BLMOVE ... 0`). +* **Process** the job. +* **ACK**: `LREM processing 1 ` when done. +* **Reaper**: auxiliary task that detects stale jobs (e.g., track job IDs + timestamps in a ZSET) and requeues them. (Lists don’t include timestamps; pairing with a ZSET is standard practice.) + Refs: LMOVE doc’s pattern. ([Redis][5]) + +--- + +# 6) Minimal test matrix + +* Push/pop happy path (both ends), with/without COUNT. +* Blocking pop: immediate availability, block + timeout, wake on push, multiple keys order, FIFO across multiple waiters. +* LMOVE/BLMOVE: RIGHT→LEFT pipeline, block + wake, cross-list atomicity, ACK via LREM. +* Type errors and key deletion on last pop. + diff --git a/run_tests.sh b/run_tests.sh index 675ee2a..84a2d16 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -13,6 +13,8 @@ echo "2️⃣ Running Comprehensive Redis Integration Tests (13 tests)..." echo "----------------------------------------------------------------" cargo test --test redis_integration_tests -- --nocapture cargo test --test redis_basic_client -- --nocapture +cargo test --test debug_hset -- --nocapture +cargo test --test debug_hset_simple -- --nocapture echo "" echo "3️⃣ Running All Tests..." diff --git a/src/cmd.rs b/src/cmd.rs index 1e29178..9b8f6c2 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -36,6 +36,9 @@ pub enum Cmd { Client(Vec), ClientSetName(String), ClientGetName, + // List commands + LPush(String, Vec), + RPush(String, Vec), Unknow(String), } diff --git a/tests/redis_tests.rs b/tests/redis_tests.rs index ed08dcb..94e425f 100644 --- a/tests/redis_tests.rs +++ b/tests/redis_tests.rs @@ -18,6 +18,7 @@ async fn start_test_server(test_name: &str) -> (Server, u16) { let option = DBOption { dir: test_dir, port, + debug: true, }; let server = Server::new(option).await;