Project 4: Coroutine-Based Redis Client

Build a coroutine-first Redis client with clean async APIs and RESP parsing.

Quick Reference

Attribute Value
Difficulty Master
Time Estimate 2-4 weeks
Main Programming Language C++20
Coolness Level Pure Magic
Business Potential Micro-SaaS / Pro Tool
Prerequisites Concurrency basics, networking, templates
Key Topics coroutines, awaitables, async I/O, RESP parsing

1. Learning Objectives

  1. Build awaitable wrappers for async network calls.
  2. Implement a coroutine task<T> type.
  3. Parse RESP frames reliably under partial reads.
  4. Produce a clean, synchronous-looking API that is truly async.

2. All Theory Needed (Per-Concept Breakdown)

2.1 Coroutine Mechanics

Description

C++20 coroutines compile to state machines with a coroutine frame, managed by a promise type.

Definitions & Key Terms

  • Coroutine frame: storage for suspended state.
  • Promise type: defines behavior and return object.

Mental Model Diagram (ASCII)

caller -> coroutine -> suspend -> resume

Minimal Concrete Example

struct task { struct promise_type { /*...*/ }; };

Where You’ll Apply It

  • Implementing task<T> and coroutine return paths

2.2 Awaitables and Event Loops

Description

Awaitables connect coroutines to async I/O. await_suspend starts an async operation and resumes the coroutine when done.

Minimal Concrete Example

void await_suspend(std::coroutine_handle<> h) {
    async_read(sock, buf, [h]{ h.resume(); });
}

Where You’ll Apply It

  • Wrapping connect, read, write

2.3 RESP Protocol

Description

RESP encodes Redis commands and replies with explicit framing. Clients must handle partial reads.

Mental Model Diagram (ASCII)

*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n

Where You’ll Apply It

  • Command serialization and response parsing

3. Project Specification

3.1 What You Will Build

A coroutine-based Redis client with connect, get, set, del, and a minimal RESP parser.

3.2 Functional Requirements

  1. Async connect, read, write.
  2. Serialize commands into RESP arrays.
  3. Parse simple bulk strings, integers, and simple strings.

3.3 Non-Functional Requirements

  • Non-blocking I/O
  • Clear error handling
  • Clean shutdown

3.4 Example Usage / Output

RedisClient client;
co_await client.connect("127.0.0.1", 6379);
co_await client.set("user:1", "alice");

3.5 Data Formats / Schemas / Protocols

  • RESP2 framing: *<n>\r\n$<len>\r\n<data>\r\n

3.6 Edge Cases

  • Partial frames
  • Server disconnect
  • Timeout

3.7 Real World Outcome

3.7.1 How to Run

cmake -S . -B build
cmake --build build
./build/redis_client_demo

3.7.2 Golden Path Demo

[CONNECT] 127.0.0.1:6379
[CMD] SET user:1 alice
[RESP] +OK
[CMD] GET user:1
[RESP] $5 alice

3.7.3 Failure Demo

[ERROR] server closed connection

4. Solution Architecture

4.1 High-Level Design

[Coroutine API] -> [Awaitables] -> [Async I/O] -> [RESP Parser]

4.2 Key Components

| Component | Responsibility | Key Decisions | |—|—|—| | task | coroutine return type | ownership of frame | | awaitables | async bridge | resume semantics | | RESP parser | framing | incremental parsing |


5. Implementation Guide

5.1 Development Environment Setup

cmake -S . -B build
cmake --build build

5.2 Project Structure

redis-async/
├── src/client.hpp
├── src/resp.hpp
├── src/resp.cpp
├── tests/resp_tests.cpp
└── CMakeLists.txt

5.3 The Core Question You’re Answering

“How do I make async network I/O look like normal sequential code?”

5.4 Concepts You Must Understand First

  • Coroutine promise types
  • Awaitable protocol
  • TCP stream framing

5.5 Questions to Guide Your Design

  • How is the coroutine frame destroyed?
  • What happens if a coroutine is abandoned early?
  • How will you parse partial RESP frames?

5.6 Thinking Exercise

Draw a state machine for parsing RESP bulk strings from a stream.

5.7 The Interview Questions They’ll Ask

  1. What is await_suspend responsible for?
  2. How do coroutines differ from threads?
  3. What is the reactor pattern?

5.8 Hints in Layers

Hint 1: Start with a minimal task<void>. Hint 2: Implement a small RESP parser that consumes a buffer incrementally. Hint 3: Log every state transition during parsing.

5.9 Books That Will Help

| Topic | Book | Chapter | |—|—|—| | Coroutines | A Tour of C++ | Coroutines section | | Networking | TCP/IP Illustrated | TCP stream behavior | | Async design | The C++ Programming Language | Concurrency chapter |

5.10 Implementation Phases

Phase 1: RESP Parser (4-5 days)

  • Parse simple strings, integers, bulk strings.
  • Checkpoint: unit tests pass.

Phase 2: Coroutine Task (4-5 days)

  • Implement task<T> and awaitables.
  • Checkpoint: coroutine test runs.

Phase 3: Client Integration (5-7 days)

  • Connect I/O + RESP.
  • Checkpoint: demo can GET/SET.

5.11 Key Implementation Decisions

| Decision | Options | Recommendation | Rationale | |—|—|—|—| | Task ownership | shared / unique | unique | clear lifetime | | Parser style | recursive / state machine | state machine | handles partial reads |


6. Testing Strategy

6.1 Test Categories

| Category | Purpose | Examples | |—|—|—| | Parser | frame parsing | partial read tests | | Integration | real Redis | GET/SET |


7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

| Pitfall | Symptom | Solution | |—|—|—| | Forgetting to resume | coroutine stalls | call resume in callback | | Blocking I/O | thread hangs | use async ops only |


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add PING and INCR commands.

8.2 Intermediate Extensions

  • Add pipelining support.

8.3 Advanced Extensions

  • Add RESP3 support.

9. Real-World Connections

9.1 Industry Applications

  • Redis clients in high-throughput services
  • Async microservices

10. Resources

10.1 Essential Reading

  • C++20 coroutine docs
  • Redis protocol specification

11. Self-Assessment Checklist

  • I can explain coroutine frames.
  • I can parse RESP incrementally.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Connect + GET/SET

Full Completion:

  • Clean error handling and shutdown

Excellence:

  • Pipelining and RESP3