Skip to content

Commit a9e03d5

Browse files
Merge pull request #12 from varunrmantri23/feature/replication-system
Implement Primary-Replica Replication System
2 parents 13a62c2 + abd2f36 commit a9e03d5

7 files changed

Lines changed: 869 additions & 38 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ TEST_BIN_DIR = $(BIN_DIR)/tests
1111

1212
# Source file handling
1313
SRC = $(wildcard $(SRC_DIR)/*.c)
14-
OBJ = $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
14+
OBJ = $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRC))
1515
EXECUTABLE = $(BIN_DIR)/crimsoncache
1616

1717
# Test file handling

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ PING
7171
+PONG
7272
```
7373

74-
### Method 1: Using Telnet
74+
### Method 2: Using Telnet
7575

7676
```bash
7777
telnet localhost 6379

src/commands.c

Lines changed: 197 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#define _POSIX_C_SOURCE 200809L
22
#include "commands.h"
33
#include "persistence.h"
4+
#include "replication.h"
45
#include <string.h>
56
#include <strings.h>
67
#include <ctype.h>
@@ -9,6 +10,12 @@
910
#include <unistd.h>
1011
#include <time.h>
1112
#include <sys/time.h>
13+
#include <arpa/inet.h>
14+
#include <pthread.h>
15+
#include "crimsoncache.h"
16+
17+
extern void track_command_change(void);
18+
extern volatile sig_atomic_t server_running;
1219

1320
// fallback implementations if not available
1421
#ifndef HAVE_STRDUP
@@ -45,6 +52,10 @@ static command_def commands[] = {
4552
{"ttl", ttl_command, 2, 2},
4653
{"save", save_command, 1, 1},
4754
{"bgsave", bgsave_command, 1, 1},
55+
{"replicaof", replicaof_command, 3, 3},
56+
{"role", role_command, 1, 1},
57+
{"incr", incr_command, 2, 2},
58+
{"replconf", replconf_command, 2, -1},
4859
{NULL, NULL, 0, 0} // sentinel to mark end of array
4960
};
5061

@@ -219,14 +230,23 @@ cmd_result execute_command(int client_sock, char *input, dict *db) {
219230
result = CMD_ERR;
220231
}
221232

222-
// track changes for write commands
223-
if (result == CMD_OK && (
224-
strcasecmp(argv[0], "set") == 0 ||
225-
strcasecmp(argv[0], "del") == 0 ||
226-
strcasecmp(argv[0], "expire") == 0)) {
227-
track_command_change();
233+
// Add this after applying the commands but before free_tokens
234+
// Propagate write commands to replicas
235+
if (result == CMD_OK && server_repl.role == ROLE_PRIMARY && client_sock >= 0) {
236+
// Commands that modify data
237+
if (strcasecmp(argv[0], "set") == 0 ||
238+
strcasecmp(argv[0], "del") == 0 ||
239+
strcasecmp(argv[0], "expire") == 0 ||
240+
strcasecmp(argv[0], "incr") == 0) {
241+
242+
// Track for persistence
243+
track_command_change();
244+
245+
// Propagate to replicas with proper formatting
246+
replication_feed_slaves(input, strlen(input));
247+
}
228248
}
229-
249+
230250
free_tokens(argv, argc);
231251
return result;
232252
}
@@ -437,4 +457,173 @@ cmd_result bgsave_command(int client_sock, int argc, char **argv, dict *db) {
437457
reply_error(client_sock, "ERR failed to start background save");
438458
return CMD_ERR;
439459
}
440-
}
460+
}
461+
462+
// replicaof command - configure server as replica of another or as primary
463+
cmd_result replicaof_command(int client_sock, int argc, char **argv, dict *db) {
464+
(void)argc; // unused
465+
(void)db; // unused
466+
467+
// replicaof no one = become primary
468+
if (strcasecmp(argv[1], "NO") == 0 && strcasecmp(argv[2], "ONE") == 0) {
469+
replication_unset_primary();
470+
reply_string(client_sock, "OK");
471+
return CMD_OK;
472+
}
473+
474+
// replicaof host port = become replica
475+
const char *host = argv[1];
476+
int port = atoi(argv[2]);
477+
478+
if (port <= 0 || port > 65535) {
479+
reply_error(client_sock, "ERR invalid port");
480+
return CMD_ERR;
481+
}
482+
483+
if (replication_set_primary(host, port)) {
484+
reply_string(client_sock, "OK");
485+
return CMD_OK;
486+
} else {
487+
reply_error(client_sock, "ERR couldn't connect to primary");
488+
return CMD_ERR;
489+
}
490+
}
491+
492+
// role command - return role of server (primary or replica)
493+
cmd_result role_command(int client_sock, int argc, char **argv, dict *db) {
494+
(void)argc; // unused
495+
(void)argv; // unused
496+
(void)db; // unused
497+
498+
char response[1024];
499+
500+
if (server_repl.role == ROLE_PRIMARY) {
501+
// format: *3\r\n$6\r\nmaster\r\n:<repl_offset>\r\n*<num_replicas>\r\n...
502+
int written = snprintf(response, sizeof(response),
503+
"*3\r\n$6\r\nmaster\r\n:%lu\r\n*%d\r\n",
504+
server_repl.repl_offset,
505+
count_replicas());
506+
507+
// add replica info
508+
pthread_mutex_lock(&server_repl.replicas_mutex);
509+
replica_t *curr = server_repl.replicas;
510+
while (curr && written < (int)sizeof(response) - 100) {
511+
written += snprintf(response + written, sizeof(response) - written,
512+
"*3\r\n$%zu\r\n%s\r\n:%d\r\n:%ld\r\n",
513+
strlen(curr->ip), curr->ip, curr->port,
514+
time(NULL) - curr->last_ack_time);
515+
curr = curr->next;
516+
}
517+
pthread_mutex_unlock(&server_repl.replicas_mutex);
518+
519+
} else {
520+
// format: *5\r\n$5\r\nslave\r\n$<host_len>\r\n<host>\r\n:<port>\r\n$<state_len>\r\n<state>\r\n:<offset>\r\n
521+
snprintf(response, sizeof(response),
522+
"*5\r\n$5\r\nslave\r\n$%zu\r\n%s\r\n:%d\r\n$%zu\r\n%s\r\n:%lu\r\n",
523+
strlen(server_repl.primary_host), server_repl.primary_host,
524+
server_repl.primary_port,
525+
strlen(server_repl.state == REPL_STATE_CONNECTED ? "connected" : "connecting"),
526+
server_repl.state == REPL_STATE_CONNECTED ? "connected" : "connecting",
527+
server_repl.repl_offset);
528+
}
529+
530+
write(client_sock, response, strlen(response));
531+
return CMD_OK;
532+
}
533+
534+
// INCR command implementation
535+
cmd_result incr_command(int client_sock, int argc, char **argv, dict *db) {
536+
(void)argc; // unused parameter
537+
538+
const char *key = argv[1];
539+
540+
// Get current value
541+
cc_obj *obj = dict_get(db, key);
542+
long long value = 0;
543+
544+
if (obj) {
545+
// Key exists, check if it's a string we can convert to number
546+
if (obj->type != CC_STRING) {
547+
reply_error(client_sock, "ERR value is not an integer or out of range");
548+
return CMD_ERR;
549+
}
550+
551+
// Try to parse as integer
552+
char *endptr;
553+
value = strtoll((char*)obj->ptr, &endptr, 10);
554+
555+
if (*endptr != '\0') {
556+
reply_error(client_sock, "ERR value is not an integer or out of range");
557+
return CMD_ERR;
558+
}
559+
}
560+
561+
// Increment the value
562+
value++;
563+
564+
// Convert back to string
565+
char new_val[32];
566+
snprintf(new_val, sizeof(new_val), "%lld", value);
567+
568+
// Create new object
569+
cc_obj *new_obj = malloc(sizeof(cc_obj));
570+
if (!new_obj) {
571+
reply_error(client_sock, "ERR out of memory");
572+
return CMD_ERR;
573+
}
574+
575+
new_obj->ptr = strdup(new_val);
576+
if (!new_obj->ptr) {
577+
free(new_obj);
578+
reply_error(client_sock, "ERR out of memory");
579+
return CMD_ERR;
580+
}
581+
582+
new_obj->type = CC_STRING;
583+
new_obj->expire = obj ? obj->expire : 0; // Preserve expiry if exists
584+
new_obj->size = strlen(new_val) + 1;
585+
new_obj->last_access = current_time_ms();
586+
587+
if (dict_add(db, key, new_obj)) {
588+
reply_integer(client_sock, value);
589+
return CMD_OK;
590+
} else {
591+
free(new_obj->ptr);
592+
free(new_obj);
593+
reply_error(client_sock, "ERR could not set key");
594+
return CMD_ERR;
595+
}
596+
}
597+
598+
// implement the REPLCONF command handler
599+
cmd_result replconf_command(int client_sock, int argc, char **argv, dict *db) {
600+
(void)db; // unused
601+
602+
// handle REPLCONF listening-port <port>
603+
if (argc >= 3 && strcasecmp(argv[1], "listening-port") == 0) {
604+
int port = atoi(argv[2]);
605+
606+
printf("Received REPLCONF listening-port %d from replica\n", port);
607+
608+
// get client IP address
609+
struct sockaddr_in addr;
610+
socklen_t addr_len = sizeof(addr);
611+
getpeername(client_sock, (struct sockaddr*)&addr, &addr_len);
612+
char ip[INET_ADDRSTRLEN];
613+
inet_ntop(AF_INET, &addr.sin_addr, ip, INET_ADDRSTRLEN);
614+
615+
// add replica to the list (which will perform initial sync)
616+
add_replica(client_sock, ip, port);
617+
618+
reply_string(client_sock, "OK");
619+
return CMD_OK;
620+
}
621+
622+
// handle other REPLCONF commands
623+
reply_string(client_sock, "OK");
624+
return CMD_OK;
625+
}
626+
627+
628+
629+

src/commands.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,9 @@ cmd_result expire_command(int client_sock, int argc, char **argv, dict *db);
5151
cmd_result ttl_command(int client_sock, int argc, char **argv, dict *db);
5252
cmd_result save_command(int client_sock, int argc, char **argv, dict *db);
5353
cmd_result bgsave_command(int client_sock, int argc, char **argv, dict *db);
54+
cmd_result replicaof_command(int client_sock, int argc, char **argv, dict *db);
55+
cmd_result role_command(int client_sock, int argc, char **argv, dict *db);
56+
cmd_result incr_command(int client_sock, int argc, char **argv, dict *db);
57+
cmd_result replconf_command(int client_sock, int argc, char **argv, dict *db);
5458

5559
#endif /* COMMANDS_H */

0 commit comments

Comments
 (0)