5#define _POSIX_C_SOURCE 200809L
24#include <sys/socket.h>
25#include <netinet/in.h>
29#define CWIST_REDIS_BUF_SIZE 65536
30#define CWIST_REDIS_LINE_MAX 8192
33#define CWIST_REDIS_MAX_BULK_BYTES ((long long)(512 * 1024 * 1024))
36#define CWIST_REDIS_MAX_REPLY_DEPTH 16
37#define CWIST_REDIS_MAX_REPLY_ELEMENTS (1 << 20)
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)
51#define CWIST_REDIS_NOSIGNAL MSG_NOSIGNAL
53#define CWIST_REDIS_NOSIGNAL 0
65struct cwist_redis_pool {
96static int redis_open_socket(
const char *host,
int port) {
104 int fd =
socket(res->ai_family, res->ai_socktype, res->ai_protocol);
109 if (
connect(fd, res->ai_addr, res->ai_addrlen) != 0) {
121static int redis_send_all(
int fd,
const char *buf,
size_t len) {
133 if (
n == 0)
return -1;
148 if (r->recv_len > 0) {
149 while (r->recv_len > 0 &&
i <
line_len - 1) {
150 char c = r->recv_buf[0];
152 memmove(r->recv_buf, r->recv_buf + 1, r->recv_len - 1);
155 if (
i > 0 &&
line[
i - 1] ==
'\r')
i--;
169 if (
n == 0)
return -1;
178static int redis_recv_bytes(
cwist_redis_t *r,
char *out,
size_t len) {
181 if (r->recv_len > 0) {
182 size_t take = r->recv_len < (len -
got) ? r->recv_len : (len -
got);
195 if (
n == 0)
return -1;
212static int redis_remaining_ms(
uint64_t deadline) {
214 if (
now >= deadline)
return 0;
229static int redis_wait_fd(
int fd,
short events,
uint64_t deadline) {
231 int ms = redis_remaining_ms(deadline);
233 struct pollfd p = {.fd = fd, .events =
events, .revents = 0};
245static int redis_send_deadline(
int fd,
const char *buf,
size_t len,
uint64_t deadline) {
248 if (redis_now_ns() >= deadline)
return -
ETIMEDOUT;
256 int rc = redis_wait_fd(fd,
POLLOUT, deadline);
257 if (
rc != 0)
return rc;
271 if (redis_now_ns() >= deadline)
return -
ETIMEDOUT;
277 if (
n == 0)
return -
EIO;
280 int rc = redis_wait_fd(r->fd,
POLLIN, deadline);
281 if (
rc != 0)
return rc;
290 memmove(r->recv_buf, r->recv_buf +
n, r->recv_len -
n);
305 if (
end == 0 || r->recv_buf[
end - 1] !=
'\r')
return -
EPROTO;
306 size_t len =
end - 1;
314 int rc = redis_fill_deadline(r, deadline);
315 if (
rc != 0)
return rc;
323 if (r->recv_len == 0) {
324 int rc = redis_fill_deadline(r, deadline);
325 if (
rc != 0)
return rc;
327 size_t take = r->recv_len < len -
got ? r->recv_len : len -
got;
329 redis_consume(r,
take);
336static bool redis_parse_ll(
const char *s,
size_t n,
long long *out) {
338 bool neg =
n > 0 && s[0] ==
'-';
340 if (
i >=
n)
return false;
341 unsigned long long limit =
343 unsigned long long v = 0;
345 if (s[
i] <
'0' || s[
i] >
'9')
return false;
347 if (
v > (
limit -
d) / 10u)
return false;
352 else if (
v == (
unsigned long long)
LLONG_MAX + 1u)
355 *out = -(
long long)
v;
374 if (r->recv_len != 0)
return true;
375 struct pollfd p = {.fd = r->fd, .events =
POLLIN, .revents = 0};
388 if (pool->peer_len == 0)
return -
EIO;
390 if (fd < 0)
return -
EIO;
399 if (
connect(fd, (
const struct sockaddr *)&pool->peer, pool->peer_len) != 0) {
401 rc = redis_wait_fd(fd,
POLLOUT, deadline);
402 if (
rc != 0)
goto out;
424static int write_bulk_string(
cwist_sstring *out,
const char *s) {
426 size_t len = s ?
strlen(s) : 0;
427 snprintf(prefix,
sizeof(prefix),
"$%zu\r\n", len);
437 if (out_len) *out_len = 0;
440 if ((
tree->type ==
'+' ||
tree->type ==
':') &&
tree->str) {
443 }
else if (
tree->type ==
'$' &&
tree->str) {
447 buf[
tree->len] =
'\0';
449 if (out_len) *out_len =
tree->len;
451 }
else if (out_len &&
tree->str) {
452 *out_len =
tree->len;
463 if (redis_recv_line(r,
line,
sizeof(
line)) != 0)
goto fail;
474 return (
line[0] ==
'-')
499 if (redis_recv_bytes(r, buf, (
size_t)
blen) != 0) {
506 if (redis_recv_bytes(r,
crlf, 2) != 0) {
527 for (
long long i = 0;
i <
count;
i++) {
530 if (
e.error.err_i16 != 0)
goto fail;
552 if (
err.error.err_i16 != 0 || !
tree) {
558 flatten_reply(
tree, &value, &len);
561 if (out_len) *out_len = len;
564 if (out_len) *out_len = len;
574 if (
reply->element) {
575 for (
size_t i = 0;
i <
reply->elements;
i++) {
612 if (
err.error.err_i16 != 0 || !
tree) {
615 if (out_len) *out_len = 0;
620 flatten_reply(
tree, &value, &len);
623 if (out_len) *out_len = len;
626 if (out_len) *out_len = len;
641 for (
size_t i = 0;
i <
argc; ++
i) {
654 if (cwist_error_is_ok(&
err) && *
reply && (*reply)->type ==
'-') {
673 if (!host || port <= 0)
return NULL;
674 int fd = redis_open_socket(host, port);
675 if (fd < 0)
return NULL;
759 bool ok = cwist_error_is_ok(&
err);
765 const size_t *
argv_lens,
char **out,
size_t *out_len) {
767 if (out) *out =
NULL;
768 if (out_len) *out_len = 0;
774 for (
size_t i = 0;
i <
argc; ++
i) {
778 if (!redis_frame_append(
frame, len,
strlen(len)) ||
780 !redis_frame_append(
frame,
"\r\n", 2))
826 const void *
args[] = {
"SELECT", db};
845 write_bulk_string(
cmd,
"GET");
846 write_bulk_string(
cmd, key);
862 write_bulk_string(
cmd,
"SET");
863 write_bulk_string(
cmd, key);
864 write_bulk_string(
cmd, value);
883 write_bulk_string(
cmd,
"SETEX");
884 write_bulk_string(
cmd, key);
887 write_bulk_string(
cmd,
sec);
888 write_bulk_string(
cmd, value);
905 write_bulk_string(
cmd,
"DEL");
906 write_bulk_string(
cmd, key);
924 write_bulk_string(
cmd,
"EXPIRE");
925 write_bulk_string(
cmd, key);
928 write_bulk_string(
cmd,
sec);
946 write_bulk_string(
cmd,
"PUBLISH");
947 write_bulk_string(
cmd, channel);
948 write_bulk_string(
cmd, message);
977 write_bulk_string(
cmd,
"SUBSCRIBE");
978 for (
size_t i = 0;
i <
count;
i++) {
983 if (
err.error.err_i16 != 0)
return err;
986 for (
size_t i = 0;
i <
count;
i++) {
988 err = recv_reply(r, &out);
990 if (
err.error.err_i16 != 0)
return err;
996 if (redis_recv_line(r,
line,
sizeof(
line)) != 0)
break;
997 if (
line[0] !=
'*')
continue;
1004 if (read_reply(r, &type,
NULL,
NULL).error.err_i16 != 0)
break;
1006 char *channel =
NULL;
1007 if (read_reply(r, &channel,
NULL,
NULL).error.err_i16 != 0) {
1012 char *message =
NULL;
1013 if (read_reply(r, &message,
NULL,
NULL).error.err_i16 != 0) {
1019 if (type &&
strcmp(type,
"message") == 0) {
1020 cb(channel, message, ctx);
1038 if (!host || port <= 0 || max_conns == 0)
return NULL;
1041 if (!pool)
return NULL;
1046 pool->max_conns = max_conns;
1049 if (!pool->host || !pool->conns || !pool->available)
goto fail;
1051 for (
size_t i = 0;
i < max_conns;
i++) {
1053 if (!pool->conns[
i])
goto fail;
1054 pool->available[
i] =
true;
1058 pool->peer_len = (
socklen_t)
sizeof(pool->peer);
1059 if (
getpeername(pool->conns[0]->fd, (
struct sockaddr *)&pool->peer, &pool->peer_len) != 0)
1061 pool->owner_pid =
getpid();
1066 for (
int i = 0;
i < 2;
i++) {
1072 pool->wake_rd =
wake[0];
1073 pool->wake_wr =
wake[1];
1086 for (
size_t i = 0;
i < max_conns && pool->conns;
i++) {
1089 if (pool->wake_rd >= 0)
close(pool->wake_rd);
1090 if (pool->wake_wr >= 0)
close(pool->wake_wr);
1103 for (
size_t i = 0;
i < pool->max_conns;
i++) {
1107 if (pool->wake_rd >= 0)
close(pool->wake_rd);
1108 if (pool->wake_wr >= 0)
close(pool->wake_wr);
1123 if (!pool)
return NULL;
1126 for (
size_t i = 0;
i < pool->max_conns;
i++) {
1127 if (pool->available[
i]) {
1128 pool->available[
i] =
false;
1142 if (!pool || !r)
return;
1144 for (
size_t i = 0;
i < pool->max_conns;
i++) {
1145 if (pool->conns[
i] == r) {
1146 pool->available[
i] =
true;
1148 if (pool->timed_waiters > 0 && pool->wake_wr >= 0) {
1166 redis_pool_release(pool, r);
1176 redis_pool_release(pool, r);
1187 redis_pool_release(pool, r);
1197 redis_pool_release(pool, r);
1205 const char *message) {
1209 redis_pool_release(pool, r);
1218 char **out,
size_t *out_len) {
1222 redis_pool_release(pool, r);
1240 if (
now >= deadline)
return;
1242 if (pool->wake_rd >= 0) {
1244 struct pollfd pfd = {.fd = pool->wake_rd, .events =
POLLIN, .revents = 0};
1245 pool->timed_waiters++;
1249 pool->timed_waiters--;
1251 if (pool->timed_waiters == 0) {
1262 int ms = redis_remaining_ms(deadline);
1278 size_t pick = pool->max_conns;
1279 for (
size_t i = 0;
i < pool->max_conns;
i++) {
1280 if (!pool->available[
i])
continue;
1282 if (pool->conns[
i]->fd >= 0) {
1288 pool->available[
pick] =
false;
1290 *out = pool->conns[
pick];
1293 if (redis_now_ns() >= deadline) {
1297 redis_pool_wait_until(pool, deadline);
1302static size_t redis_digits(
size_t v) {
1313static int redis_frame_size(
size_t argc,
const void *
const *
argv,
const size_t *
argv_lens,
1316 size_t total = 1 + redis_digits(
argc) + 2;
1317 for (
size_t i = 0;
i <
argc;
i++) {
1321 size_t part = 1 + redis_digits(len) + 2 + len + 2;
1337static char *redis_copy_str(
const char *src,
size_t len) {
1339 if (!s)
return NULL;
1340 if (len)
memcpy(s, src, len);
1353 int rc = redis_peek_line(r,
ps->deadline, &
n);
1354 if (
rc != 0)
return rc;
1356 const char *
line = r->recv_buf;
1357 char type =
line[0];
1359 if ((type ==
':' || type ==
'$' || type ==
'*') && !redis_parse_ll(
line + 1,
n - 1, &
v))
1361 if (type !=
'+' && type !=
'-' && type !=
':' && type !=
'$' && type !=
'*')
return -
EPROTO;
1365 if (!node)
return -
ENOMEM;
1372 ps->payload +=
n - 1;
1373 node->
str = redis_copy_str(
line + 1,
n - 1);
1379 node->
integer = type ==
':' ?
v : 0;
1380 redis_consume(r,
n + 2);
1383 redis_consume(r,
n + 2);
1397 rc = redis_read_exact(r, node->
str, (
size_t)
v,
ps->deadline);
1399 node->
str[
v] =
'\0';
1403 rc = redis_read_exact(r,
crlf, 2,
ps->deadline);
1405 if (
crlf[0] !=
'\r' ||
crlf[1] !=
'\n') {
1412 redis_consume(r,
n + 2);
1445 const void *
const *
argv,
1447 unsigned timeout_ms,
1451 return redis_code_error(-
EINVAL);
1452 const uint64_t deadline = redis_now_ns() + (
uint64_t)timeout_ms * 1000000u;
1455 if (pool->owner_pid !=
getpid())
return redis_code_error(-
EPERM);
1459 if (
rc != 0)
return redis_code_error(
rc);
1465 return redis_code_error(
rc);
1469 rc = redis_pool_acquire_deadline(pool, deadline, &r);
1472 return redis_code_error(
rc);
1477 if (redis_now_ns() >= deadline) {
1482 if (r->fd >= 0 && redis_conn_unclean(r)) redis_conn_discard(r);
1483 rc = r->fd >= 0 ? 0 : redis_reconnect(pool, r, deadline);
1486#if !defined(MSG_NOSIGNAL) && defined(SO_NOSIGPIPE)
1499 rc = redis_timed_read(&
ps, 0, &
tree);
1503 if (
rc != 0 || r->recv_len != 0) redis_conn_discard(r);
1504 if (
rc == 0 &&
tree->type ==
'-') {
1511 redis_pool_release(pool, r);
1515 return redis_code_error(
rc);
1518 return redis_code_error(0);
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
void * cwist_malloc(size_t size)
Allocate zeroed memory via the CWIST owner context.
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
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.
cwist_sstring * cwist_sstring_create(void)
Create a new sstring instance.
cwist_error_t cwist_sstring_append(cwist_sstring *str, const char *data)
Append a C string.
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
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
cwist_error_t cwist_redis_pool_setex(cwist_redis_pool_t *pool, const char *key, const char *value, int seconds)
#define CWIST_REDIS_NOSIGNAL
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
cwist_error_t cwist_redis_auth(cwist_redis_t *r, const char *username, const char *password)
#define CWIST_REDIS_MAX_REPLY_DEPTH
#define CWIST_REDIS_MAX_REPLY_ELEMENTS
#define CWIST_REDIS_TIMED_MAX_PAYLOAD
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
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
#define CWIST_REDIS_TIMED_MAX_NODES
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
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
struct redis_timed_parse redis_timed_parse
#define CWIST_REDIS_TIMED_MAX_DEPTH
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.
struct cwist_redis_pool cwist_redis_pool_t
Redis connection pool handle.
void(* cwist_redis_msg_cb)(const char *channel, const char *message, void *ctx)
Pub/sub message callback.
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
Integrated error structure used throughout the system.
struct cwist_redis_reply ** element
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...