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
- Build awaitable wrappers for async network calls.
- Implement a coroutine
task<T>type. - Parse RESP frames reliably under partial reads.
- 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
- Async connect, read, write.
- Serialize commands into RESP arrays.
- 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
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
- What is
await_suspendresponsible for? - How do coroutines differ from threads?
- 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
PINGandINCRcommands.
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