iodine 0.4.19 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
@@ -28,6 +28,7 @@ extern struct ___RegistryClass___ {
28
28
  void (*remove)(VALUE obj);
29
29
  VALUE (*add)(VALUE obj);
30
30
  void (*print)(void);
31
+ void (*on_fork)(void);
31
32
  } Registry;
32
33
 
33
34
  #endif // RB_REGISTRY_HELPER_H
@@ -1,398 +1,836 @@
1
1
  /*
2
- Copyright: Boaz segev, 2017
3
- License: MIT except for any non-public-domain algorithms (none that I'm aware
4
- of), which might be subject to their own licenses.
2
+ Copyright: Boaz segev, 2016-2017
3
+ License: MIT
5
4
 
6
- Feel free to copy, use and enjoy in accordance with to the license(s).
5
+ Feel free to copy, use and enjoy according to the license provided.
7
6
  */
8
7
  #include "spnlock.inc"
9
8
 
10
- #include "fio_list.h"
11
- #include "redis_connection.h"
9
+ #include "fio_llist.h"
10
+ #include "fiobj4sock.h"
12
11
  #include "redis_engine.h"
13
- #include "resp.h"
12
+ #include "resp_parser.h"
14
13
 
15
- #include <string.h>
14
+ #define REDIS_READ_BUFFER 8192
16
15
  /* *****************************************************************************
17
- Data Structures / State
16
+ The Redis Engine and Callbacks Object
18
17
  ***************************************************************************** */
19
18
 
20
19
  typedef struct {
21
- pubsub_engine_s engine;
22
- resp_parser_pt sub_parser;
23
- resp_parser_pt pub_parser;
24
- intptr_t sub;
25
- intptr_t pub;
26
- void *sub_ctx;
27
- void *pub_ctx;
20
+ uintptr_t id_protection;
21
+ pubsub_engine_s en;
22
+ struct redis_engine_internal_s {
23
+ protocol_s protocol;
24
+ uintptr_t uuid;
25
+ resp_parser_s parser;
26
+ uintptr_t is_pub;
27
+ FIOBJ str;
28
+ FIOBJ ary;
29
+ uintptr_t ary_count;
30
+ uintptr_t buf_pos;
31
+ } pub_data, sub_data;
32
+ fio_ls_embd_s callbacks;
33
+ spn_lock_i lock;
28
34
  char *address;
29
35
  char *port;
30
- fio_list_s callbacks;
31
- uint16_t ref;
32
- volatile uint8_t active;
33
- volatile uint8_t sub_state;
34
- volatile uint8_t pub_state;
35
- spn_lock_i lock;
36
+ char *auth;
37
+ FIOBJ last_ch;
38
+ size_t auth_len;
39
+ size_t ref;
40
+ uint8_t ping_int;
41
+ uint8_t sent;
42
+ uint8_t flag;
43
+ uint8_t buf[];
36
44
  } redis_engine_s;
37
45
 
38
46
  typedef struct {
39
- fio_list_s node;
40
- void (*callback)(pubsub_engine_s *e, resp_object_s *reply, void *udata);
47
+ fio_ls_embd_s node;
48
+ void (*callback)(pubsub_engine_s *e, FIOBJ reply, void *udata);
41
49
  void *udata;
42
- size_t len;
43
- uint8_t sent;
44
- } callbacks_s;
45
-
46
- static int dealloc_engine(redis_engine_s *r) {
47
- if (!spn_sub(&r->ref, 1)) {
48
- resp_parser_destroy(r->sub_parser);
49
- resp_parser_destroy(r->pub_parser);
50
- free(r);
51
- return -1;
50
+ size_t cmd_len;
51
+ uint8_t cmd[];
52
+ } redis_commands_s;
53
+
54
+ /** converst from a `pubsub_engine_s` to a `redis_engine_s`. */
55
+ #define en2redis(e) FIO_LS_EMBD_OBJ(redis_engine_s, en, (e))
56
+
57
+ /** converst from a `protocol_s` to a `redis_engine_s`. */
58
+ #define prot2redis(prot) \
59
+ ((FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, protocol, (prot))->is_pub) \
60
+ ? FIO_LS_EMBD_OBJ(redis_engine_s, pub_data.protocol, (pr)) \
61
+ : FIO_LS_EMBD_OBJ(redis_engine_s, sub_data.protocol, (pr)))
62
+
63
+ /** converst from a `resp_parser_s` to the internal data structure. */
64
+ #define parser2data(prsr) \
65
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, (prsr))
66
+
67
+ #define parser2redis(prsr) \
68
+ (parser2data(prsr)->is_pub \
69
+ ? FIO_LS_EMBD_OBJ(redis_engine_s, pub_data.parser, (prsr)) \
70
+ : FIO_LS_EMBD_OBJ(redis_engine_s, sub_data.parser, (prsr)))
71
+
72
+ /** cleans up and frees the engine data. */
73
+ static inline void redis_free(redis_engine_s *r) {
74
+ if (spn_sub(&r->ref, 1))
75
+ return;
76
+ fiobj_free(r->pub_data.ary ? r->pub_data.ary : r->pub_data.str);
77
+ fiobj_free(r->sub_data.ary ? r->sub_data.ary : r->sub_data.str);
78
+ fiobj_free(r->last_ch);
79
+ while (fio_ls_embd_any(&r->callbacks)) {
80
+ free(FIO_LS_EMBD_OBJ(redis_commands_s, node,
81
+ fio_ls_embd_pop(&r->callbacks)));
52
82
  }
53
- return 0;
83
+ free(r);
54
84
  }
55
85
 
86
+ /**
87
+ * Defined later, converts FIOBJ objects into a RESP string (client mode).
88
+ *
89
+ * Don't call `fiobj_free`, object will self-destruct.
90
+ */
91
+ static FIOBJ fiobj2resp_tmp(FIOBJ obj1, FIOBJ obj2);
92
+
56
93
  /* *****************************************************************************
57
- Writing commands
94
+ Command / Callback handling
58
95
  ***************************************************************************** */
59
- static void redis_pub_send(void *e, void *uuid) {
60
- redis_engine_s *r = e;
61
- callbacks_s *cb;
62
- spn_lock(&r->lock);
63
96
 
64
- if (fio_list_any(r->callbacks)) {
65
- cb = fio_node2obj(callbacks_s, node, r->callbacks.next);
66
- if (cb->sent == 0) {
67
- cb->sent = 1;
68
- sock_write2(.uuid = r->pub, .buffer = (uint8_t *)(cb + 1),
69
- .length = cb->len, .move = 1, .dealloc = SOCK_DEALLOC_NOOP);
70
- }
71
- }
72
- spn_unlock(&r->lock);
73
- dealloc_engine(r);
74
- (void)uuid;
97
+ /* the deferred callback handler */
98
+ static void redis_perform_callback(void *e, void *cmd_) {
99
+ redis_commands_s *cmd = cmd_;
100
+ FIOBJ reply = (FIOBJ)cmd->node.next;
101
+ if (cmd->callback)
102
+ cmd->callback(e, reply, cmd->udata);
103
+ fiobj_free(reply);
104
+ // fprintf(stderr, "Handled: %s\n", cmd->cmd);
105
+ free(cmd);
75
106
  }
76
107
 
77
- static void schedule_pub_send(redis_engine_s *r, intptr_t uuid) {
78
- spn_add(&r->ref, 1);
79
- defer(redis_pub_send, r, (void *)uuid);
108
+ /* send the necxt command in the queue */
109
+ static void redis_send_cmd_queue(void *r_, void *ignr) {
110
+ redis_engine_s *r = r_;
111
+ if (!r->pub_data.uuid)
112
+ return;
113
+ spn_lock(&r->lock);
114
+ if (r->sent == 0 && fio_ls_embd_any(&r->callbacks)) {
115
+ redis_commands_s *cmd =
116
+ FIO_LS_EMBD_OBJ(redis_commands_s, node, r->callbacks.prev);
117
+ intptr_t uuid = r->pub_data.uuid;
118
+ r->sent = 1;
119
+ spn_unlock(&r->lock);
120
+ // fprintf(stderr, "Sending: %s\n", cmd->cmd);
121
+ sock_write2(.uuid = uuid, .buffer = cmd->cmd, .length = cmd->cmd_len,
122
+ .dealloc = SOCK_DEALLOC_NOOP);
123
+ } else {
124
+ r->sent = 0;
125
+ spn_unlock(&r->lock);
126
+ }
127
+ (void)ignr;
80
128
  }
81
129
 
82
- /* *****************************************************************************
83
- Engine Bridge
84
- ***************************************************************************** */
85
-
86
- static void on_message_sub(intptr_t uuid, resp_object_s *msg, void *udata) {
87
- if (msg->type == RESP_PUBSUB) {
88
- pubsub_engine_distribute(
89
- .engine = udata,
90
- .channel =
91
- {.name =
92
- (char *)resp_obj2str(resp_obj2arr(msg)->array[1])->string,
93
- .len = resp_obj2str(resp_obj2arr(msg)->array[1])->len},
94
- .msg = {
95
- .data =
96
- (char *)resp_obj2str(resp_obj2arr(msg)->array[2])->string,
97
- .len = resp_obj2str(resp_obj2arr(msg)->array[2])->len});
98
- return;
130
+ static void redis_attach_cmd(redis_engine_s *r, redis_commands_s *cmd) {
131
+ uint8_t schedule = 0;
132
+ spn_lock(&r->lock);
133
+ fio_ls_embd_push(&r->callbacks, &cmd->node);
134
+ if (r->sent == 0) {
135
+ schedule = 1;
136
+ }
137
+ spn_unlock(&r->lock);
138
+ if (schedule) {
139
+ defer(redis_send_cmd_queue, r, NULL);
99
140
  }
100
- (void)uuid;
101
141
  }
102
142
 
103
- static void on_message_pub(intptr_t uuid, resp_object_s *msg, void *udata) {
104
- redis_engine_s *r = udata;
105
- callbacks_s *cb;
143
+ static void redis_cmd_reply(redis_engine_s *r, FIOBJ reply) {
144
+ uint8_t schedule = 0;
106
145
  spn_lock(&r->lock);
107
- cb = fio_list_shift(callbacks_s, node, r->callbacks);
146
+ r->sent = 0;
147
+ fio_ls_embd_s *node = fio_ls_embd_shift(&r->callbacks);
148
+ schedule = fio_ls_embd_any(&r->callbacks);
108
149
  spn_unlock(&r->lock);
109
- if (cb) {
110
- schedule_pub_send(r, uuid);
111
- if (cb->callback)
112
- cb->callback(&r->engine, msg, cb->udata);
113
- free(cb);
114
- } else {
115
- uint8_t buffer[64] = {0};
116
- size_t len = 63;
117
- resp_format(NULL, buffer, &len, msg);
150
+ if (!node) {
151
+ /* TODO: possible ping? from server?! not likely... */
118
152
  fprintf(stderr,
119
- "WARN: (RedisEngine) Possible issue, "
120
- "received unknown message (%lu bytes):\n%s\n",
121
- len, (char *)buffer);
153
+ "WARNING: (redis) received a reply when no command was sent\n");
154
+ return;
122
155
  }
123
- (void)uuid;
156
+ node->next = (void *)fiobj_dup(reply);
157
+ if (schedule)
158
+ defer(redis_send_cmd_queue, r, NULL);
159
+ defer(redis_perform_callback, &r->en,
160
+ FIO_LS_EMBD_OBJ(redis_commands_s, node, node));
124
161
  }
125
162
 
126
163
  /* *****************************************************************************
127
- Connections
164
+ Connection Establishment
128
165
  ***************************************************************************** */
129
166
 
130
- static inline int connect_sub(redis_engine_s *r) {
131
- spn_add(&r->ref, 1);
132
- return (r->sub = facil_connect(.address = r->address, .port = r->port,
133
- .on_connect = redis_create_client_protocol,
134
- .udata = r->sub_ctx,
135
- .on_fail = redis_protocol_cleanup));
167
+ static void redis_on_auth(pubsub_engine_s *e, FIOBJ reply, void *udata) {
168
+ if (FIOBJ_TYPE_IS(reply, FIOBJ_T_TRUE)) {
169
+ fio_cstr_s s = fiobj_obj2cstr(reply);
170
+ fprintf(stderr,
171
+ "WARNING: (redis) Authentication FAILED.\n"
172
+ " %.*s\n",
173
+ (int)s.len, s.data);
174
+ }
175
+ (void)e;
176
+ (void)udata;
136
177
  }
137
178
 
138
- static inline int connect_pub(redis_engine_s *r) {
139
- spn_add(&r->ref, 1);
140
- return (r->pub = facil_connect(.address = r->address, .port = r->port,
141
- .on_connect = redis_create_client_protocol,
142
- .udata = r->pub_ctx,
143
- .on_fail = redis_protocol_cleanup));
144
- }
179
+ static void redis_on_pub_connect(intptr_t uuid, void *pr) {
180
+ redis_engine_s *r = prot2redis(pr);
181
+ if (r->pub_data.uuid)
182
+ sock_close(r->pub_data.uuid);
183
+ r->pub_data.uuid = uuid;
184
+ facil_attach(uuid, pr);
185
+ facil_set_timeout(uuid, r->ping_int);
145
186
 
146
- static void on_close_sub(intptr_t uuid, void *p) {
147
- redis_engine_s *r = p;
148
- if (!defer_fork_is_active()) {
149
- dealloc_engine(r);
187
+ if (!facil_is_running() || !r->flag) {
188
+ sock_close(uuid);
150
189
  return;
151
190
  }
152
- if (r->sub == uuid && r->active) {
153
- if (r->sub_state) {
154
- r->sub_state = 0;
155
- fprintf(stderr,
156
- "ERROR: (RedisEngine) redis Sub "
157
- "connection LOST: %s:%s\n",
158
- r->address ? r->address : "0.0.0.0", r->port);
159
- }
160
- connect_sub(r);
161
- facil_run_every(50, 1, (void (*)(void *))pubsub_engine_resubscribe,
162
- (void *)&r->engine, NULL);
191
+
192
+ if (r->auth) {
193
+ redis_commands_s *cmd = malloc(sizeof(*cmd) + r->auth_len);
194
+ *cmd =
195
+ (redis_commands_s){.cmd_len = r->auth_len, .callback = redis_on_auth};
196
+ memcpy(cmd->cmd, r->auth, r->auth_len);
197
+ spn_lock(&r->lock);
198
+ fio_ls_embd_unshift(&r->callbacks, &cmd->node);
199
+ r->sent = 1;
200
+ spn_unlock(&r->lock);
201
+ sock_write2(.uuid = uuid, .buffer = cmd->cmd, .length = cmd->cmd_len,
202
+ .dealloc = SOCK_DEALLOC_NOOP);
203
+ } else {
204
+ r->sent = 0;
205
+ defer(redis_send_cmd_queue, r, NULL);
163
206
  }
164
- dealloc_engine(r);
207
+ fprintf(stderr, "INFO: (redis %d) publishing connection established.\n",
208
+ (int)getpid());
165
209
  }
166
-
167
- static void on_close_pub(intptr_t uuid, void *p) {
168
- redis_engine_s *r = p;
169
- if (!defer_fork_is_active()) {
170
- dealloc_engine(r);
210
+ static void redis_on_pub_connect_fail(intptr_t uuid, void *pr);
211
+ static void redis_on_sub_connect(intptr_t uuid, void *pr) {
212
+ redis_engine_s *r = prot2redis(pr);
213
+ r->sub_data.uuid = uuid;
214
+ facil_attach(uuid, pr);
215
+ facil_set_timeout(uuid, r->ping_int);
216
+
217
+ if (!facil_is_running() || !r->flag) {
218
+ sock_close(uuid);
171
219
  return;
172
220
  }
173
- if (r->pub == uuid && r->active) {
174
- connect_pub(r);
175
- if (r->pub_state) {
176
- r->pub_state = 0;
177
- fprintf(stderr,
178
- "ERROR: (RedisEngine) redis Pub "
179
- "connection LOST: %s:%s\n",
180
- r->address ? r->address : "0.0.0.0", r->port);
181
- }
221
+
222
+ if (r->auth)
223
+ sock_write2(.uuid = uuid, .buffer = r->auth, .length = r->auth_len,
224
+ .dealloc = SOCK_DEALLOC_NOOP);
225
+ pubsub_engine_resubscribe(&r->en);
226
+ if (!r->pub_data.uuid) {
227
+ spn_add(&r->ref, 1);
228
+ redis_on_pub_connect_fail(uuid, pr);
182
229
  }
183
- dealloc_engine(r);
230
+ fprintf(stderr, "INFO: (redis %d) subscription connection established.\n",
231
+ (int)getpid());
184
232
  }
185
233
 
186
- static void on_open_pub(intptr_t uuid, void *e) {
187
- redis_engine_s *r = e;
188
- if (r->pub != uuid)
234
+ static void redis_deferred_connect(void *r_, void *is_pub);
235
+ static void redis_on_pub_connect_fail(intptr_t uuid, void *pr) {
236
+ redis_engine_s *r = prot2redis(pr);
237
+ if ((facil_parent_pid() == getpid() && !r->sub_data.uuid) || r->flag == 0 ||
238
+ !facil_is_running()) {
239
+ r->pub_data.uuid = 0;
240
+ redis_free(r);
189
241
  return;
190
- if (!r->pub_state) /* no message on first connection */
191
- fprintf(stderr,
192
- "INFO: (RedisEngine) redis Pub "
193
- "connection (re)established: %s:%s\n",
194
- r->address ? r->address : "0.0.0.0", r->port);
195
- r->pub_state = 1;
196
- spn_lock(&r->lock);
197
- callbacks_s *cb;
198
- fio_list_for_each(callbacks_s, node, cb, r->callbacks) { cb->sent = 0; }
199
- spn_unlock(&r->lock);
200
- schedule_pub_send(r, uuid);
242
+ }
243
+ r->pub_data.uuid = 0;
244
+ /* we defer publishing by a cycle, so subsciptions race a bit faster */
245
+ defer(redis_deferred_connect, r, (void *)1);
246
+ (void)uuid;
201
247
  }
202
-
203
- static void on_open_sub(intptr_t uuid, void *e) {
204
- redis_engine_s *r = e;
205
- if (r->sub != uuid)
248
+ static void redis_on_sub_connect_fail(intptr_t uuid, void *pr) {
249
+ redis_engine_s *r = prot2redis(pr);
250
+ if (!facil_is_running() || !r->flag) {
251
+ redis_free(r);
206
252
  return;
207
- if (!r->sub_state) /* no message on first connection */
208
- fprintf(stderr,
209
- "INFO: (RedisEngine) redis Sub "
210
- "connection (re)established: %s:%s\n",
211
- r->address ? r->address : "0.0.0.0", r->port);
212
- r->sub_state = 1;
213
- pubsub_engine_resubscribe(&r->engine);
253
+ }
254
+ if (facil_parent_pid() != getpid()) {
255
+ /* respawned as worker */
256
+ redis_on_pub_connect_fail(uuid, pr);
257
+ return;
258
+ }
259
+ r->sub_data.uuid = 0;
260
+ defer(redis_deferred_connect, r, (void *)0);
214
261
  (void)uuid;
215
262
  }
216
263
 
264
+ static void redis_deferred_connect(void *r_, void *is_pub) {
265
+ redis_engine_s *r = r_;
266
+ if (is_pub) {
267
+ facil_connect(.address = r->address, .port = r->port,
268
+ .on_connect = redis_on_pub_connect,
269
+ .udata = &r->pub_data.protocol,
270
+ .on_fail = redis_on_pub_connect_fail);
271
+
272
+ } else {
273
+ facil_connect(.address = r->address, .port = r->port,
274
+ .on_connect = redis_on_sub_connect,
275
+ .udata = &r->sub_data.protocol,
276
+ .on_fail = redis_on_sub_connect_fail);
277
+ }
278
+ }
279
+
217
280
  /* *****************************************************************************
218
- Callbacks
281
+ The Protocol layer (connection handling)
219
282
  ***************************************************************************** */
220
283
 
221
- /** Should return 0 on success and -1 on failure. */
222
- static int subscribe(const pubsub_engine_s *eng, const char *ch, size_t ch_len,
223
- uint8_t use_pattern) {
224
- redis_engine_s *e = (redis_engine_s *)eng;
225
- if (!sock_isvalid(e->sub)) {
226
- return 0;
284
+ static void redis_on_data(intptr_t uuid, protocol_s *pr) {
285
+ redis_engine_s *r = prot2redis(pr);
286
+ struct redis_engine_internal_s *internal =
287
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, protocol, pr);
288
+ uint8_t *buf;
289
+ if (internal->is_pub) {
290
+ buf = r->buf + REDIS_READ_BUFFER;
291
+ } else {
292
+ buf = r->buf;
227
293
  }
228
- resp_object_s *cmd = resp_arr2obj(2, NULL);
229
- if (!cmd) {
230
- return -1;
294
+ ssize_t i = sock_read(uuid, buf + internal->buf_pos,
295
+ REDIS_READ_BUFFER - internal->buf_pos);
296
+ if (i <= 0)
297
+ return;
298
+ internal->buf_pos += i;
299
+ i = resp_parse(&internal->parser, buf, internal->buf_pos);
300
+ if (i) {
301
+ memmove(buf, buf + internal->buf_pos - i, i);
231
302
  }
232
- resp_obj2arr(cmd)->array[0] = use_pattern ? resp_str2obj("PSUBSCRIBE", 10)
233
- : resp_str2obj("SUBSCRIBE", 9);
234
- resp_obj2arr(cmd)->array[1] =
235
- (ch ? resp_str2obj(ch, ch_len) : resp_nil2obj());
236
- void *buffer = malloc(32 + ch_len);
237
- size_t size = 32 + ch_len;
238
- if (resp_format(e->sub_parser, buffer, &size, cmd))
239
- fprintf(stderr, "ERROR: RESP format? size = %lu ch = %lu\n", size, ch_len);
240
- sock_write2(.uuid = e->sub, .buffer = buffer, .length = size, .move = 1);
241
- resp_free_object(cmd);
242
- return 0;
303
+ internal->buf_pos = i;
243
304
  }
244
305
 
245
- /** Return value is ignored. */
246
- static void unsubscribe(const pubsub_engine_s *eng, const char *ch,
247
- size_t ch_len, uint8_t use_pattern) {
248
- redis_engine_s *e = (redis_engine_s *)eng;
306
+ static void redis_pub_on_close(intptr_t uuid, protocol_s *pr) {
307
+ redis_engine_s *r = prot2redis(pr);
308
+ fiobj_free(r->pub_data.ary ? r->pub_data.ary : r->pub_data.str);
309
+ r->pub_data.ary = r->pub_data.str = FIOBJ_INVALID;
310
+ r->pub_data.uuid = 0;
311
+ r->sent = 0;
312
+ if (r->flag && facil_is_running()) {
313
+ fprintf(stderr,
314
+ "WARNING: (redis %d) lost publishing connection to database\n",
315
+ (int)getpid());
316
+ redis_on_pub_connect_fail(uuid, &r->pub_data.protocol);
317
+ } else {
318
+ redis_free(r);
319
+ }
320
+ }
249
321
 
250
- if (!sock_isvalid(e->sub))
251
- return;
252
- resp_object_s *cmd = resp_arr2obj(2, NULL);
253
- if (!cmd)
322
+ static void redis_sub_on_close(intptr_t uuid, protocol_s *pr) {
323
+ redis_engine_s *r = prot2redis(pr);
324
+ fiobj_free(r->sub_data.ary ? r->sub_data.ary : r->sub_data.str);
325
+ r->sub_data.ary = r->sub_data.str = FIOBJ_INVALID;
326
+ r->sub_data.uuid = 0;
327
+ if (r->flag && facil_is_running() && facil_parent_pid() == getpid()) {
328
+ fprintf(stderr,
329
+ "WARNING: (redis %d) lost subscribing connection to database\n",
330
+ (int)getpid());
331
+ redis_on_sub_connect_fail(uuid, &r->sub_data.protocol);
332
+ } else {
333
+ redis_free(r);
334
+ }
335
+ }
336
+
337
+ static void redis_on_shutdown(intptr_t uuid, protocol_s *pr) {
338
+ sock_write2(.uuid = uuid, .buffer = "*1\r\n$4\r\nQUIT\r\n", .length = 14,
339
+ .dealloc = SOCK_DEALLOC_NOOP);
340
+ (void)pr;
341
+ }
342
+
343
+ static void redis_sub_ping(intptr_t uuid, protocol_s *pr) {
344
+ sock_write2(.uuid = uuid, .buffer = "*1\r\n$4\r\nPING\r\n", .length = 14,
345
+ .dealloc = SOCK_DEALLOC_NOOP);
346
+ (void)pr;
347
+ }
348
+
349
+ static void redis_pub_ping(intptr_t uuid, protocol_s *pr) {
350
+ redis_engine_s *r = prot2redis(pr);
351
+ if (r->sent) {
352
+ fprintf(stderr,
353
+ "WARNING: (redis) Redis server unresponsive, disconnecting.\n");
354
+ sock_close(uuid);
254
355
  return;
255
- resp_obj2arr(cmd)->array[0] = use_pattern ? resp_str2obj("PUNSUBSCRIBE", 12)
256
- : resp_str2obj("UNSUBSCRIBE", 11);
257
- resp_obj2arr(cmd)->array[1] =
258
- (ch ? resp_str2obj(ch, ch_len) : resp_nil2obj());
259
- void *buffer = malloc(32 + ch_len);
260
- size_t size = 32 + ch_len;
261
- if (!resp_format(e->sub_parser, buffer, &size, cmd) && size <= (32 + ch_len))
262
- sock_write2(.uuid = e->sub, .buffer = buffer, .length = size, .move = 1);
263
- resp_free_object(cmd);
264
- }
265
-
266
- /** Should return 0 on success and -1 on failure. */
267
- static int publish(const pubsub_engine_s *eng, const char *ch, size_t ch_len,
268
- const char *msg, size_t msg_len, uint8_t use_pattern) {
269
- if (!msg || use_pattern || !ch)
270
- return -1;
271
- resp_object_s *cmd = resp_arr2obj(3, NULL);
272
- if (!cmd)
273
- return -1;
274
- resp_obj2arr(cmd)->array[0] = resp_str2obj("PUBLISH", 7);
275
- resp_obj2arr(cmd)->array[1] = resp_str2obj(ch, ch_len);
276
- resp_obj2arr(cmd)->array[2] = resp_str2obj(msg, msg_len);
277
- redis_engine_send((pubsub_engine_s *)eng, cmd, NULL, NULL);
278
- resp_free_object(cmd);
279
- return 0;
356
+ }
357
+ redis_commands_s *cmd = malloc(sizeof(*cmd) + 15);
358
+ *cmd = (redis_commands_s){.cmd_len = 14};
359
+ memcpy(cmd->cmd, "*1\r\n$4\r\nPING\r\n\0", 15);
360
+ redis_attach_cmd(r, cmd);
280
361
  }
281
362
 
282
363
  /* *****************************************************************************
283
- Creation / Destruction
364
+ Engine Callbacks
284
365
  ***************************************************************************** */
285
366
 
286
- static void initialize_engine(void *en_, void *ig) {
287
- redis_engine_s *r = (redis_engine_s *)en_;
288
- (void)ig;
289
- connect_sub(r);
290
- connect_pub(r);
367
+ static void redis_on_subscribe(const pubsub_engine_s *eng, FIOBJ channel,
368
+ uint8_t use_pattern) {
369
+ redis_engine_s *r = en2redis(eng);
370
+ if (r->sub_data.uuid) {
371
+ fio_cstr_s ch_str = fiobj_obj2cstr(channel);
372
+ FIOBJ cmd = fiobj_str_buf(96 + ch_str.len);
373
+ if (use_pattern)
374
+ fiobj_str_write(cmd, "*2\r\n$10\r\nPSUBSCRIBE\r\n$", 22);
375
+ else
376
+ fiobj_str_write(cmd, "*2\r\n$9\r\nSUBSCRIBE\r\n$", 20);
377
+ fiobj_str_join(cmd, fiobj_num_tmp(ch_str.len));
378
+ fiobj_str_write(cmd, "\r\n", 2);
379
+ fiobj_str_write(cmd, ch_str.data, ch_str.len);
380
+ fiobj_str_write(cmd, "\r\n", 2);
381
+ // {
382
+ // fio_cstr_s s = fiobj_obj2cstr(cmd);
383
+ // fprintf(stderr, "%s\n", s.data);
384
+ // }
385
+ fiobj_send_free(r->sub_data.uuid, cmd);
386
+ }
387
+ }
388
+ static void redis_on_unsubscribe(const pubsub_engine_s *eng, FIOBJ channel,
389
+ uint8_t use_pattern) {
390
+ redis_engine_s *r = en2redis(eng);
391
+ if (r->sub_data.uuid) {
392
+ fio_cstr_s ch_str = fiobj_obj2cstr(channel);
393
+ FIOBJ cmd = fiobj_str_buf(96 + ch_str.len);
394
+ if (use_pattern)
395
+ fiobj_str_write(cmd, "*2\r\n$12\r\nPUNSUBSCRIBE\r\n$", 24);
396
+ else
397
+ fiobj_str_write(cmd, "*2\r\n$11\r\nUNSUBSCRIBE\r\n$", 23);
398
+ fiobj_str_join(cmd, fiobj_num_tmp(ch_str.len));
399
+ fiobj_str_write(cmd, "\r\n", 2);
400
+ fiobj_str_write(cmd, ch_str.data, ch_str.len);
401
+ fiobj_str_write(cmd, "\r\n", 2);
402
+ // {
403
+ // fio_cstr_s s = fiobj_obj2cstr(cmd);
404
+ // fprintf(stderr, "%s\n", s.data);
405
+ // }
406
+ fiobj_send_free(r->sub_data.uuid, cmd);
407
+ }
291
408
  }
409
+ static int redis_on_publish(const pubsub_engine_s *eng, FIOBJ channel,
410
+ FIOBJ msg) {
411
+ redis_engine_s *r = en2redis(eng);
412
+ if (FIOBJ_TYPE(msg) == FIOBJ_T_ARRAY || FIOBJ_TYPE(msg) == FIOBJ_T_HASH)
413
+ msg = fiobj_obj2json(msg, 0);
414
+ else
415
+ msg = fiobj_dup(msg);
416
+
417
+ fio_cstr_s msg_str = fiobj_obj2cstr(msg);
418
+ fio_cstr_s ch_str = fiobj_obj2cstr(channel);
419
+
420
+ redis_commands_s *cmd = malloc(sizeof(*cmd) + ch_str.len + msg_str.len + 96);
421
+ *cmd = (redis_commands_s){.cmd_len = 0};
422
+ memcpy(cmd->cmd, "*3\r\n$7\r\nPUBLISH\r\n$", 18);
423
+ char *buf = (char *)cmd->cmd + 18;
424
+ buf += fio_ltoa((void *)buf, ch_str.len, 10);
425
+ *buf++ = '\r';
426
+ *buf++ = '\n';
427
+ memcpy(buf, ch_str.data, ch_str.len);
428
+ buf += ch_str.len;
429
+ *buf++ = '\r';
430
+ *buf++ = '\n';
431
+ *buf++ = '$';
432
+ msg_str = fiobj_obj2cstr(msg);
433
+ buf += fio_ltoa(buf, msg_str.len, 10);
434
+ *buf++ = '\r';
435
+ *buf++ = '\n';
436
+ memcpy(buf, msg_str.data, msg_str.len);
437
+ buf += msg_str.len;
438
+ *buf++ = '\r';
439
+ *buf++ = '\n';
440
+ *buf = 0;
441
+ // fprintf(stderr, "%s\n", cmd->cmd);
442
+ cmd->cmd_len = (uintptr_t)buf - (uintptr_t)(cmd + 1);
443
+ redis_attach_cmd(r, cmd);
444
+ fiobj_free(msg);
445
+ return 0;
446
+ }
447
+ /* *****************************************************************************
448
+ Object Creation
449
+ ***************************************************************************** */
292
450
 
293
- /**
294
- See the {pubsub.h} file for documentation about engines.
451
+ static void redis_on_startup(const pubsub_engine_s *r_) {
452
+ redis_engine_s *r = en2redis(r_);
453
+ /* start adding one connection, so add one reference. */
454
+ spn_add(&r->ref, 1);
455
+ if (facil_parent_pid() == getpid()) {
456
+ defer((void (*)(void *, void *))redis_on_sub_connect_fail, 0,
457
+ &r->sub_data.protocol);
458
+ } else {
459
+ /* workers don't need to subscribe, tha't only on the root process. */
460
+ defer((void (*)(void *, void *))redis_on_pub_connect_fail, 0,
461
+ &r->pub_data.protocol);
462
+ }
463
+ }
295
464
 
296
- function names speak for themselves ;-)
297
- */
298
465
  #undef redis_engine_create
299
- pubsub_engine_s *redis_engine_create(struct redis_engine_create_args a) {
300
- if (!a.port) {
466
+ pubsub_engine_s *redis_engine_create(struct redis_engine_create_args args) {
467
+ if (!args.address)
301
468
  return NULL;
302
- }
303
- size_t addr_len = a.address ? strlen(a.address) : 0;
304
- size_t port_len = strlen(a.port);
305
- redis_engine_s *e = malloc(sizeof(*e) + addr_len + port_len + 2);
306
- *e = (redis_engine_s){
307
- .engine = {.subscribe = subscribe,
308
- .unsubscribe = unsubscribe,
309
- .publish = publish},
310
- .address = (char *)(e + 1),
311
- .port = ((char *)(e + 1) + addr_len + 1),
312
- .ref = 1,
313
- .sub_parser = resp_parser_new(),
314
- .pub_parser = resp_parser_new(),
315
- .callbacks = FIO_LIST_INIT_STATIC(e->callbacks),
316
- .active = 1,
317
- .sub_state = 1,
318
- .pub_state = 1,
469
+ if (!args.port)
470
+ args.port = "6379";
471
+ size_t port_len = 0;
472
+ size_t address_len = 0;
473
+ if (args.auth && !args.auth_len)
474
+ args.auth_len = strlen(args.auth);
475
+ if (args.address)
476
+ address_len = strlen(args.address);
477
+ if (args.port)
478
+ port_len = strlen(args.port);
479
+ redis_engine_s *r =
480
+ malloc(sizeof(*r) + REDIS_READ_BUFFER + REDIS_READ_BUFFER + 2 +
481
+ address_len + port_len + (args.auth_len ? args.auth_len + 32 : 0));
482
+ *r = (redis_engine_s){
483
+ .id_protection = 15,
484
+ .flag = 1,
485
+ .ping_int = args.ping_interval,
486
+ .callbacks = FIO_LS_INIT(r->callbacks),
487
+ .port = (char *)r->buf + (REDIS_READ_BUFFER + REDIS_READ_BUFFER),
488
+ .address = (char *)r->buf + (REDIS_READ_BUFFER + REDIS_READ_BUFFER) +
489
+ port_len + 1,
490
+ .auth = (char *)r->buf + (REDIS_READ_BUFFER + REDIS_READ_BUFFER) +
491
+ port_len + address_len + 2,
492
+ .auth_len = args.auth_len,
493
+ .en =
494
+ {
495
+ .subscribe = redis_on_subscribe,
496
+ .unsubscribe = redis_on_unsubscribe,
497
+ .publish = redis_on_publish,
498
+ .on_startup = redis_on_startup,
499
+ },
500
+ .pub_data =
501
+ {
502
+ .is_pub = 1,
503
+ .protocol =
504
+ {
505
+ .service = "redis engine publishing connection",
506
+ .on_data = redis_on_data,
507
+ .on_close = redis_pub_on_close,
508
+ .ping = redis_pub_ping,
509
+ .on_shutdown = redis_on_shutdown,
510
+ },
511
+ },
512
+ .sub_data =
513
+ {
514
+ .protocol =
515
+ {
516
+ .service = "redis engine subscribing connection",
517
+ .on_data = redis_on_data,
518
+ .on_close = redis_sub_on_close,
519
+ .ping = redis_sub_ping,
520
+ .on_shutdown = redis_on_shutdown,
521
+ },
522
+ },
523
+ .ref = 1, /* starts with only the user handle */
319
524
  };
320
- if (a.address)
321
- memcpy(e->address, a.address, addr_len);
322
- else
323
- e->address = NULL;
324
- e->address[addr_len] = 0;
325
- memcpy(e->port, a.port, port_len);
326
- e->port[port_len] = 0;
327
-
328
- e->sub_ctx =
329
- redis_create_context(.parser = e->sub_parser, .auth = (char *)a.auth,
330
- .auth_len = a.auth_len, .on_message = on_message_sub,
331
- .on_close = on_close_sub, .on_open = on_open_sub,
332
- .udata = e, .ping = a.ping_interval),
333
-
334
- e->pub_ctx =
335
- redis_create_context(.parser = e->pub_parser, .auth = (char *)a.auth,
336
- .auth_len = a.auth_len, .on_message = on_message_pub,
337
- .on_close = on_close_pub, .on_open = on_open_pub,
338
- .udata = e, .ping = a.ping_interval, ),
525
+ memcpy(r->port, args.port, port_len);
526
+ r->port[port_len] = 0;
527
+ memcpy(r->address, args.address, address_len);
528
+ r->address[address_len] = 0;
529
+ if (args.auth) {
530
+ char *pos = r->auth;
531
+ pos = memcpy(pos, "*2\r\n$4\r\nAUTH\r\n$", 15);
532
+ pos += fio_ltoa(pos, args.auth_len, 10);
533
+ *pos++ = '\r';
534
+ *pos++ = '\n';
535
+ pos = memcpy(pos, args.auth, args.auth_len);
536
+ pos[0] = 0;
537
+ args.auth_len = (uintptr_t)pos - (uintptr_t)r->auth;
538
+ } else {
539
+ r->auth = NULL;
540
+ }
541
+ pubsub_engine_register(&r->en);
542
+ if (facil_is_running())
543
+ redis_on_startup(&r->en);
544
+ return &r->en;
545
+ }
339
546
 
340
- defer(initialize_engine, e, NULL);
341
- return (pubsub_engine_s *)e;
547
+ void redis_engine_destroy(pubsub_engine_s *e) {
548
+ if (e == PUBSUB_CLUSTER_ENGINE || e == PUBSUB_PROCESS_ENGINE) {
549
+ fprintf(stderr, "WARNING: (redis free) trying to distroy one of the "
550
+ "core engines\n");
551
+ return;
552
+ }
553
+ redis_engine_s *r = en2redis(e);
554
+ if (r->id_protection != 15) {
555
+ fprintf(
556
+ stderr,
557
+ "FATAL ERROR: (redis) engine pointer incorrect, protection failure.\n");
558
+ exit(-1);
559
+ }
560
+ pubsub_engine_deregister(&r->en);
561
+ r->flag = 0;
562
+ if (r->pub_data.uuid)
563
+ sock_close(r->pub_data.uuid);
564
+ if (r->sub_data.uuid)
565
+ sock_close(r->sub_data.uuid);
566
+ redis_free(r);
342
567
  }
343
568
 
344
569
  /**
345
- See the {pubsub.h} file for documentation about engines.
570
+ * Sends a Redis command through the engine's connection.
571
+ *
572
+ * The response will be sent back using the optional callback. `udata` is passed
573
+ * along untouched.
574
+ *
575
+ * The message will be resent on network failures, until a response validates
576
+ * the fact that the command was sent (or the engine is destroyed).
577
+ *
578
+ * Note: NEVER call Pub/Sub commands using this function, as it will violate the
579
+ * Redis connection's protocol (best case scenario, a disconnection will occur
580
+ * before and messages are lost).
581
+ */
582
+ intptr_t redis_engine_send(pubsub_engine_s *engine, FIOBJ command, FIOBJ data,
583
+ void (*callback)(pubsub_engine_s *e, FIOBJ reply,
584
+ void *udata),
585
+ void *udata) {
586
+ if (engine == PUBSUB_CLUSTER_ENGINE || engine == PUBSUB_PROCESS_ENGINE) {
587
+ fprintf(stderr, "WARNING: (redis send) trying to use one of the "
588
+ "core engines\n");
589
+ return -1;
590
+ }
591
+ redis_engine_s *r = en2redis(engine);
592
+ if (r->id_protection != 15) {
593
+ fprintf(stderr,
594
+ "ERROR: (redis) engine pointer incorrect, protection failure.\n");
595
+ return -1;
596
+ }
597
+ FIOBJ tmp = fiobj2resp_tmp(command, data);
598
+ fio_cstr_s cmd_str = fiobj_obj2cstr(tmp);
599
+ redis_commands_s *cmd = malloc(sizeof(*cmd) + cmd_str.len + 1);
600
+ *cmd = (redis_commands_s){
601
+ .callback = callback, .udata = udata, .cmd_len = cmd_str.len};
602
+ memcpy(cmd->cmd, cmd_str.data, cmd_str.len + 1);
603
+ redis_attach_cmd(r, cmd);
604
+ return 0;
605
+ }
346
606
 
347
- function names speak for themselves ;-)
348
- */
349
- void redis_engine_destroy(const pubsub_engine_s *engine) {
350
- redis_engine_s *r = (redis_engine_s *)engine;
607
+ /* *****************************************************************************
608
+ Simple RESP formatting
609
+ ***************************************************************************** */
351
610
 
352
- spn_lock(&r->lock);
353
- callbacks_s *cb;
354
- fio_list_for_each(callbacks_s, node, cb, r->callbacks) free(cb);
355
- sock_force_close(r->pub);
356
- sock_force_close(r->sub);
611
+ /**
612
+ * Converts FIOBJ objects into a RESP string (client mode).
613
+ *
614
+ * Don't call `fiobj_free`, object will self-destruct.
615
+ */
616
+ static FIOBJ fiobj2resp_tmp(FIOBJ obj1, FIOBJ obj2) {
617
+ FIOBJ dest = fiobj_str_tmp();
618
+ if (!obj2 || FIOBJ_IS_NULL(obj2)) {
619
+ fio_cstr_s s = fiobj_obj2cstr(obj1);
620
+ fiobj_str_write(dest, "*1\r\n$", 5);
621
+ fiobj_str_join(dest, fiobj_num_tmp(s.len));
622
+ fiobj_str_write(dest, "\r\n", 2);
623
+ fiobj_str_write(dest, s.data, s.len);
624
+ fiobj_str_write(dest, "\r\n", 2);
625
+ } else if (FIOBJ_TYPE(obj2) == FIOBJ_T_ARRAY) {
626
+ size_t count = fiobj_ary_count(obj2);
627
+ fiobj_str_write(dest, "*", 1);
628
+ fiobj_str_join(dest, fiobj_num_tmp(count + 1));
629
+ fiobj_str_write(dest, "\r\n$", 3);
630
+
631
+ fio_cstr_s s = fiobj_obj2cstr(obj1);
632
+ fiobj_str_join(dest, fiobj_num_tmp(s.len));
633
+ fiobj_str_write(dest, "\r\n", 2);
634
+ fiobj_str_write(dest, s.data, s.len);
635
+ fiobj_str_write(dest, "\r\n", 2);
636
+
637
+ FIOBJ *ary = fiobj_ary2ptr(obj2);
638
+
639
+ for (size_t i = 0; i < count; ++i) {
640
+ s = fiobj_obj2cstr(ary[i]);
641
+ fiobj_str_write(dest, "$", 1);
642
+ fiobj_str_join(dest, fiobj_num_tmp(s.len));
643
+ fiobj_str_write(dest, "\r\n", 2);
644
+ fiobj_str_write(dest, s.data, s.len);
645
+ fiobj_str_write(dest, "\r\n", 2);
646
+ }
357
647
 
358
- r->active = 0;
359
- if (dealloc_engine(r))
360
- return;
361
- spn_unlock(&r->lock);
648
+ } else if (FIOBJ_TYPE(obj2) == FIOBJ_T_HASH) {
649
+ FIOBJ json = fiobj_obj2json(obj2, 0);
650
+ fio_cstr_s s = fiobj_obj2cstr(obj1);
651
+ fiobj_str_write(dest, "*2\r\n$", 5);
652
+ fiobj_str_join(dest, fiobj_num_tmp(s.len));
653
+ fiobj_str_write(dest, "\r\n", 2);
654
+ fiobj_str_write(dest, s.data, s.len);
655
+ fiobj_str_write(dest, "\r\n$", 3);
656
+ s = fiobj_obj2cstr(json);
657
+ fiobj_str_join(dest, fiobj_num_tmp(s.len));
658
+ fiobj_str_write(dest, "\r\n", 2);
659
+ fiobj_str_write(dest, s.data, s.len);
660
+ fiobj_str_write(dest, "\r\n", 2);
661
+ fiobj_free(json);
662
+
663
+ } else {
664
+ fio_cstr_s s = fiobj_obj2cstr(obj1);
665
+ fiobj_str_write(dest, "*2\r\n$", 5);
666
+ fiobj_str_join(dest, fiobj_num_tmp(s.len));
667
+ fiobj_str_write(dest, "\r\n", 2);
668
+ fiobj_str_write(dest, s.data, s.len);
669
+ fiobj_str_write(dest, "\r\n$", 3);
670
+ s = fiobj_obj2cstr(obj2);
671
+ fiobj_str_join(dest, fiobj_num_tmp(s.len));
672
+ fiobj_str_write(dest, "\r\n", 2);
673
+ fiobj_str_write(dest, s.data, s.len);
674
+ fiobj_str_write(dest, "\r\n", 2);
675
+ }
676
+ return dest;
362
677
  }
363
678
 
364
679
  /* *****************************************************************************
365
- Sending Data
680
+ RESP parser callbacks
366
681
  ***************************************************************************** */
367
682
 
683
+ /** a local static callback, called when a parser / protocol error occurs. */
684
+ static int resp_on_parser_error(resp_parser_s *parser) {
685
+ struct redis_engine_internal_s *i =
686
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
687
+ fprintf(stderr,
688
+ "ERROR: (redis) parser error - attempting to restart connection.\n");
689
+ sock_close(i->uuid);
690
+ return -1;
691
+ }
692
+
693
+ /** a local static callback, called when the RESP message is complete. */
694
+ static int resp_on_message(resp_parser_s *parser) {
695
+ struct redis_engine_internal_s *i =
696
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
697
+ FIOBJ msg = i->ary ? i->ary : i->str;
698
+ if (i->is_pub) {
699
+ /* publishing / command parser */
700
+ redis_cmd_reply(FIO_LS_EMBD_OBJ(redis_engine_s, pub_data, i), msg);
701
+ } else {
702
+ /* subscriotion parser */
703
+ if (FIOBJ_TYPE(msg) != FIOBJ_T_ARRAY) {
704
+ if (FIOBJ_TYPE(msg) != FIOBJ_T_STRING || fiobj_obj2cstr(msg).len != 4 ||
705
+ fiobj_obj2cstr(msg).data[0] != 'P') {
706
+ fprintf(stderr, "WARNING: (redis) unexpected data format in "
707
+ "subscription stream:\n");
708
+ fio_cstr_s tmp = fiobj_obj2cstr(msg);
709
+ fprintf(stderr, " %s\n", tmp.data);
710
+ }
711
+ } else {
712
+ // FIOBJ *ary = fiobj_ary2ptr(msg);
713
+ // for (size_t i = 0; i < fiobj_ary_count(msg); ++i) {
714
+ // fio_cstr_s tmp = fiobj_obj2cstr(ary[i]);
715
+ // fprintf(stderr, "(%lu) %s\n", (unsigned long)i, tmp.data);
716
+ // }
717
+ fio_cstr_s tmp = fiobj_obj2cstr(fiobj_ary_index(msg, 0));
718
+ redis_engine_s *r = parser2redis(parser);
719
+ if (tmp.len == 7) { /* "message" */
720
+ fiobj_free(r->last_ch);
721
+ r->last_ch = fiobj_dup(fiobj_ary_index(msg, 1));
722
+ pubsub_publish(.channel = r->last_ch,
723
+ .message = fiobj_ary_index(msg, 2),
724
+ .engine = PUBSUB_CLUSTER_ENGINE);
725
+ } else if (tmp.len == 8) { /* "pmessage" */
726
+ if (!fiobj_iseq(r->last_ch, fiobj_ary_index(msg, 2)))
727
+ pubsub_publish(.channel = fiobj_ary_index(msg, 2),
728
+ .message = fiobj_ary_index(msg, 3),
729
+ .engine = PUBSUB_CLUSTER_ENGINE);
730
+ }
731
+ }
732
+ }
733
+ /* cleanup */
734
+ fiobj_free(msg);
735
+ i->ary = FIOBJ_INVALID;
736
+ i->str = FIOBJ_INVALID;
737
+ return 0;
738
+ }
739
+
740
+ /** a local helper to add parsed objects to the data store. */
741
+ static inline void resp_add_obj(struct redis_engine_internal_s *dest, FIOBJ o) {
742
+ if (dest->ary) {
743
+ if (!dest->ary_count)
744
+ fprintf(stderr,
745
+ "ERROR: (redis) array overflow indicates a protocol error.\n");
746
+ fiobj_ary_push(dest->ary, o);
747
+ --dest->ary_count;
748
+ }
749
+ dest->str = o;
750
+ }
751
+
752
+ /** a local static callback, called when a Number object is parsed. */
753
+ static int resp_on_number(resp_parser_s *parser, int64_t num) {
754
+ struct redis_engine_internal_s *data =
755
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
756
+ resp_add_obj(data, fiobj_num_new(num));
757
+ return 0;
758
+ }
759
+ /** a local static callback, called when a OK message is received. */
760
+ static int resp_on_okay(resp_parser_s *parser) {
761
+ struct redis_engine_internal_s *data =
762
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
763
+ resp_add_obj(data, fiobj_true());
764
+ return 0;
765
+ }
766
+ /** a local static callback, called when NULL is received. */
767
+ static int resp_on_null(resp_parser_s *parser) {
768
+ struct redis_engine_internal_s *data =
769
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
770
+ resp_add_obj(data, fiobj_null());
771
+ return 0;
772
+ }
773
+
368
774
  /**
369
- Sends a Redis message through the engine's connection. The response will be sent
370
- back using the optional callback. `udata` is passed along untouched.
371
- */
372
- intptr_t redis_engine_send(pubsub_engine_s *e, resp_object_s *data,
373
- void (*callback)(pubsub_engine_s *e,
374
- resp_object_s *reply, void *udata),
375
- void *udata) {
376
- if (!e || !data)
377
- return -1;
775
+ * a local static callback, called when a String should be allocated.
776
+ *
777
+ * `str_len` is the expected number of bytes that will fill the final string
778
+ * object, without any NUL byte marker (the string might be binary).
779
+ *
780
+ * If this function returns any value besides 0, parsing is stopped.
781
+ */
782
+ static int resp_on_start_string(resp_parser_s *parser, size_t str_len) {
783
+ struct redis_engine_internal_s *data =
784
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
785
+ resp_add_obj(data, fiobj_str_buf(str_len));
786
+ return 0;
787
+ }
788
+ /** a local static callback, called as String objects are streamed. */
789
+ static int resp_on_string_chunk(resp_parser_s *parser, void *data, size_t len) {
790
+ struct redis_engine_internal_s *i =
791
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
792
+ fiobj_str_write(i->str, data, len);
793
+ return 0;
794
+ }
795
+ /** a local static callback, called when a String object had finished
796
+ * streaming.
797
+ */
798
+ static int resp_on_end_string(resp_parser_s *parser) {
799
+ return 0;
800
+ (void)parser;
801
+ }
378
802
 
379
- redis_engine_s *r = (redis_engine_s *)e;
380
- size_t len = 0;
381
- resp_format(r->pub_parser, NULL, &len, data);
382
- if (!len)
383
- return -1;
803
+ /** a local static callback, called an error message is received. */
804
+ static int resp_on_err_msg(resp_parser_s *parser, void *data, size_t len) {
805
+ struct redis_engine_internal_s *i =
806
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
807
+ resp_add_obj(i, fiobj_str_new(data, len));
808
+ return 0;
809
+ }
384
810
 
385
- callbacks_s *cb = malloc(sizeof(*cb) + len);
386
- *cb = (callbacks_s){
387
- .node = FIO_LIST_INIT_STATIC(cb->node),
388
- .callback = callback,
389
- .udata = udata,
390
- .len = len,
391
- };
392
- resp_format(r->pub_parser, (uint8_t *)(cb + 1), &len, data);
393
- spn_lock(&r->lock);
394
- fio_list_push(callbacks_s, node, r->callbacks, cb);
395
- spn_unlock(&r->lock);
396
- schedule_pub_send(r, r->pub);
811
+ /**
812
+ * a local static callback, called when an Array should be allocated.
813
+ *
814
+ * `array_len` is the expected number of objects that will fill the Array
815
+ * object.
816
+ *
817
+ * There's no `resp_on_end_array` callback since the RESP protocol assumes the
818
+ * message is finished along with the Array (`resp_on_message` is called).
819
+ * However, just in case a non-conforming client/server sends nested Arrays,
820
+ * the callback should test against possible overflow or nested Array endings.
821
+ *
822
+ * If this function returns any value besides 0, parsing is stopped.
823
+ */
824
+ static int resp_on_start_array(resp_parser_s *parser, size_t array_len) {
825
+ struct redis_engine_internal_s *i =
826
+ FIO_LS_EMBD_OBJ(struct redis_engine_internal_s, parser, parser);
827
+ if (i->ary) {
828
+ /* this is an error ... */
829
+ fprintf(stderr, "ERROR: (redis) RESP protocol violation "
830
+ "(array within array).\n");
831
+ return -1;
832
+ }
833
+ i->ary = fiobj_ary_new2(array_len);
834
+ i->ary_count = array_len;
397
835
  return 0;
398
836
  }