Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Background

Table of contents

  1. Code
    1. Files you will modify
    2. Files you should read but not modify
  2. RPC framework
    1. Server dispatch
    2. RpcClient
    3. Defining RPCs
  3. KV types
    1. version_t
    2. kv_err_t
    3. kv_client_t vtable
  4. Persister
  5. Network simulation

Code

.
├── Makefile
├── lib/                           # Infrastructure library (provided)
│   ├── rpc.c / rpc.h              # RPC framework
│   ├── persister.c / persister.h  # Crash-safe file persistence
│   ├── kv_types.c / kv_types.h    # KV types (version_t, kv_err_t, kv_client_t)
│   ├── hash.c / hash.h            # Pintos hash table (for your server state)
│   ├── list.c / list.h            # Pintos intrusive list (used by hash)
│   └── cjson/                     # JSON parser (for RPC bodies)
├── bin/
│   ├── kv_server.c                # Single-server entrypoint (provided)
│   └── raft_server.c              # KVRaft peer entrypoint (provided)
├── src/
│   ├── lock.c / lock.h            # Part 2 & 4: Distributed lock
│   ├── kv_single/
│   │   ├── server.c / server.h    # Part 1: KV server
│   │   └── client.c / client.h    # Part 1: KV client
│   └── kv_raft/
│       ├── raft.c / raft.h        # Part 3: Raft consensus
│       ├── rsm.c / rsm.h          # Part 4: Replicated state machine
│       └── client.c / client.h    # Part 4: Cluster client
└── tests/
    ├── kvsrv_test.c               # Part 1-2 tests
    ├── raft_test.c                # Part 3 tests
    ├── kvraft_test.c              # Part 4 tests
    ├── network_proxy.c/.h         # Test network proxy
    └── test_common.c/.h           # Test harness

Files you will modify

FilePartWhat to implement
src/kv_single/server.c1KV server with versioned puts
src/kv_single/client.c1Client with retry logic
src/lock.c2, 4Distributed lock using KV
src/kv_raft/raft.c3Raft consensus algorithm
src/kv_raft/rsm.c4Replicated state machine
src/kv_raft/client.c4Cluster-aware client

Files you should read but not modify

FileDescription
lib/rpc.hRPC framework: rpc_dispatch_fn, rpc_client_t
lib/kv_types.hKV types (version_t, kv_err_t, kv_client_t)
lib/persister.hCrash-safe file persistence
tests/test_common.*, tests/network_proxy.*Test infrastructure

RPC framework

Your servers and clients communicate over HTTP using a simple RPC framework defined in lib/rpc.h.

Server dispatch

Every server exposes a dispatch function of type:

typedef char *(*rpc_dispatch_fn)(void *ctx, const char *method,
                                 const char *body, int client_id);

The framework routes POST /:method HTTP requests to dispatch(ctx, method, body, client_id). Your dispatch parses the JSON body, processes the request, and returns a malloc’d JSON response string (the framework frees it).

You start an RPC server with:

uint16_t port = rpc_server_start(ctx, dispatch);

RpcClient

Clients send RPCs using rpc_client_t:

rpc_client_t *rpc_client_new(const char *url, int client_id);
char *rpc_call(rpc_client_t *c, const char *method, const char *body);
char *rpc_call_timeout(rpc_client_t *c, const char *method,
                       const char *body, int timeout_sec);

rpc_call returns NULL when the network drops the request or the reply. Your client must handle this.

Defining RPCs

There is no .proto file or code generation. You define your own RPC types and serialize them to JSON with cJSON:

// Typed form on the stack (optional, for readability)
typedef struct {
    char *key;
} get_args_t;

// Build the JSON on the wire
cJSON *j = cJSON_CreateObject();
cJSON_AddStringToObject(j, "key", key);
char *body = cJSON_PrintUnformatted(j);
cJSON_Delete(j);

// Send it
char *reply = rpc_call(endpoint, "get", body);

Your dispatch function pattern-matches on the method name and parses the body back:

char *kv_server_dispatch(void *ctx, const char *method,
                         const char *body, int client_id) {
    if (strcmp(method, "get") == 0) {
        cJSON *args = cJSON_Parse(body);
        // ... build reply ...
        cJSON_Delete(args);
        return reply;  // malloc'd
    }
    // ...
}

KV types

lib/kv_types.h provides shared types for the KV service:

version_t

typedef uint64_t version_t;

Each key has a version number. Version 0 means the key does not exist. The first successful put sets it to 1, and it increments on each subsequent successful put.

kv_err_t

typedef enum {
    KV_OK = 0,       // Operation succeeded
    KV_NO_KEY,       // Key does not exist
    KV_VERSION,      // Put's version didn't match server's version
    KV_MAYBE,        // Client retried; the put may or may not have applied
    KV_WRONG_LEADER  // Not the Raft leader; try another server
} kv_err_t;

Helpers kv_err_to_str and kv_err_from_str convert to/from the wire strings "OK", "NoKey", "Version", "Maybe", "WrongLeader".

kv_client_t vtable

typedef struct kv_client {
    void *ctx;
    void (*get)(void *ctx, const char *key,
                char **out_value, version_t *out_version, kv_err_t *out_err);
    kv_err_t (*put)(void *ctx, const char *key,
                    const char *value, version_t ver);
} kv_client_t;

Both the single-server client (Part 1) and the Raft client (Part 4) expose this vtable via a ..._as_kv_client helper. The lock_t (Part 2, 4) takes a kv_client_t *, so the same lock code works with either backend.


Persister

lib/persister.h provides crash-safe file persistence for Raft state:

typedef struct { char path[512]; } persister_t;

void persister_init(persister_t *p, const char *path);
void persister_save(persister_t *p, const char *data);  // write-tmp + rename
char *persister_read(persister_t *p);                   // NULL if file missing; caller frees

You will use this in Part 3 to persist Raft state (term, vote, log) so that servers can recover after crashes.


Network simulation

The test harness interposes a network proxy between clients and servers. The proxy can:

  • Drop requests: simulates request loss
  • Drop replies: simulates reply loss (server processed the request, but client doesn’t know)
  • Add delays: simulates network latency
  • Block specific clients: simulates network partitions

Tests run in two network modes:

  • Reliable (network_config_reliable()): no drops or delay
  • Unreliable (network_config_unreliable()): 50% drop rate for both requests and replies, plus small random delays

Your implementation must handle both correctly.