cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
cwist_redis.c
Go to the documentation of this file.
1
5#define _POSIX_C_SOURCE 200809L
11#include <stdio.h>
12#include <stdlib.h>
13#include <stdbool.h>
14#include <stdint.h>
15#include <string.h>
16#include <limits.h>
17#include <time.h>
18#include <unistd.h>
19#include <errno.h>
20#include <fcntl.h>
21#include <netdb.h>
22#include <poll.h>
23#include <sys/types.h>
24#include <sys/socket.h>
25#include <netinet/in.h>
26#include <arpa/inet.h>
27#include <pthread.h>
28
29#define CWIST_REDIS_BUF_SIZE 65536
30#define CWIST_REDIS_LINE_MAX 8192
31/* Maximum accepted bulk-string byte count (512 MiB). Redis's own protocol
32 * limit is 512 MiB; cap here so a rogue server can't force a huge alloc. */
33#define CWIST_REDIS_MAX_BULK_BYTES ((long long)(512 * 1024 * 1024))
34/* Reply-tree guards: XREADGROUP-style replies nest ~4 levels; anything far
35 * beyond that (or an absurd element count) means a rogue server. */
36#define CWIST_REDIS_MAX_REPLY_DEPTH 16
37#define CWIST_REDIS_MAX_REPLY_ELEMENTS (1 << 20)
38
39/* Limits for the deadline-bounded pooled command. Tighter than the legacy
40 * parser: a timed caller (e.g. a per-request rate limiter) never needs huge
41 * replies, and every byte must arrive inside the caller's budget anyway. */
42#define CWIST_REDIS_TIMED_MAX_ARGC ((size_t)1 << 20)
43#define CWIST_REDIS_TIMED_MAX_FRAME ((size_t)512 * 1024 * 1024)
44#define CWIST_REDIS_TIMED_MAX_DEPTH 8
45#define CWIST_REDIS_TIMED_MAX_NODES ((size_t)65536)
46#define CWIST_REDIS_TIMED_MAX_BULK ((size_t)8 * 1024 * 1024)
47#define CWIST_REDIS_TIMED_MAX_PAYLOAD ((size_t)16 * 1024 * 1024)
48
49/* Timed sends never raise SIGPIPE (Apple sockets also get SO_NOSIGPIPE). */
50#ifdef MSG_NOSIGNAL
51#define CWIST_REDIS_NOSIGNAL MSG_NOSIGNAL
52#else
53#define CWIST_REDIS_NOSIGNAL 0
54#endif
55
56struct cwist_redis {
57 int fd;
58 char *host;
59 int port;
60 char recv_buf[CWIST_REDIS_BUF_SIZE];
61 size_t recv_len;
63};
64
65struct cwist_redis_pool {
66 char *host;
67 int port;
68 size_t max_conns;
69 cwist_redis_t **conns;
70 bool *available;
72 pthread_cond_t cond;
73 /* Process that created the pool; timed commands from any other process
74 * (a fork() child) are rejected because the sockets are shared. */
75 pid_t owner_pid;
76 /* Peer address resolved at creation, reused for reconnects so request
77 * paths never perform DNS. peer_len == 0 means unknown (no reconnect). */
78 struct sockaddr_storage peer;
79 socklen_t peer_len;
80 /* Wake pipe for timed acquirers: they poll() the read end because
81 * condition-variable and nanosleep timeouts can be coalesced by tens of
82 * milliseconds (observed ~70-150 ms on background-QoS macOS) while poll()
83 * timeouts stay precise. Releases write one byte while timed_waiters > 0.
84 * -1 when unavailable; timed waits then fall back to the condition. */
85 int wake_rd;
86 int wake_wr;
87 size_t timed_waiters;
88};
89
90/* --- Socket helpers ----------------------------------------------------- */
91
96static int redis_open_socket(const char *host, int port) {
97 struct addrinfo hints = {0}, *res = NULL;
98 hints.ai_family = AF_INET;
99 hints.ai_socktype = SOCK_STREAM;
100 char port_str[16];
101 snprintf(port_str, sizeof(port_str), "%d", port);
102 if (getaddrinfo(host, port_str, &hints, &res) != 0 || !res) return -1;
103
104 int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
105 if (fd < 0) {
106 freeaddrinfo(res);
107 return -1;
108 }
109 if (connect(fd, res->ai_addr, res->ai_addrlen) != 0) {
110 close(fd);
111 freeaddrinfo(res);
112 return -1;
113 }
114 freeaddrinfo(res);
115 return fd;
116}
117
118// codeql[cpp/cleartext-transmission]
121static int redis_send_all(int fd, const char *buf, size_t len) {
122 size_t sent = 0;
123 while (sent < len) {
124 /* RESP speaks plaintext TCP by design (same as redis-cli without
125 * --tls). Credentials must be protected at the deployment layer:
126 * loopback/Unix sockets, private network, or a TLS-terminating proxy
127 * such as stunnel/spiped in front of Redis. */
128 ssize_t n = send(fd, buf + sent, len - sent, 0); // codeql[cpp/cleartext-transmission]
129 if (n < 0) {
130 if (errno == EINTR) continue;
131 return -1;
132 }
133 if (n == 0) return -1;
134 sent += (size_t)n;
135 }
136 return 0;
137}
138
145static int redis_recv_line(cwist_redis_t *r, char *line, size_t line_len) {
146 size_t i = 0;
147 while (1) {
148 if (r->recv_len > 0) {
149 while (r->recv_len > 0 && i < line_len - 1) {
150 char c = r->recv_buf[0];
151 /* simple shift */
152 memmove(r->recv_buf, r->recv_buf + 1, r->recv_len - 1);
153 r->recv_len--;
154 if (c == '\n') {
155 if (i > 0 && line[i - 1] == '\r') i--;
156 line[i] = '\0';
157 return 0;
158 }
159 line[i++] = c;
160 }
161 if (i >= line_len - 1) return -1;
162 }
163 ssize_t n =
164 recv(r->fd, r->recv_buf + r->recv_len, CWIST_REDIS_BUF_SIZE - r->recv_len - 1, 0);
165 if (n < 0) {
166 if (errno == EINTR) continue;
167 return -1;
168 }
169 if (n == 0) return -1;
170 r->recv_len += (size_t)n;
171 }
172}
173
178static int redis_recv_bytes(cwist_redis_t *r, char *out, size_t len) {
179 size_t got = 0;
180 while (got < len) {
181 if (r->recv_len > 0) {
182 size_t take = r->recv_len < (len - got) ? r->recv_len : (len - got);
183 memcpy(out + got, r->recv_buf, take);
184 memmove(r->recv_buf, r->recv_buf + take, r->recv_len - take);
185 r->recv_len -= take;
186 got += take;
187 continue;
188 }
189 ssize_t n =
190 recv(r->fd, r->recv_buf + r->recv_len, CWIST_REDIS_BUF_SIZE - r->recv_len - 1, 0);
191 if (n < 0) {
192 if (errno == EINTR) continue;
193 return -1;
194 }
195 if (n == 0) return -1;
196 r->recv_len += (size_t)n;
197 }
198 return 0;
199}
200
201/* --- Deadline-bounded socket helpers ------------------------------------ */
202
204static uint64_t redis_now_ns(void) {
205 struct timespec ts;
207 return (uint64_t)ts.tv_sec * 1000000000u + (uint64_t)ts.tv_nsec;
208}
209
212static int redis_remaining_ms(uint64_t deadline) {
213 uint64_t now = redis_now_ns();
214 if (now >= deadline) return 0;
215 uint64_t ms = (deadline - now + 999999u) / 1000000u;
216 return ms > (uint64_t)INT_MAX ? INT_MAX : (int)ms;
217}
218
220static cwist_error_t redis_code_error(int code) {
222 err.error.err_i16 = (int16_t)code;
223 return err;
224}
225
229static int redis_wait_fd(int fd, short events, uint64_t deadline) {
230 for (;;) {
231 int ms = redis_remaining_ms(deadline);
232 if (ms <= 0) return -ETIMEDOUT;
233 struct pollfd p = {.fd = fd, .events = events, .revents = 0};
234 int n = poll(&p, 1, ms);
235 if (n > 0) return 0;
236 if (n < 0 && errno != EINTR) return -EIO;
237 }
238}
239
245static int redis_send_deadline(int fd, const char *buf, size_t len, uint64_t deadline) {
246 size_t sent = 0;
247 while (sent < len) {
248 if (redis_now_ns() >= deadline) return -ETIMEDOUT;
249 ssize_t n = send(fd, buf + sent, len - sent, CWIST_REDIS_NOSIGNAL);
250 if (n > 0) {
251 sent += (size_t)n;
252 continue;
253 }
254 if (n < 0 && errno == EINTR) continue;
255 if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS)) {
256 int rc = redis_wait_fd(fd, POLLOUT, deadline);
257 if (rc != 0) return rc;
258 continue;
259 }
260 return -EIO;
261 }
262 return 0;
263}
264
268static int redis_fill_deadline(cwist_redis_t *r, uint64_t deadline) {
269 if (r->recv_len >= CWIST_REDIS_BUF_SIZE) return -EPROTO;
270 for (;;) {
271 if (redis_now_ns() >= deadline) return -ETIMEDOUT;
272 ssize_t n = recv(r->fd, r->recv_buf + r->recv_len, CWIST_REDIS_BUF_SIZE - r->recv_len, 0);
273 if (n > 0) {
274 r->recv_len += (size_t)n;
275 return 0;
276 }
277 if (n == 0) return -EIO;
278 if (errno == EINTR) continue;
279 if (errno == EAGAIN || errno == EWOULDBLOCK) {
280 int rc = redis_wait_fd(r->fd, POLLIN, deadline);
281 if (rc != 0) return rc;
282 continue;
283 }
284 return -EIO;
285 }
286}
287
289static void redis_consume(cwist_redis_t *r, size_t n) {
290 memmove(r->recv_buf, r->recv_buf + n, r->recv_len - n);
291 r->recv_len -= n;
292}
293
299static int redis_peek_line(cwist_redis_t *r, uint64_t deadline, size_t *out_len) {
300 size_t scanned = 0;
301 for (;;) {
302 char *nl = memchr(r->recv_buf + scanned, '\n', r->recv_len - scanned);
303 if (nl) {
304 size_t end = (size_t)(nl - r->recv_buf);
305 if (end == 0 || r->recv_buf[end - 1] != '\r') return -EPROTO;
306 size_t len = end - 1;
307 if (len >= CWIST_REDIS_LINE_MAX) return -EPROTO;
308 if (memchr(r->recv_buf, '\r', len)) return -EPROTO;
309 *out_len = len;
310 return 0;
311 }
312 scanned = r->recv_len;
313 if (r->recv_len > CWIST_REDIS_LINE_MAX) return -EPROTO;
314 int rc = redis_fill_deadline(r, deadline);
315 if (rc != 0) return rc;
316 }
317}
318
320static int redis_read_exact(cwist_redis_t *r, char *out, size_t len, uint64_t deadline) {
321 size_t got = 0;
322 while (got < len) {
323 if (r->recv_len == 0) {
324 int rc = redis_fill_deadline(r, deadline);
325 if (rc != 0) return rc;
326 }
327 size_t take = r->recv_len < len - got ? r->recv_len : len - got;
328 memcpy(out + got, r->recv_buf, take);
329 redis_consume(r, take);
330 got += take;
331 }
332 return 0;
333}
334
336static bool redis_parse_ll(const char *s, size_t n, long long *out) {
337 size_t i = 0;
338 bool neg = n > 0 && s[0] == '-';
339 if (neg) i = 1;
340 if (i >= n) return false;
341 unsigned long long limit =
342 neg ? (unsigned long long)LLONG_MAX + 1u : (unsigned long long)LLONG_MAX;
343 unsigned long long v = 0;
344 for (; i < n; i++) {
345 if (s[i] < '0' || s[i] > '9') return false;
346 unsigned d = (unsigned)(s[i] - '0');
347 if (v > (limit - d) / 10u) return false;
348 v = v * 10u + d;
349 }
350 if (!neg)
351 *out = (long long)v;
352 else if (v == (unsigned long long)LLONG_MAX + 1u)
353 *out = LLONG_MIN;
354 else
355 *out = -(long long)v;
356 return true;
357}
358
362static void redis_conn_discard(cwist_redis_t *r) {
363 if (r->fd >= 0) {
364 shutdown(r->fd, SHUT_RDWR);
365 close(r->fd);
366 }
367 r->fd = -1;
368 r->recv_len = 0;
369}
370
373static bool redis_conn_unclean(const cwist_redis_t *r) {
374 if (r->recv_len != 0) return true;
375 struct pollfd p = {.fd = r->fd, .events = POLLIN, .revents = 0};
376 int n;
377 do {
378 n = poll(&p, 1, 0);
379 } while (n < 0 && errno == EINTR);
380 return n != 0;
381}
382
387static int redis_reconnect(const cwist_redis_pool_t *pool, cwist_redis_t *r, uint64_t deadline) {
388 if (pool->peer_len == 0) return -EIO;
389 int fd = socket(pool->peer.ss_family, SOCK_STREAM, 0);
390 if (fd < 0) return -EIO;
391 int rc = -EIO;
393#ifdef SO_NOSIGPIPE
394 int one = 1;
395 (void)setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
396#endif
397 int flags = fcntl(fd, F_GETFL, 0);
398 if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) goto out;
399 if (connect(fd, (const struct sockaddr *)&pool->peer, pool->peer_len) != 0) {
400 if (errno != EINPROGRESS && errno != EINTR) goto out;
401 rc = redis_wait_fd(fd, POLLOUT, deadline);
402 if (rc != 0) goto out;
403 rc = -EIO;
404 int soerr = 0;
405 socklen_t sl = sizeof(soerr);
406 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerr, &sl) != 0 || soerr != 0) goto out;
407 }
408 if (fcntl(fd, F_SETFL, flags) != 0) goto out;
409 r->fd = fd;
410 r->recv_len = 0;
411 return 0;
412out:
413 close(fd);
414 return rc;
415}
416
417/* --- RESP2 helpers ------------------------------------------------------ */
418
420
424static int write_bulk_string(cwist_sstring *out, const char *s) {
425 char prefix[32];
426 size_t len = s ? strlen(s) : 0;
427 snprintf(prefix, sizeof(prefix), "$%zu\r\n", len);
428 cwist_sstring_append(out, prefix);
429 if (len) cwist_sstring_append(out, s);
430 cwist_sstring_append(out, "\r\n");
431 return 0;
432}
433
435static void flatten_reply(const cwist_redis_reply_t *tree, char **out_value, size_t *out_len) {
436 if (out_value) *out_value = NULL;
437 if (out_len) *out_len = 0;
438 if (!tree) return;
439 if (out_value) {
440 if ((tree->type == '+' || tree->type == ':') && tree->str) {
441 *out_value = cwist_strdup(tree->str);
442 if (out_len) *out_len = strlen(tree->str);
443 } else if (tree->type == '$' && tree->str) {
444 char *buf = cwist_alloc(tree->len + 1);
445 if (!buf) return; /* legacy contract: allocation failure yields NULL */
446 memcpy(buf, tree->str, tree->len);
447 buf[tree->len] = '\0';
448 *out_value = buf;
449 if (out_len) *out_len = tree->len;
450 }
451 } else if (out_len && tree->str) {
452 *out_len = tree->len;
453 }
454}
455
458static cwist_error_t redis_read_resp(cwist_redis_t *r, int depth, cwist_redis_reply_t **out) {
460 cwist_redis_reply_t *node = cwist_alloc(sizeof(*node));
461 if (!node) return make_error(CWIST_ERR_INT16);
462 *out = node;
463 if (redis_recv_line(r, line, sizeof(line)) != 0) goto fail;
464 node->type = line[0];
465
466 switch (line[0]) {
467 case '+': /* simple string */
468 case '-': /* error */
469 node->str = cwist_strdup(line + 1);
470 if (!node->str) goto fail;
471 node->len = strlen(line + 1);
472 /* Legacy behavior: a '-' reply is signalled through the reply
473 * tree (type '-') with a zeroed error value. */
474 return (line[0] == '-')
476 : (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0};
477
478 case ':': { /* integer */
479 char *end = NULL;
480 long long v = strtoll(line + 1, &end, 10);
481 if (end == line + 1 || *end != '\0') goto fail;
482 node->str = cwist_strdup(line + 1);
483 if (!node->str) goto fail;
484 node->len = strlen(line + 1);
485 node->integer = v;
486 return (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0};
487 }
488
489 case '$': { /* bulk string */
490 char *end = NULL;
491 long long blen = strtoll(line + 1, &end, 10);
492 if (end == line + 1 || *end != '\0') goto fail;
493 if (blen < 0) return (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0};
494 /* Guard against a rogue/malicious server advertising a huge bulk
495 * string that would exhaust memory before we read a single byte. */
497 char *buf = cwist_alloc((size_t)blen + 1);
498 if (!buf) goto fail;
499 if (redis_recv_bytes(r, buf, (size_t)blen) != 0) {
500 cwist_free(buf);
501 goto fail;
502 }
503 buf[blen] = '\0';
504 /* consume trailing \r\n */
505 char crlf[2];
506 if (redis_recv_bytes(r, crlf, 2) != 0) {
507 cwist_free(buf);
508 goto fail;
509 }
510 node->str = buf;
511 node->len = (size_t)blen;
512 node->integer = blen;
513 return (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0};
514 }
515
516 case '*': { /* array */
518 char *end = NULL;
519 long long count = strtoll(line + 1, &end, 10);
520 if (end == line + 1 || *end != '\0' || count < 0 ||
522 goto fail;
523 node->elements = (size_t)count;
524 if (count > 0) {
525 node->element = cwist_alloc_array((size_t)count, sizeof(*node->element));
526 if (!node->element) goto fail;
527 for (long long i = 0; i < count; i++) {
528 node->element[i] = NULL;
529 cwist_error_t e = redis_read_resp(r, depth + 1, &node->element[i]);
530 if (e.error.err_i16 != 0) goto fail;
531 }
532 }
533 return (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0};
534 }
535
536 default: goto fail;
537 }
538
539fail:
541 *out = NULL;
543}
544
547static cwist_error_t read_reply(cwist_redis_t *r, char **out_value, size_t *out_len,
548 char *out_type) {
550 cwist_error_t err = redis_read_resp(r, 0, &tree);
551 if (out_type) *out_type = tree ? (char)tree->type : '\0';
552 if (err.error.err_i16 != 0 || !tree) {
554 return err;
555 }
556 char *value = NULL;
557 size_t len = 0;
558 flatten_reply(tree, &value, &len);
559 if (out_value) {
560 *out_value = value;
561 if (out_len) *out_len = len;
562 } else {
563 cwist_free(value);
564 if (out_len) *out_len = len;
565 }
567 return (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0};
568}
569
573 if (!reply) return;
574 if (reply->element) {
575 for (size_t i = 0; i < reply->elements; i++) {
576 cwist_redis_reply_free(reply->element[i]);
577 }
578 cwist_free(reply->element);
579 }
580 cwist_free(reply->str);
582}
583
589static cwist_error_t redis_command_frame_tree(cwist_redis_t *r, const char *frame, size_t frame_len,
591 if (!r || !frame) return make_error(CWIST_ERR_INT16);
592 if (out_tree) *out_tree = NULL;
593 /* A request/reply pair must be serialized as one critical section. Keeping
594 * send and receive separate allows two callers to consume each other's
595 * replies on the same connection. */
596 pthread_mutex_lock(&r->mtx);
597 cwist_error_t err = redis_send_all(r->fd, frame, frame_len) == 0
598 ? redis_read_resp(r, 0, out_tree)
600 pthread_mutex_unlock(&r->mtx);
601 return err;
602}
603
608static cwist_error_t redis_command_frame(cwist_redis_t *r, const char *frame, size_t frame_len,
609 char **out_value, size_t *out_len) {
611 cwist_error_t err = redis_command_frame_tree(r, frame, frame_len, &tree);
612 if (err.error.err_i16 != 0 || !tree) {
614 if (out_value) *out_value = NULL;
615 if (out_len) *out_len = 0;
616 return err;
617 }
618 char *value = NULL;
619 size_t len = 0;
620 flatten_reply(tree, &value, &len);
621 if (out_value) {
622 *out_value = value;
623 if (out_len) *out_len = len;
624 } else {
625 cwist_free(value);
626 if (out_len) *out_len = len;
627 }
629 return (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0};
630}
631
633 const size_t *argv_lens, cwist_redis_reply_t **reply) {
634 if (!r || !argc || !argv || !argv_lens || !reply) return make_error(CWIST_ERR_INT16);
635 *reply = NULL;
637 if (!frame) return make_error(CWIST_ERR_INT16);
638 char count[32];
639 snprintf(count, sizeof(count), "*%zu\r\n", argc);
640 if (cwist_sstring_append_len(frame, count, strlen(count)).error.err_i8) goto fail;
641 for (size_t i = 0; i < argc; ++i) {
642 if (!argv[i] && argv_lens[i]) goto fail;
643 char len[32];
644 snprintf(len, sizeof(len), "$%zu\r\n", argv_lens[i]);
645 if (cwist_sstring_append_len(frame, len, strlen(len)).error.err_i8 ||
646 (argv_lens[i] && cwist_sstring_append_len(frame, argv[i], argv_lens[i]).error.err_i8) ||
647 cwist_sstring_append_len(frame, "\r\n", 2).error.err_i8)
648 goto fail;
649 }
650 cwist_error_t err = redis_command_frame_tree(r, frame->data, frame->size, reply);
652 /* Unlike the legacy string API, surface Redis '-' error replies as
653 * failure so callers can tell a server error from an empty array. */
654 if (cwist_error_is_ok(&err) && *reply && (*reply)->type == '-') {
656 *reply = NULL;
658 }
659 return err;
660fail:
663}
664
665/* --- Connection lifecycle ----------------------------------------------- */
666
672cwist_redis_t *cwist_redis_connect(const char *host, int port) {
673 if (!host || port <= 0) return NULL;
674 int fd = redis_open_socket(host, port);
675 if (fd < 0) return NULL;
676
677 cwist_redis_t *r = cwist_alloc(sizeof(*r));
678 if (!r) {
679 close(fd);
680 return NULL;
681 }
682 r->fd = fd;
683 r->host = cwist_strdup(host);
684 r->port = port;
685 r->recv_len = 0;
686 pthread_mutex_init(&r->mtx, NULL);
687 return r;
688}
689
694 if (!r) return;
695 /* shutdown wakes a subscriber blocked in recv before the descriptor is
696 * released. This is also the documented way to stop subscribe(). */
697 if (r->fd >= 0) {
698 shutdown(r->fd, SHUT_RDWR);
699 close(r->fd);
700 r->fd = -1;
701 }
702 cwist_free(r->host);
703 pthread_mutex_destroy(&r->mtx);
704 cwist_free(r);
705}
706
707/* --- Command helpers ---------------------------------------------------- */
708
713static cwist_error_t recv_reply(cwist_redis_t *r, char **out_value) {
714 if (!r) return make_error(CWIST_ERR_INT16);
715 pthread_mutex_lock(&r->mtx);
716 cwist_error_t err = read_reply(r, out_value, NULL, NULL);
717 pthread_mutex_unlock(&r->mtx);
718 return err;
719}
720
724static cwist_error_t send_command(cwist_redis_t *r, const char *cmd) {
725 pthread_mutex_lock(&r->mtx);
726 cwist_error_t err = redis_send_all(r->fd, cmd, strlen(cmd)) == 0
727 ? (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0}
729 pthread_mutex_unlock(&r->mtx);
730 return err;
731}
732
741 if (!r || !cmd) return make_error(CWIST_ERR_INT16);
742 char *value = NULL;
743 cwist_error_t err = redis_command_frame(r, cmd, strlen(cmd), &value, NULL);
744 if (out)
745 *out = value;
746 else
747 cwist_free(value);
748 return err;
749}
750
757static bool redis_frame_append(cwist_sstring *frame, const void *data, size_t len) {
758 cwist_error_t err = cwist_sstring_append_len(frame, (const char *)data, len);
759 bool ok = cwist_error_is_ok(&err);
761 return ok;
762}
763
765 const size_t *argv_lens, char **out, size_t *out_len) {
766 if (!r || !argc || !argv || !argv_lens) return make_error(CWIST_ERR_INT16);
767 if (out) *out = NULL;
768 if (out_len) *out_len = 0;
770 if (!frame) return make_error(CWIST_ERR_INT16);
771 char count[32];
772 snprintf(count, sizeof(count), "*%zu\r\n", argc);
773 if (!redis_frame_append(frame, count, strlen(count))) goto fail;
774 for (size_t i = 0; i < argc; ++i) {
775 if (!argv[i] && argv_lens[i]) goto fail;
776 char len[32];
777 snprintf(len, sizeof(len), "$%zu\r\n", argv_lens[i]);
778 if (!redis_frame_append(frame, len, strlen(len)) ||
779 (argv_lens[i] && !redis_frame_append(frame, argv[i], argv_lens[i])) ||
780 !redis_frame_append(frame, "\r\n", 2))
781 goto fail;
782 }
783 char *value = NULL;
784 cwist_error_t err = redis_command_frame(r, frame->data, frame->size, &value, out_len);
786 if (out)
787 *out = value;
788 else
789 cwist_free(value);
790 return err;
791fail:
794}
795
803 if (!r || !password) return make_error(CWIST_ERR_INT16);
804 char *reply = NULL;
806 if (username) {
807 const void *args[3] = {"AUTH", username, password};
808 size_t lengths[3] = {4, strlen(username), strlen(password)};
810 } else {
811 const void *args[2] = {"AUTH", password};
812 size_t lengths[2] = {4, strlen(password)};
814 }
816 return err;
817}
818
824 char db[16];
825 snprintf(db, sizeof(db), "%u", database);
826 const void *args[] = {"SELECT", db};
827 size_t lengths[] = {6, strlen(db)};
828 char *reply = NULL;
831 return err;
832}
833
841 if (!r || !key || !out_value) return make_error(CWIST_ERR_INT16);
843 if (!cmd) return make_error(CWIST_ERR_INT16);
844 cwist_sstring_append(cmd, "*2\r\n");
845 write_bulk_string(cmd, "GET");
846 write_bulk_string(cmd, key);
849 return err;
850}
851
857cwist_error_t cwist_redis_set(cwist_redis_t *r, const char *key, const char *value) {
858 if (!r || !key || !value) return make_error(CWIST_ERR_INT16);
860 if (!cmd) return make_error(CWIST_ERR_INT16);
861 cwist_sstring_append(cmd, "*3\r\n");
862 write_bulk_string(cmd, "SET");
863 write_bulk_string(cmd, key);
864 write_bulk_string(cmd, value);
865 char *out = NULL;
866 cwist_error_t err = cwist_redis_command(r, cmd->data, &out);
867 cwist_free(out);
869 return err;
870}
871
878cwist_error_t cwist_redis_setex(cwist_redis_t *r, const char *key, const char *value, int seconds) {
879 if (!r || !key || !value || seconds <= 0) return make_error(CWIST_ERR_INT16);
881 if (!cmd) return make_error(CWIST_ERR_INT16);
882 cwist_sstring_append(cmd, "*4\r\n");
883 write_bulk_string(cmd, "SETEX");
884 write_bulk_string(cmd, key);
885 char sec[32];
886 snprintf(sec, sizeof(sec), "%d", seconds);
887 write_bulk_string(cmd, sec);
888 write_bulk_string(cmd, value);
889 char *out = NULL;
890 cwist_error_t err = cwist_redis_command(r, cmd->data, &out);
891 cwist_free(out);
893 return err;
894}
895
901 if (!r || !key) return make_error(CWIST_ERR_INT16);
903 if (!cmd) return make_error(CWIST_ERR_INT16);
904 cwist_sstring_append(cmd, "*2\r\n");
905 write_bulk_string(cmd, "DEL");
906 write_bulk_string(cmd, key);
907 char *out = NULL;
908 cwist_error_t err = cwist_redis_command(r, cmd->data, &out);
909 cwist_free(out);
911 return err;
912}
913
920 if (!r || !key || seconds <= 0) return make_error(CWIST_ERR_INT16);
922 if (!cmd) return make_error(CWIST_ERR_INT16);
923 cwist_sstring_append(cmd, "*3\r\n");
924 write_bulk_string(cmd, "EXPIRE");
925 write_bulk_string(cmd, key);
926 char sec[32];
927 snprintf(sec, sizeof(sec), "%d", seconds);
928 write_bulk_string(cmd, sec);
929 char *out = NULL;
930 cwist_error_t err = cwist_redis_command(r, cmd->data, &out);
931 cwist_free(out);
933 return err;
934}
935
941cwist_error_t cwist_redis_publish(cwist_redis_t *r, const char *channel, const char *message) {
942 if (!r || !channel || !message) return make_error(CWIST_ERR_INT16);
944 if (!cmd) return make_error(CWIST_ERR_INT16);
945 cwist_sstring_append(cmd, "*3\r\n");
946 write_bulk_string(cmd, "PUBLISH");
947 write_bulk_string(cmd, channel);
948 write_bulk_string(cmd, message);
949 char *out = NULL;
950 cwist_error_t err = cwist_redis_command(r, cmd->data, &out);
951 cwist_free(out);
953 return err;
954}
955
956/* --- Pub/Sub ------------------------------------------------------------ */
957
967 void *ctx) {
968 if (!r || !channels || !channels[0] || !cb) return make_error(CWIST_ERR_INT16);
969
971 if (!cmd) return make_error(CWIST_ERR_INT16);
972 size_t count = 0;
973 while (channels[count]) count++;
974 char prefix[32];
975 snprintf(prefix, sizeof(prefix), "*%zu\r\n", count + 1);
976 cwist_sstring_append(cmd, prefix);
977 write_bulk_string(cmd, "SUBSCRIBE");
978 for (size_t i = 0; i < count; i++) {
979 write_bulk_string(cmd, channels[i]);
980 }
981 cwist_error_t err = send_command(r, cmd->data);
983 if (err.error.err_i16 != 0) return err;
984
985 /* Consume subscription confirmations. */
986 for (size_t i = 0; i < count; i++) {
987 char *out = NULL;
988 err = recv_reply(r, &out);
989 cwist_free(out);
990 if (err.error.err_i16 != 0) return err;
991 }
992
993 /* Blocking read loop for messages. */
994 while (1) {
996 if (redis_recv_line(r, line, sizeof(line)) != 0) break;
997 if (line[0] != '*') continue;
998 char *arr_end = NULL;
999 long long arr_count = strtoll(line + 1, &arr_end, 10);
1000 if (arr_end == line + 1 || *arr_end != '\0') continue;
1001 if (arr_count < 3) continue;
1002
1003 char *type = NULL;
1004 if (read_reply(r, &type, NULL, NULL).error.err_i16 != 0) break;
1005
1006 char *channel = NULL;
1007 if (read_reply(r, &channel, NULL, NULL).error.err_i16 != 0) {
1008 cwist_free(type);
1009 break;
1010 }
1011
1012 char *message = NULL;
1013 if (read_reply(r, &message, NULL, NULL).error.err_i16 != 0) {
1014 cwist_free(type);
1015 cwist_free(channel);
1016 break;
1017 }
1018
1019 if (type && strcmp(type, "message") == 0) {
1020 cb(channel, message, ctx);
1021 }
1022 cwist_free(type);
1023 cwist_free(channel);
1024 cwist_free(message);
1025 }
1026 return (cwist_error_t){.errtype = CWIST_ERR_INT16, .error.err_i16 = 0};
1027}
1028
1029/* --- Connection pool ---------------------------------------------------- */
1030
1037cwist_redis_pool_t *cwist_redis_pool_create(const char *host, int port, size_t max_conns) {
1038 if (!host || port <= 0 || max_conns == 0) return NULL;
1039
1040 cwist_redis_pool_t *pool = cwist_alloc(sizeof(*pool));
1041 if (!pool) return NULL;
1042 pool->wake_rd = -1;
1043 pool->wake_wr = -1;
1044 pool->host = cwist_strdup(host);
1045 pool->port = port;
1046 pool->max_conns = max_conns;
1047 pool->conns = cwist_alloc_array(max_conns, sizeof(cwist_redis_t *));
1048 pool->available = cwist_alloc_array(max_conns, sizeof(bool));
1049 if (!pool->host || !pool->conns || !pool->available) goto fail;
1050
1051 for (size_t i = 0; i < max_conns; i++) {
1052 pool->conns[i] = cwist_redis_connect(host, port);
1053 if (!pool->conns[i]) goto fail;
1054 pool->available[i] = true;
1055 }
1056
1057 /* Cache the resolved peer once so request-time reconnects need no DNS. */
1058 pool->peer_len = (socklen_t)sizeof(pool->peer);
1059 if (getpeername(pool->conns[0]->fd, (struct sockaddr *)&pool->peer, &pool->peer_len) != 0)
1060 pool->peer_len = 0;
1061 pool->owner_pid = getpid();
1062
1063 int wake[2];
1064 if (pipe(wake) == 0) {
1065 bool ok = true;
1066 for (int i = 0; i < 2; i++) {
1067 int fl = fcntl(wake[i], F_GETFL, 0);
1068 ok = ok && fl >= 0 && fcntl(wake[i], F_SETFL, fl | O_NONBLOCK) == 0 &&
1069 fcntl(wake[i], F_SETFD, FD_CLOEXEC) == 0;
1070 }
1071 if (ok) {
1072 pool->wake_rd = wake[0];
1073 pool->wake_wr = wake[1];
1074 } else {
1075 close(wake[0]);
1076 close(wake[1]);
1077 }
1078 }
1079
1080 pthread_mutex_init(&pool->mtx, NULL);
1081 pthread_cond_init(&pool->cond, NULL);
1082 return pool;
1083
1084fail:
1085 if (pool) {
1086 for (size_t i = 0; i < max_conns && pool->conns; i++) {
1087 if (pool->conns[i]) cwist_redis_close(pool->conns[i]);
1088 }
1089 if (pool->wake_rd >= 0) close(pool->wake_rd);
1090 if (pool->wake_wr >= 0) close(pool->wake_wr);
1091 cwist_free(pool->conns);
1092 cwist_free(pool->available);
1093 cwist_free(pool->host);
1094 cwist_free(pool);
1095 }
1096 return NULL;
1097}
1098
1101 if (!pool) return;
1102 pthread_mutex_lock(&pool->mtx);
1103 for (size_t i = 0; i < pool->max_conns; i++) {
1104 if (pool->conns[i]) cwist_redis_close(pool->conns[i]);
1105 }
1106 pthread_mutex_unlock(&pool->mtx);
1107 if (pool->wake_rd >= 0) close(pool->wake_rd);
1108 if (pool->wake_wr >= 0) close(pool->wake_wr);
1109 pthread_mutex_destroy(&pool->mtx);
1110 pthread_cond_destroy(&pool->cond);
1111 cwist_free(pool->conns);
1112 cwist_free(pool->available);
1113 cwist_free(pool->host);
1114 cwist_free(pool);
1115}
1116
1122static cwist_redis_t *redis_pool_acquire(cwist_redis_pool_t *pool) {
1123 if (!pool) return NULL;
1124 pthread_mutex_lock(&pool->mtx);
1125 while (1) {
1126 for (size_t i = 0; i < pool->max_conns; i++) {
1127 if (pool->available[i]) {
1128 pool->available[i] = false;
1129 pthread_mutex_unlock(&pool->mtx);
1130 cwist_redis_t *r = pool->conns[i];
1131 if (r->fd < 0) (void)redis_reconnect(pool, r, UINT64_MAX);
1132 return r;
1133 }
1134 }
1135 pthread_cond_wait(&pool->cond, &pool->mtx);
1136 }
1137}
1138
1141static void redis_pool_release(cwist_redis_pool_t *pool, cwist_redis_t *r) {
1142 if (!pool || !r) return;
1143 pthread_mutex_lock(&pool->mtx);
1144 for (size_t i = 0; i < pool->max_conns; i++) {
1145 if (pool->conns[i] == r) {
1146 pool->available[i] = true;
1147 pthread_cond_signal(&pool->cond);
1148 if (pool->timed_waiters > 0 && pool->wake_wr >= 0) {
1149 /* Non-blocking; a full pipe is already readable. */
1150 const char b = 1;
1151 ssize_t n = write(pool->wake_wr, &b, 1);
1152 (void)n;
1153 }
1154 break;
1155 }
1156 }
1157 pthread_mutex_unlock(&pool->mtx);
1158}
1159
1163 cwist_redis_t *r = redis_pool_acquire(pool);
1164 if (!r) return make_error(CWIST_ERR_INT16);
1166 redis_pool_release(pool, r);
1167 return err;
1168}
1169
1172cwist_error_t cwist_redis_pool_set(cwist_redis_pool_t *pool, const char *key, const char *value) {
1173 cwist_redis_t *r = redis_pool_acquire(pool);
1174 if (!r) return make_error(CWIST_ERR_INT16);
1175 cwist_error_t err = cwist_redis_set(r, key, value);
1176 redis_pool_release(pool, r);
1177 return err;
1178}
1179
1182cwist_error_t cwist_redis_pool_setex(cwist_redis_pool_t *pool, const char *key, const char *value,
1183 int seconds) {
1184 cwist_redis_t *r = redis_pool_acquire(pool);
1185 if (!r) return make_error(CWIST_ERR_INT16);
1186 cwist_error_t err = cwist_redis_setex(r, key, value, seconds);
1187 redis_pool_release(pool, r);
1188 return err;
1189}
1190
1194 cwist_redis_t *r = redis_pool_acquire(pool);
1195 if (!r) return make_error(CWIST_ERR_INT16);
1197 redis_pool_release(pool, r);
1198 return err;
1199}
1200
1205 const char *message) {
1206 cwist_redis_t *r = redis_pool_acquire(pool);
1207 if (!r) return make_error(CWIST_ERR_INT16);
1208 cwist_error_t err = cwist_redis_publish(r, channel, message);
1209 redis_pool_release(pool, r);
1210 return err;
1211}
1212
1217 const void *const *argv, const size_t *argv_lens,
1218 char **out, size_t *out_len) {
1219 cwist_redis_t *r = redis_pool_acquire(pool);
1220 if (!r) return make_error(CWIST_ERR_INT16);
1222 redis_pool_release(pool, r);
1223 return err;
1224}
1225
1226/* --- Deadline-bounded pooled command ------------------------------------ */
1227
1238static void redis_pool_wait_until(cwist_redis_pool_t *pool, uint64_t deadline) {
1239 uint64_t now = redis_now_ns();
1240 if (now >= deadline) return;
1241 uint64_t rem = deadline - now;
1242 if (pool->wake_rd >= 0) {
1243 uint64_t ms = (rem + 999999u) / 1000000u;
1244 struct pollfd pfd = {.fd = pool->wake_rd, .events = POLLIN, .revents = 0};
1245 pool->timed_waiters++;
1246 pthread_mutex_unlock(&pool->mtx);
1247 (void)poll(&pfd, 1, ms > (uint64_t)INT_MAX ? INT_MAX : (int)ms);
1248 pthread_mutex_lock(&pool->mtx);
1249 pool->timed_waiters--;
1250 char sink[64];
1251 if (pool->timed_waiters == 0) {
1252 while (read(pool->wake_rd, sink, sizeof(sink)) > 0) {
1253 }
1254 } else {
1255 ssize_t n = read(pool->wake_rd, sink, 1);
1256 (void)n;
1257 }
1258 return;
1259 }
1260 /* Pipe allocation failed: use short monotonic rechecks, not a
1261 * realtime condition timer that can jump backwards or be coalesced. */
1262 int ms = redis_remaining_ms(deadline);
1263 pthread_mutex_unlock(&pool->mtx);
1264 if (ms > 0) (void)poll(NULL, 0, ms < 2 ? ms : 2);
1265 pthread_mutex_lock(&pool->mtx);
1266}
1267
1274static int redis_pool_acquire_deadline(cwist_redis_pool_t *pool, uint64_t deadline,
1275 cwist_redis_t **out) {
1276 pthread_mutex_lock(&pool->mtx);
1277 for (;;) {
1278 size_t pick = pool->max_conns;
1279 for (size_t i = 0; i < pool->max_conns; i++) {
1280 if (!pool->available[i]) continue;
1281 if (pick == pool->max_conns) pick = i;
1282 if (pool->conns[i]->fd >= 0) {
1283 pick = i;
1284 break;
1285 }
1286 }
1287 if (pick < pool->max_conns) {
1288 pool->available[pick] = false;
1289 pthread_mutex_unlock(&pool->mtx);
1290 *out = pool->conns[pick];
1291 return 0;
1292 }
1293 if (redis_now_ns() >= deadline) {
1294 pthread_mutex_unlock(&pool->mtx);
1295 return -ETIMEDOUT;
1296 }
1297 redis_pool_wait_until(pool, deadline);
1298 }
1299}
1300
1302static size_t redis_digits(size_t v) {
1303 size_t n = 1;
1304 while (v >= 10) {
1305 v /= 10;
1306 n++;
1307 }
1308 return n;
1309}
1310
1313static int redis_frame_size(size_t argc, const void *const *argv, const size_t *argv_lens,
1314 size_t *out) {
1315 if (argc > CWIST_REDIS_TIMED_MAX_ARGC) return -EINVAL;
1316 size_t total = 1 + redis_digits(argc) + 2;
1317 for (size_t i = 0; i < argc; i++) {
1318 size_t len = argv_lens[i];
1319 if (!argv[i] && len) return -EINVAL;
1320 if (len > CWIST_REDIS_TIMED_MAX_FRAME) return -EINVAL;
1321 size_t part = 1 + redis_digits(len) + 2 + len + 2;
1322 if (part > CWIST_REDIS_TIMED_MAX_FRAME - total) return -EINVAL;
1323 total += part;
1324 }
1325 *out = total;
1326 return 0;
1327}
1328
1329typedef struct redis_timed_parse {
1330 cwist_redis_t *r;
1331 uint64_t deadline;
1332 size_t nodes;
1333 size_t payload;
1335
1337static char *redis_copy_str(const char *src, size_t len) {
1338 char *s = cwist_alloc(len + 1);
1339 if (!s) return NULL;
1340 if (len) memcpy(s, src, len);
1341 s[len] = '\0';
1342 return s;
1343}
1344
1348static int redis_timed_read(redis_timed_parse *ps, int depth, cwist_redis_reply_t **out) {
1349 *out = NULL;
1350 if (++ps->nodes > CWIST_REDIS_TIMED_MAX_NODES) return -EPROTO;
1351 cwist_redis_t *r = ps->r;
1352 size_t n = 0;
1353 int rc = redis_peek_line(r, ps->deadline, &n);
1354 if (rc != 0) return rc;
1355 if (n == 0) return -EPROTO;
1356 const char *line = r->recv_buf;
1357 char type = line[0];
1358 long long v = 0;
1359 if ((type == ':' || type == '$' || type == '*') && !redis_parse_ll(line + 1, n - 1, &v))
1360 return -EPROTO;
1361 if (type != '+' && type != '-' && type != ':' && type != '$' && type != '*') return -EPROTO;
1362 if (n - 1 > CWIST_REDIS_TIMED_MAX_PAYLOAD - ps->payload) return -EPROTO;
1363
1364 cwist_redis_reply_t *node = cwist_alloc(sizeof(*node));
1365 if (!node) return -ENOMEM;
1366 node->type = type;
1367 rc = -EPROTO;
1368 switch (type) {
1369 case '+':
1370 case '-':
1371 case ':':
1372 ps->payload += n - 1;
1373 node->str = redis_copy_str(line + 1, n - 1);
1374 if (!node->str) {
1375 rc = -ENOMEM;
1376 goto fail;
1377 }
1378 node->len = n - 1;
1379 node->integer = type == ':' ? v : 0;
1380 redis_consume(r, n + 2);
1381 break;
1382 case '$': {
1383 redis_consume(r, n + 2);
1384 if (v == -1) {
1385 node->integer = -1;
1386 break;
1387 }
1388 if (v < 0 || (unsigned long long)v > CWIST_REDIS_TIMED_MAX_BULK ||
1389 (size_t)v > CWIST_REDIS_TIMED_MAX_PAYLOAD - ps->payload)
1390 goto fail;
1391 ps->payload += (size_t)v;
1392 node->str = cwist_alloc((size_t)v + 1);
1393 if (!node->str) {
1394 rc = -ENOMEM;
1395 goto fail;
1396 }
1397 rc = redis_read_exact(r, node->str, (size_t)v, ps->deadline);
1398 if (rc != 0) goto fail;
1399 node->str[v] = '\0';
1400 node->len = (size_t)v;
1401 node->integer = v;
1402 char crlf[2];
1403 rc = redis_read_exact(r, crlf, 2, ps->deadline);
1404 if (rc != 0) goto fail;
1405 if (crlf[0] != '\r' || crlf[1] != '\n') {
1406 rc = -EPROTO;
1407 goto fail;
1408 }
1409 break;
1410 }
1411 case '*':
1412 redis_consume(r, n + 2);
1413 if (v == -1) {
1414 node->integer = -1;
1415 break;
1416 }
1418 (unsigned long long)v > CWIST_REDIS_TIMED_MAX_NODES - ps->nodes)
1419 goto fail;
1420 node->integer = v;
1421 if (v > 0) {
1422 node->element = cwist_alloc_array((size_t)v, sizeof(*node->element));
1423 if (!node->element) {
1424 rc = -ENOMEM;
1425 goto fail;
1426 }
1427 node->elements = (size_t)v;
1428 for (size_t i = 0; i < (size_t)v; i++) {
1429 node->element[i] = NULL;
1430 rc = redis_timed_read(ps, depth + 1, &node->element[i]);
1431 if (rc != 0) goto fail;
1432 }
1433 }
1434 break;
1435 }
1436 *out = node;
1437 return 0;
1438fail:
1440 return rc;
1441}
1442
1445 const void *const *argv,
1446 const size_t *argv_lens,
1447 unsigned timeout_ms,
1449 if (reply) *reply = NULL;
1450 if (!pool || !argc || !argv || !argv_lens || !reply || timeout_ms == 0)
1451 return redis_code_error(-EINVAL);
1452 const uint64_t deadline = redis_now_ns() + (uint64_t)timeout_ms * 1000000u;
1453 /* A fork() child shares the parent's sockets and may hold a copy of a
1454 * locked mutex: reject before touching any pool state. */
1455 if (pool->owner_pid != getpid()) return redis_code_error(-EPERM);
1456
1457 size_t frame_len = 0;
1458 int rc = redis_frame_size(argc, argv, argv_lens, &frame_len);
1459 if (rc != 0) return redis_code_error(rc);
1460 char *frame = cwist_malloc(frame_len + 1);
1461 if (!frame) return redis_code_error(-ENOMEM);
1462 rc = redis_frame_fill(frame, frame_len + 1, argc, argv, argv_lens);
1463 if (rc != 0) {
1465 return redis_code_error(rc);
1466 }
1467
1468 cwist_redis_t *r = NULL;
1469 rc = redis_pool_acquire_deadline(pool, deadline, &r);
1470 if (rc != 0) {
1472 return redis_code_error(rc);
1473 }
1474
1476 pthread_mutex_lock(&r->mtx);
1477 if (redis_now_ns() >= deadline) {
1478 rc = -ETIMEDOUT;
1479 } else {
1480 /* Nothing has been sent yet, so replacing a stale, hung-up, or
1481 * previously discarded connection is not a retry. */
1482 if (r->fd >= 0 && redis_conn_unclean(r)) redis_conn_discard(r);
1483 rc = r->fd >= 0 ? 0 : redis_reconnect(pool, r, deadline);
1484 }
1485 if (rc == 0) {
1486#if !defined(MSG_NOSIGNAL) && defined(SO_NOSIGPIPE)
1487 /* No per-send flag: mark the (possibly legacy-created) socket. */
1488 int nosig = 1;
1489 (void)setsockopt(r->fd, SOL_SOCKET, SO_NOSIGPIPE, &nosig, sizeof(nosig));
1490#endif
1491 /* Non-blocking only for this call; legacy APIs expect blocking fds. */
1492 int fl = fcntl(r->fd, F_GETFL, 0);
1493 if (fl < 0 || fcntl(r->fd, F_SETFL, fl | O_NONBLOCK) != 0) rc = -EIO;
1494 /* From the first written byte on, the command is ambiguous: any
1495 * failure discards the connection and is never retried. */
1496 if (rc == 0) rc = redis_send_deadline(r->fd, frame, frame_len, deadline);
1497 if (rc == 0) {
1498 redis_timed_parse ps = {.r = r, .deadline = deadline, .nodes = 0, .payload = 0};
1499 rc = redis_timed_read(&ps, 0, &tree);
1500 }
1501 if (rc == 0 && fcntl(r->fd, F_SETFL, fl) != 0) rc = -EIO;
1502 /* Bytes beyond the reply mean the peer is out of sync with us. */
1503 if (rc != 0 || r->recv_len != 0) redis_conn_discard(r);
1504 if (rc == 0 && tree->type == '-') {
1506 tree = NULL;
1507 rc = -EBADMSG;
1508 }
1509 }
1510 pthread_mutex_unlock(&r->mtx);
1511 redis_pool_release(pool, r);
1513 if (rc != 0) {
1515 return redis_code_error(rc);
1516 }
1517 *reply = tree;
1518 return redis_code_error(0);
1519}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
Definition alloc.c:32
void * cwist_malloc(size_t size)
Allocate zeroed memory via the CWIST owner context.
Definition alloc.c:13
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
Definition alloc.c:21
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
Safe string type with helper methods.
cwist_error_t cwist_sstring_append_len(cwist_sstring *str, const char *data, size_t len)
Append raw bytes with length.
Definition sstring.c:212
cwist_sstring * cwist_sstring_create(void)
Create a new sstring instance.
Definition sstring.c:802
cwist_error_t cwist_sstring_append(cwist_sstring *str, const char *data)
Append a C string.
Definition sstring.c:559
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
Definition sstring.c:823
cwist_error_t cwist_redis_command_argv(cwist_redis_t *r, size_t argc, const void *const *argv, const size_t *argv_lens, char **out, size_t *out_len)
cwist_error_t cwist_redis_publish(cwist_redis_t *r, const char *channel, const char *message)
#define CWIST_REDIS_TIMED_MAX_FRAME
Definition cwist_redis.c:43
cwist_error_t cwist_redis_pool_setex(cwist_redis_pool_t *pool, const char *key, const char *value, int seconds)
#define CWIST_REDIS_NOSIGNAL
Definition cwist_redis.c:53
cwist_error_t cwist_redis_command_argv_reply(cwist_redis_t *r, size_t argc, const void *const *argv, const size_t *argv_lens, cwist_redis_reply_t **reply)
#define CWIST_REDIS_BUF_SIZE
Definition cwist_redis.c:29
cwist_error_t cwist_redis_auth(cwist_redis_t *r, const char *username, const char *password)
#define CWIST_REDIS_MAX_REPLY_DEPTH
Definition cwist_redis.c:36
#define CWIST_REDIS_MAX_REPLY_ELEMENTS
Definition cwist_redis.c:37
#define CWIST_REDIS_TIMED_MAX_PAYLOAD
Definition cwist_redis.c:47
cwist_error_t cwist_redis_del(cwist_redis_t *r, const char *key)
cwist_error_t cwist_redis_pool_set(cwist_redis_pool_t *pool, const char *key, const char *value)
cwist_error_t cwist_redis_command(cwist_redis_t *r, const char *cmd, char **out)
Execute a raw Redis command and return the bulk reply.
#define CWIST_REDIS_MAX_BULK_BYTES
Definition cwist_redis.c:33
cwist_error_t cwist_redis_get(cwist_redis_t *r, const char *key, char **out_value)
Convenience commands.
void cwist_redis_pool_destroy(cwist_redis_pool_t *pool)
Destroy the pool and close all connections.
cwist_error_t cwist_redis_pool_get(cwist_redis_pool_t *pool, const char *key, char **out_value)
Pooled convenience commands.
cwist_error_t cwist_redis_setex(cwist_redis_t *r, const char *key, const char *value, int seconds)
cwist_error_t cwist_redis_pool_command_argv_reply_timeout(cwist_redis_pool_t *pool, size_t argc, const void *const *argv, const size_t *argv_lens, unsigned timeout_ms, cwist_redis_reply_t **reply)
Run one binary-safe pooled command under a single deadline.
cwist_error_t cwist_redis_expire(cwist_redis_t *r, const char *key, int seconds)
cwist_error_t cwist_redis_pool_publish(cwist_redis_pool_t *pool, const char *channel, const char *message)
cwist_error_t cwist_redis_pool_del(cwist_redis_pool_t *pool, const char *key)
cwist_error_t cwist_redis_select(cwist_redis_t *r, unsigned int database)
#define CWIST_REDIS_LINE_MAX
Definition cwist_redis.c:30
#define CWIST_REDIS_TIMED_MAX_NODES
Definition cwist_redis.c:45
cwist_error_t cwist_redis_subscribe(cwist_redis_t *r, const char **channels, cwist_redis_msg_cb cb, void *ctx)
Subscribe to channels on a dedicated connection.
cwist_redis_t * cwist_redis_connect(const char *host, int port)
Open a TCP connection to a Redis server.
void cwist_redis_reply_free(cwist_redis_reply_t *reply)
cwist_error_t cwist_redis_pool_command_argv(cwist_redis_pool_t *pool, size_t argc, const void *const *argv, const size_t *argv_lens, char **out, size_t *out_len)
#define CWIST_REDIS_TIMED_MAX_ARGC
Definition cwist_redis.c:42
cwist_redis_pool_t * cwist_redis_pool_create(const char *host, int port, size_t max_conns)
Create a Redis connection pool.
#define CWIST_REDIS_TIMED_MAX_BULK
Definition cwist_redis.c:46
struct redis_timed_parse redis_timed_parse
#define CWIST_REDIS_TIMED_MAX_DEPTH
Definition cwist_redis.c:44
void cwist_redis_close(cwist_redis_t *r)
Close a Redis connection.
cwist_error_t cwist_redis_set(cwist_redis_t *r, const char *key, const char *value)
Minimal in-tree Redis client (RESP2) with connection pooling.
struct cwist_redis cwist_redis_t
Single Redis connection handle.
Definition cwist_redis.h:19
struct cwist_redis_pool cwist_redis_pool_t
Redis connection pool handle.
Definition cwist_redis.h:24
void(* cwist_redis_msg_cb)(const char *channel, const char *message, void *ctx)
Pub/sub message callback.
Definition cwist_redis.h:29
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
Definition cwist_err.h:16
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
Definition error.c:14
size_t count
Definition otel.c:58
size_t cap
Definition otel.c:56
Integrated error structure used throughout the system.
Definition cwist_err.h:72
struct cwist_redis_reply ** element
Definition cwist_redis.h:85
long long integer
Definition cwist_redis.h:84
Unified error code and type definitions for the CWIST system layer.
void cwist_error_dispose(cwist_error_t *err)
Release resources owned by an error value. Currently only CWIST_ERR_JSON owns memory (the cJSON paylo...
Definition error.c:43