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
@@ -12,8 +12,6 @@ Feel free to copy, use and enjoy according to the license provided.
12
12
 
13
13
  void Iodine_init_websocket(void);
14
14
 
15
- void iodine_websocket_upgrade(http_request_s *request,
16
- http_response_s *response, VALUE handler,
17
- size_t max_msg, uint8_t ping);
18
-
15
+ void iodine_upgrade_websocket(http_s *request, VALUE handler);
16
+ void iodine_upgrade_sse(http_s *h, VALUE handler);
19
17
  #endif
data/ext/iodine/pubsub.c CHANGED
@@ -7,8 +7,8 @@ Feel free to copy, use and enjoy according to the license provided.
7
7
  #include "spnlock.inc"
8
8
 
9
9
  #include "facil.h"
10
- #include "fio_dict.h"
11
- #include "fio_hash_table.h"
10
+ #include "fio_llist.h"
11
+ #include "fiobj.h"
12
12
  #include "pubsub.h"
13
13
 
14
14
  #include <errno.h>
@@ -18,529 +18,823 @@ Feel free to copy, use and enjoy according to the license provided.
18
18
  #include <stdlib.h>
19
19
  #include <string.h>
20
20
 
21
+ #include "fio_mem.h"
22
+
23
+ /* used later on */
24
+ static int pubsub_glob_match(uint8_t *data, size_t data_len, uint8_t *pattern,
25
+ size_t pat_len);
26
+
27
+ #define PUBSUB_FACIL_CLUSTER_CHANNEL_FILTER ((int32_t)-1)
28
+ #define PUBSUB_FACIL_CLUSTER_PATTERN_FILTER ((int32_t)-2)
29
+ #define PUBSUB_FACIL_CLUSTER_CHANNEL_SUB_FILTER ((int32_t)-3)
30
+ #define PUBSUB_FACIL_CLUSTER_PATTERN_SUB_FILTER ((int32_t)-4)
31
+ #define PUBSUB_FACIL_CLUSTER_CHANNEL_UNSUB_FILTER ((int32_t)-5)
32
+ #define PUBSUB_FACIL_CLUSTER_PATTERN_UNSUB_FILTER ((int32_t)-6)
33
+
21
34
  /* *****************************************************************************
22
- Data Structures / State
35
+ The Hash Map (macros and the include instruction for `fio_hashmap.h`)
23
36
  ***************************************************************************** */
24
37
 
38
+ /* the hash key type for string keys */
25
39
  typedef struct {
26
- volatile uint64_t ref;
27
- struct pubsub_publish_args pub;
28
- } msg_s;
40
+ uintptr_t hash;
41
+ FIOBJ obj;
42
+ } fio_hash_key_s;
43
+
44
+ static inline int fio_hash_fiobj_keys_eq(fio_hash_key_s a, fio_hash_key_s b) {
45
+ if (a.obj == b.obj)
46
+ return 1;
47
+ fio_cstr_s sa = fiobj_obj2cstr(a.obj);
48
+ fio_cstr_s sb = fiobj_obj2cstr(b.obj);
49
+ return sa.len == sb.len && !memcmp(sa.data, sb.data, sa.len);
50
+ }
51
+ /* define the macro to set the key type */
52
+ #define FIO_HASH_KEY_TYPE fio_hash_key_s
53
+ /* the macro that returns the key's hash value */
54
+ #define FIO_HASH_KEY2UINT(key) ((key).hash)
55
+ /* Compare the keys using length testing and `memcmp` */
56
+ #define FIO_HASH_COMPARE_KEYS(k1, k2) \
57
+ ((k1).obj == (k2).obj || fio_hash_fiobj_keys_eq((k1), (k2)))
58
+ /* an "all bytes are zero" invalid key */
59
+ #define FIO_HASH_KEY_INVALID ((fio_hash_key_s){.obj = FIOBJ_INVALID})
60
+ /* tests if a key is the invalid key */
61
+ #define FIO_HASH_KEY_ISINVALID(key) ((key).obj == FIOBJ_INVALID && !key.hash)
62
+ /* creates a persistent copy of the key's string */
63
+ #define FIO_HASH_KEY_COPY(key) \
64
+ ((fio_hash_key_s){.hash = (key).hash, .obj = fiobj_dup((key).obj)})
65
+ /* frees the allocated string */
66
+ #define FIO_HASH_KEY_DESTROY(key) (fiobj_free((key).obj))
67
+
68
+ #define FIO_OBJ2KEY(fiobj) \
69
+ ((fio_hash_key_s){.hash = fiobj_obj2hash((fiobj)), .obj = (fiobj)})
70
+
71
+ #include "fio_hashmap.h"
29
72
 
30
- typedef struct {
31
- fio_ht_s clients;
32
- const pubsub_engine_s *engine;
33
- struct {
34
- char *name;
35
- uint32_t len;
36
- };
37
- union {
38
- fio_dict_s dict;
39
- fio_list_s list;
40
- } channels;
41
- unsigned use_pattern : 1;
42
- } channel_s;
73
+ /* *****************************************************************************
74
+ Channel and Client Data Structures
75
+ ***************************************************************************** */
43
76
 
44
- typedef struct pubsub_sub_s {
45
- fio_ht_node_s clients;
77
+ typedef struct {
78
+ /* clients are nodes in a list. */
79
+ fio_ls_embd_s node;
80
+ /* a reference counter (how many messages pending) */
81
+ size_t ref;
82
+ /* a subscription counter (protection against multiple unsubscribe calls) */
83
+ size_t sub_count;
84
+ /* a pointer to the channel data */
85
+ void *parent;
86
+ /** The on message callback. the `*msg` pointer is to a temporary object. */
46
87
  void (*on_message)(pubsub_message_s *msg);
88
+ /** An optional callback for when a subscription is fully canceled. */
47
89
  void (*on_unsubscribe)(void *udata1, void *udata2);
90
+ /** Opaque user data#1 */
48
91
  void *udata1;
92
+ /** Opaque user data#2 .. using two allows some allocations to be avoided. */
49
93
  void *udata2;
50
- channel_s *parent;
51
- volatile uint64_t active;
52
- uint32_t ref;
94
+ /** Task lock (per client-channel combination */
95
+ spn_lock_i lock;
53
96
  } client_s;
54
97
 
55
98
  typedef struct {
56
- msg_s *origin;
57
- pubsub_message_s msg;
58
- } msg_container_s;
59
-
60
- static fio_dict_s pubsub_channels = FIO_DICT_INIT_STATIC;
61
- static fio_list_s pubsub_patterns = FIO_LIST_INIT_STATIC(pubsub_patterns);
99
+ /* the root for the client's list */
100
+ fio_ls_embd_s clients;
101
+ /** The channel name. */
102
+ FIOBJ name;
103
+ /** Use pattern matching for channel subscription. */
104
+ unsigned use_pattern : 1;
105
+ /** Use pattern matching for channel subscription. */
106
+ unsigned publish2cluster : 1;
107
+ } channel_s;
62
108
 
63
- spn_lock_i pubsub_GIL = SPN_LOCK_INIT;
109
+ static fio_hash_s patterns;
110
+ static fio_hash_s channels;
111
+ static fio_hash_s clients;
112
+ static fio_hash_s engines;
113
+ static spn_lock_i lock = SPN_LOCK_INIT;
114
+ static spn_lock_i engn_lock = SPN_LOCK_INIT;
64
115
 
65
116
  /* *****************************************************************************
66
- Helpers
117
+ Channel and Client Management
67
118
  ***************************************************************************** */
68
119
 
69
- static void pubsub_free_client(void *client_, void *ignr) {
70
- client_s *client = client_;
71
- if (spn_sub(&client->active, 1))
72
- return;
73
- if (client->on_unsubscribe)
74
- client->on_unsubscribe(client->udata1, client->udata2);
75
- free(client);
120
+ /* for engine thingy */
121
+ static void pubsub_on_channel_create(channel_s *ch);
122
+ /* for engine thingy */
123
+ static void pubsub_on_channel_destroy(channel_s *ch);
124
+
125
+ static void pubsub_deferred_unsub(void *cl_, void *ignr) {
126
+ client_s *cl = cl_;
127
+ cl->on_unsubscribe(cl->udata1, cl->udata2);
128
+ free(cl);
76
129
  (void)ignr;
77
130
  }
78
131
 
79
- static void pubsub_free_msg(void *msg_, void *ignr) {
80
- msg_s *msg = msg_;
81
- if (!spn_sub(&msg->ref, 1)) {
82
- free(msg);
132
+ static inline void client_test4free(client_s *cl) {
133
+ if (spn_sub(&cl->ref, 1)) {
134
+ /* client is still being used. */
135
+ return;
83
136
  }
84
- (void)ignr;
137
+ if (cl->on_unsubscribe) {
138
+ /* we'll call the callback before freeing the object. */
139
+ defer(pubsub_deferred_unsub, cl, NULL);
140
+ return;
141
+ }
142
+ free(cl);
85
143
  }
86
144
 
87
- static void pubsub_deliver_msg(void *client_, void *msg_) {
88
- client_s *cl = client_;
89
- msg_s *msg = msg_;
90
- msg_container_s clmsg = {
91
- .origin = msg,
92
- .msg =
93
- {
94
- .engine = msg->pub.engine,
95
- .channel.name = msg->pub.channel.name,
96
- .channel.len = msg->pub.channel.len,
97
- .msg.data = msg->pub.msg.data,
98
- .msg.len = msg->pub.msg.len,
99
- .use_pattern = msg->pub.use_pattern,
100
- .udata1 = cl->udata1,
101
- .udata2 = cl->udata2,
102
- .subscription = cl,
103
- },
104
- };
105
- cl->on_message(&clmsg.msg);
106
- pubsub_free_msg(msg, NULL);
107
- pubsub_free_client(cl, NULL);
145
+ static inline uint64_t client_compute_hash(client_s client) {
146
+ return (((((uint64_t)(client.on_message) *
147
+ ((uint64_t)client.udata1 ^ 0x736f6d6570736575ULL)) >>
148
+ 5) |
149
+ (((uint64_t)(client.on_unsubscribe) *
150
+ ((uint64_t)client.udata1 ^ 0x736f6d6570736575ULL))
151
+ << 47)) ^
152
+ ((uint64_t)client.udata2 ^ 0x646f72616e646f6dULL));
108
153
  }
109
154
 
110
- /* *****************************************************************************
111
- Default Engine / Cluster Support
112
- ***************************************************************************** */
113
-
114
- /** Used internally to identify oub/sub cluster messages. */
115
- #define FACIL_PUBSUB_CLUSTER_MESSAGE_ID ((int32_t)-1)
116
-
117
- /*
118
- a `ppublish` / `publish` found this channel as a match...
119
- Pubblish to clients and test against subscribed patterns.
120
- */
121
- static void pubsub_publish_matched_channel(fio_dict_s *ch_, void *msg_) {
122
- msg_s *msg = msg_;
123
- client_s *cl;
124
- if (ch_) {
125
- channel_s *channel = fio_node2obj(channel_s, channels, ch_);
126
- fio_ht_for_each(client_s, clients, cl, channel->clients) {
127
- spn_add(&msg->ref, 1);
128
- spn_add(&cl->active, 1);
129
- defer(pubsub_deliver_msg, cl, msg);
130
- }
155
+ static client_s *pubsub_client_new(client_s client, channel_s channel) {
156
+ if (!client.on_message || !channel.name) {
157
+ fprintf(stderr,
158
+ "ERROR: (pubsub) subscription request failed. missing on of:\n"
159
+ " 1. channel name.\n"
160
+ " 2. massage handler.\n");
161
+ if (client.on_unsubscribe)
162
+ client.on_unsubscribe(client.udata1, client.udata2);
163
+ return NULL;
131
164
  }
132
- channel_s *pattern;
133
- fio_list_for_each(channel_s, channels.list, pattern, pubsub_patterns) {
134
- if (msg->pub.engine == pattern->engine &&
135
- fio_glob_match((uint8_t *)msg->pub.channel.name, msg->pub.channel.len,
136
- (uint8_t *)pattern->name, pattern->len)) {
137
- fio_ht_for_each(client_s, clients, cl, pattern->clients) {
138
- spn_add(&msg->ref, 1);
139
- spn_add(&cl->active, 1);
140
- defer(pubsub_deliver_msg, cl, msg);
141
- }
165
+ uint64_t channel_hash = fiobj_obj2hash(channel.name);
166
+ uint64_t client_hash = client_compute_hash(client);
167
+ spn_lock(&lock);
168
+ /* ignore if client exists. */
169
+ client_s *cl = fio_hash_find(
170
+ &clients, (fio_hash_key_s){.hash = client_hash, .obj = channel.name});
171
+ if (cl) {
172
+ cl->sub_count++;
173
+ spn_unlock(&lock);
174
+ return cl;
175
+ }
176
+ /* no client, we need a new client */
177
+ cl = malloc(sizeof(*cl));
178
+ if (!cl) {
179
+ perror("FATAL ERROR: (pubsub) client memory allocation error");
180
+ exit(errno);
181
+ }
182
+ *cl = client;
183
+ cl->ref = 1;
184
+ cl->sub_count = 1;
185
+
186
+ fio_hash_insert(
187
+ &clients, (fio_hash_key_s){.hash = client_hash, .obj = channel.name}, cl);
188
+
189
+ /* test for existing channel */
190
+ fio_hash_s *ch_hashmap = (channel.use_pattern ? &patterns : &channels);
191
+ channel_s *ch = fio_hash_find(
192
+ ch_hashmap, (fio_hash_key_s){.hash = channel_hash, .obj = channel.name});
193
+ if (!ch) {
194
+ /* open new channel */
195
+ ch = malloc(sizeof(*ch));
196
+ if (!ch) {
197
+ perror("FATAL ERROR: (pubsub) channel memory allocation error");
198
+ exit(errno);
142
199
  }
200
+ *ch = (channel_s){
201
+ .name = fiobj_dup(channel.name),
202
+ .clients = FIO_LS_INIT(ch->clients),
203
+ .use_pattern = channel.use_pattern,
204
+ .publish2cluster = channel.publish2cluster,
205
+ };
206
+ fio_hash_insert(ch_hashmap,
207
+ (fio_hash_key_s){.hash = channel_hash, .obj = channel.name},
208
+ ch);
209
+ pubsub_on_channel_create(ch);
210
+ } else {
211
+ /* channel exists */
143
212
  }
213
+ cl->parent = ch;
214
+ fio_ls_embd_push(&ch->clients, &cl->node);
215
+ spn_unlock(&lock);
216
+ return cl;
144
217
  }
145
218
 
146
- static void pubsub_perform_publish(void *msg_, void *ignr) {
147
- msg_s *msg = msg_;
148
- fio_dict_s *channel;
149
- spn_lock(&pubsub_GIL);
219
+ /** Destroys a client (and empty channels as well) */
220
+ static int pubsub_client_destroy(client_s *client) {
221
+ if (!client || !client->parent)
222
+ return -1;
223
+ channel_s *ch = client->parent;
150
224
 
151
- if (msg->pub.use_pattern) {
152
- fio_dict_each_match_glob(fio_dict_prefix(&pubsub_channels,
153
- (void *)&msg->pub.engine,
154
- sizeof(void *)),
155
- msg->pub.channel.name, msg->pub.channel.len,
156
- pubsub_publish_matched_channel, msg);
225
+ fio_hash_s *ch_hashmap = (ch->use_pattern ? &patterns : &channels);
226
+ uint64_t channel_hash = fiobj_obj2hash(ch->name);
227
+ uint64_t client_hash = client_compute_hash(*client);
228
+ uint8_t is_ch_any;
229
+ spn_lock(&lock);
230
+ if ((client->sub_count -= 1)) {
231
+ spn_unlock(&lock);
232
+ return 0;
233
+ }
234
+ fio_ls_embd_remove(&client->node);
235
+ fio_hash_insert(&clients,
236
+ (fio_hash_key_s){.hash = client_hash, .obj = ch->name}, NULL);
237
+ is_ch_any = fio_ls_embd_any(&ch->clients);
238
+ if (is_ch_any) {
239
+ /* channel still has client - we should keep it */
240
+ (void)0;
157
241
  } else {
158
- channel = (void *)fio_dict_get(fio_dict_prefix(&pubsub_channels,
159
- (void *)&msg->pub.engine,
160
- sizeof(void *)),
161
- msg->pub.channel.name, msg->pub.channel.len);
162
- pubsub_publish_matched_channel(channel, msg);
242
+ channel_s *test = fio_hash_insert(
243
+ ch_hashmap, (fio_hash_key_s){.hash = channel_hash, .obj = ch->name},
244
+ NULL);
245
+ if (test != ch) {
246
+ fprintf(stderr,
247
+ "FATAL ERROR: (pubsub) channel database corruption detected.\n");
248
+ exit(-1);
249
+ }
250
+ if (ch_hashmap->capa > 32 && (ch_hashmap->pos >> 1) > ch_hashmap->count) {
251
+ fio_hash_compact(ch_hashmap);
252
+ }
163
253
  }
164
-
165
- spn_unlock(&pubsub_GIL);
166
- pubsub_free_msg(msg, NULL);
167
- (void)ignr;
168
- }
169
-
170
- static int pubsub_cluster_publish(struct pubsub_publish_args args) {
171
- msg_s *msg = malloc(sizeof(*msg) + args.channel.len + args.msg.len + 2);
172
- if (!msg)
173
- return -1;
174
- *msg = (msg_s){.ref = 1, .pub = args};
175
- struct pubsub_publish_args *pub = &msg->pub;
176
- pub->msg.data = (char *)(pub + 1);
177
- memcpy(pub->msg.data, args.msg.data, args.msg.len);
178
- pub->msg.data[args.msg.len] = 0;
179
- if (pub->channel.name) {
180
- pub->channel.name = pub->msg.data + args.msg.len + 1;
181
- memcpy(pub->channel.name, args.channel.name, args.channel.len);
182
- pub->channel.name[args.channel.len] = 0;
254
+ if ((clients.pos >> 1) > clients.count) {
255
+ // fprintf(stderr, "INFO: (pubsub) reducing client hash map %zu",
256
+ // (size_t)clients.capa);
257
+ fio_hash_compact(&clients);
258
+ // fprintf(stderr, " => %zu (%zu clients)\n", (size_t)clients.capa,
259
+ // (size_t)clients.count);
260
+ }
261
+ spn_unlock(&lock);
262
+ client_test4free(client);
263
+ if (is_ch_any) {
264
+ return 0;
183
265
  }
184
- pub->use_pattern = args.use_pattern;
185
- if (args.push2cluster)
186
- facil_cluster_send(FACIL_PUBSUB_CLUSTER_MESSAGE_ID, msg,
187
- sizeof(*msg) + args.channel.len + args.msg.len + 2);
188
- defer(pubsub_perform_publish, msg, NULL);
266
+ pubsub_on_channel_destroy(ch);
267
+ fiobj_free(ch->name);
268
+ free(ch);
189
269
  return 0;
190
270
  }
191
271
 
192
- static void pubsub_cluster_handle_publishing(void *data, uint32_t len) {
193
- msg_s *msg = data;
194
- if (len != sizeof(*msg) + msg->pub.channel.len + msg->pub.msg.len + 2) {
195
- fprintf(
196
- stderr,
197
- "ERROR: (pub/sub) cluster message size error. Message ignored.\n"
198
- " expecting %zu (ch: %zu, msg: %zu), got %zu\n",
199
- (size_t)(sizeof(*msg) + msg->pub.channel.len + msg->pub.msg.len + 2),
200
- (size_t)msg->pub.channel.len, (size_t)msg->pub.msg.len, (size_t)len);
201
- return;
202
- }
203
- msg = malloc(len);
204
- if (!msg) {
205
- fprintf(stderr, "ERROR: (pub/sub) cluster message allocation error. "
206
- "Message ignored.\n");
207
- return;
272
+ /** finds a pointer to an existing client (matching registration details) */
273
+ static inline client_s *pubsub_client_find(client_s client, channel_s channel) {
274
+ /* the logic is written twice due to locking logic (we don't want to release
275
+ * the lock for `pubsub_client_new`)
276
+ */
277
+ if (!client.on_message || !channel.name) {
278
+ return NULL;
208
279
  }
209
- memcpy(msg, data, len);
210
- msg->ref = 1;
211
- msg->pub.msg.data = (char *)(msg + 1);
212
- if (msg->pub.channel.name)
213
- msg->pub.channel.name = msg->pub.msg.data + msg->pub.msg.len + 1;
214
- defer(pubsub_perform_publish, msg, NULL);
280
+ uint64_t client_hash = client_compute_hash(client);
281
+ spn_lock(&lock);
282
+ client_s *cl = fio_hash_find(
283
+ &clients, (fio_hash_key_s){.hash = client_hash, .obj = channel.name});
284
+ spn_unlock(&lock);
285
+ return cl;
215
286
  }
216
287
 
217
- void pubsub_cluster_init(void) {
218
- /* facil functions are thread safe, so we can call this without a guard. */
219
- facil_cluster_set_handler(FACIL_PUBSUB_CLUSTER_MESSAGE_ID,
220
- pubsub_cluster_handle_publishing);
221
- }
288
+ /* *****************************************************************************
289
+ Subscription API
290
+ ***************************************************************************** */
222
291
 
223
- static int pubsub_cluster_subscribe(const pubsub_engine_s *eng, const char *ch,
224
- size_t ch_len, uint8_t use_pattern) {
225
- return 0;
226
- (void)ch;
227
- (void)ch_len;
228
- (void)use_pattern;
229
- (void)eng;
292
+ /**
293
+ * Subscribes to a specific channel.
294
+ *
295
+ * Returns a subscription pointer or NULL (failure).
296
+ */
297
+ #undef pubsub_subscribe
298
+ pubsub_sub_pt pubsub_subscribe(struct pubsub_subscribe_args args) {
299
+ channel_s channel = {
300
+ .name = args.channel,
301
+ .clients = FIO_LS_INIT(channel.clients),
302
+ .use_pattern = args.use_pattern,
303
+ .publish2cluster = 1,
304
+ };
305
+ client_s client = {.on_message = args.on_message,
306
+ .on_unsubscribe = args.on_unsubscribe,
307
+ .udata1 = args.udata1,
308
+ .udata2 = args.udata2};
309
+ return (pubsub_sub_pt)pubsub_client_new(client, channel);
230
310
  }
311
+ #define pubsub_subscribe(...) \
312
+ pubsub_subscribe((struct pubsub_subscribe_args){__VA_ARGS__})
231
313
 
232
- static void pubsub_cluster_unsubscribe(const pubsub_engine_s *eng,
233
- const char *ch, size_t ch_len,
234
- uint8_t use_pattern) {
235
- (void)ch;
236
- (void)ch_len;
237
- (void)use_pattern;
238
- (void)eng;
314
+ /**
315
+ * This helper searches for an existing subscription.
316
+ *
317
+ * Use with care, NEVER call `pubsub_unsubscribe` more times than you have
318
+ * called `pubsub_subscribe`, since the subscription handle memory is realesed
319
+ * onnce the reference count reaches 0.
320
+ *
321
+ * Returns a subscription pointer or NULL (none found).
322
+ */
323
+ #undef pubsub_find_sub
324
+ pubsub_sub_pt pubsub_find_sub(struct pubsub_subscribe_args args) {
325
+ channel_s channel = {.name = args.channel, .use_pattern = args.use_pattern};
326
+ client_s client = {.on_message = args.on_message,
327
+ .on_unsubscribe = args.on_unsubscribe,
328
+ .udata1 = args.udata1,
329
+ .udata2 = args.udata2};
330
+ return (pubsub_sub_pt)pubsub_client_find(client, channel);
239
331
  }
332
+ #define pubsub_find_sub(...) \
333
+ pubsub_find_sub((struct pubsub_subscribe_args){__VA_ARGS__})
240
334
 
241
- static int pubsub_cluster_eng_publish(const pubsub_engine_s *eng,
242
- const char *ch, size_t ch_len,
243
- const char *msg, size_t msg_len,
244
- uint8_t use_pattern) {
245
- pubsub_cluster_publish((struct pubsub_publish_args){
246
- .engine = eng,
247
- .channel.name = (char *)ch,
248
- .channel.len = ch_len,
249
- .msg.data = (char *)msg,
250
- .msg.len = msg_len,
251
- .use_pattern = use_pattern,
252
- .push2cluster = eng->push2cluster,
253
- });
254
- return 0;
335
+ /**
336
+ * Unsubscribes from a specific subscription.
337
+ *
338
+ * Returns 0 on success and -1 on failure.
339
+ */
340
+ int pubsub_unsubscribe(pubsub_sub_pt subscription) {
341
+ if (!subscription)
342
+ return -1;
343
+ return pubsub_client_destroy((client_s *)subscription);
255
344
  }
256
345
 
257
- const pubsub_engine_s PUBSUB_CLUSTER_ENGINE_S = {
258
- .publish = pubsub_cluster_eng_publish,
259
- .subscribe = pubsub_cluster_subscribe,
260
- .unsubscribe = pubsub_cluster_unsubscribe,
261
- .push2cluster = 1,
262
- };
263
- const pubsub_engine_s *PUBSUB_CLUSTER_ENGINE = &PUBSUB_CLUSTER_ENGINE_S;
264
-
265
- const pubsub_engine_s PUBSUB_PROCESS_ENGINE_S = {
266
- .publish = pubsub_cluster_eng_publish,
267
- .subscribe = pubsub_cluster_subscribe,
268
- .unsubscribe = pubsub_cluster_unsubscribe,
269
- };
270
- const pubsub_engine_s *PUBSUB_PROCESS_ENGINE = &PUBSUB_PROCESS_ENGINE_S;
271
-
272
- const pubsub_engine_s *PUBSUB_DEFAULT_ENGINE = &PUBSUB_CLUSTER_ENGINE_S;
346
+ /**
347
+ * Publishes a message to a channel belonging to a pub/sub service (engine).
348
+ *
349
+ * Returns 0 on success and -1 on failure.
350
+ */
351
+ #undef pubsub_publish
352
+ int pubsub_publish(struct pubsub_message_s m) {
353
+ if (!m.channel || !m.message)
354
+ return -1;
355
+ if (!m.engine) {
356
+ m.engine = PUBSUB_DEFAULT_ENGINE;
357
+ if (!m.engine) {
358
+ m.engine = PUBSUB_CLUSTER_ENGINE;
359
+ if (!m.engine) {
360
+ fprintf(stderr,
361
+ "FATAL ERROR: (pubsub) engine pointer data corrupted! \n");
362
+ exit(-1);
363
+ }
364
+ }
365
+ }
366
+ return m.engine->publish(m.engine, m.channel, m.message);
367
+ // We don't call `fiobj_free` because the data isn't placed into an accessible
368
+ // object.
369
+ }
370
+ #define pubsub_publish(...) \
371
+ pubsub_publish((struct pubsub_message_s){__VA_ARGS__})
273
372
 
274
373
  /* *****************************************************************************
275
- External Engine Bridge
374
+ Engine handling and Management
276
375
  ***************************************************************************** */
277
376
 
278
- #undef pubsub_engine_distribute
279
-
280
- void pubsub_engine_distribute(pubsub_message_s msg) {
281
- pubsub_cluster_publish((struct pubsub_publish_args){
282
- .engine = msg.engine,
283
- .channel.name = msg.channel.name,
284
- .channel.len = msg.channel.len,
285
- .msg.data = msg.msg.data,
286
- .msg.len = msg.msg.len,
287
- .push2cluster = msg.engine->push2cluster,
288
- .use_pattern = msg.use_pattern,
289
- });
290
- }
291
-
292
- static void resubscribe_action(fio_dict_s *ch_, void *eng_) {
293
- if (!eng_)
294
- return;
295
- pubsub_engine_s *eng = eng_;
296
- channel_s *ch = fio_node2obj(channel_s, channels.dict, ch_);
297
- eng->subscribe(eng, ch->name, ch->len, ch->use_pattern);
377
+ /* runs in lock(!) let'm all know */
378
+ static void pubsub_on_channel_create(channel_s *ch) {
379
+ if (ch->publish2cluster)
380
+ PUBSUB_CLUSTER_ENGINE->subscribe(PUBSUB_CLUSTER_ENGINE, ch->name,
381
+ ch->use_pattern);
382
+ spn_lock(&engn_lock);
383
+ FIO_HASH_FOR_LOOP(&engines, e_) {
384
+ if (!e_ || !e_->obj)
385
+ continue;
386
+ pubsub_engine_s *e = e_->obj;
387
+ e->subscribe(e, ch->name, ch->use_pattern);
388
+ }
389
+ spn_unlock(&engn_lock);
298
390
  }
299
391
 
300
- static void pubsub_engine_resubscribe_task(void *eng_, void *ignored) {
301
- if (!eng_)
302
- return;
303
- pubsub_engine_s *eng = eng_;
304
- channel_s *ch;
305
- spn_lock(&pubsub_GIL);
306
- fio_list_for_each(channel_s, channels.list, ch, pubsub_patterns) {
307
- eng->subscribe(eng, ch->name, ch->len, ch->use_pattern);
392
+ /* runs in lock(!) let'm all know */
393
+ static void pubsub_on_channel_destroy(channel_s *ch) {
394
+ if (ch->publish2cluster)
395
+ PUBSUB_CLUSTER_ENGINE->unsubscribe(PUBSUB_CLUSTER_ENGINE, ch->name,
396
+ ch->use_pattern);
397
+ spn_lock(&engn_lock);
398
+ FIO_HASH_FOR_LOOP(&engines, e_) {
399
+ if (!e_ || !e_->obj)
400
+ continue;
401
+ pubsub_engine_s *e = e_->obj;
402
+ e->unsubscribe(e, ch->name, ch->use_pattern);
308
403
  }
404
+ spn_unlock(&engn_lock);
405
+ }
309
406
 
310
- fio_dict_each(fio_dict_prefix(&pubsub_channels, (void *)&eng, sizeof(void *)),
311
- resubscribe_action, eng);
312
- spn_unlock(&pubsub_GIL);
407
+ /** Registers an engine, so it's callback can be called. */
408
+ void pubsub_engine_register(pubsub_engine_s *engine) {
409
+ spn_lock(&engn_lock);
410
+ fio_hash_insert(
411
+ &engines,
412
+ (fio_hash_key_s){.hash = (uintptr_t)engine, .obj = FIOBJ_INVALID},
413
+ engine);
414
+ spn_unlock(&engn_lock);
415
+ }
313
416
 
314
- (void)ignored;
417
+ /** Unregisters an engine, so it could be safely destroyed. */
418
+ void pubsub_engine_deregister(pubsub_engine_s *engine) {
419
+ spn_lock(&engn_lock);
420
+ if (PUBSUB_DEFAULT_ENGINE == engine)
421
+ PUBSUB_DEFAULT_ENGINE = (pubsub_engine_s *)PUBSUB_CLUSTER_ENGINE;
422
+ void *old = fio_hash_insert(
423
+ &engines,
424
+ (fio_hash_key_s){.hash = (uintptr_t)engine, .obj = FIOBJ_INVALID}, NULL);
425
+ fio_hash_compact(&engines);
426
+ spn_unlock(&engn_lock);
427
+ if (!old)
428
+ fprintf(stderr, "Deregister error, not registered?\n");
315
429
  }
430
+
316
431
  /**
317
- * Engines can ask facil.io to perform an action on all their active channels.
432
+ * Engines can ask facil.io to resubscribe to all active channels.
433
+ *
434
+ * This allows engines that lost their connection to their Pub/Sub service to
435
+ * resubscribe all the currently active channels with the new connection.
436
+ *
437
+ * CAUTION: This is an evented task... try not to free the engine's memory while
438
+ * resubscriptions are under way...
318
439
  */
319
440
  void pubsub_engine_resubscribe(pubsub_engine_s *eng) {
320
- if (!eng)
321
- return;
322
- defer(pubsub_engine_resubscribe_task, eng, NULL);
441
+ spn_lock(&lock);
442
+ FIO_HASH_FOR_LOOP(&channels, i) {
443
+ channel_s *ch = i->obj;
444
+ eng->subscribe(eng, ch->name, 0);
445
+ }
446
+ FIO_HASH_FOR_LOOP(&patterns, i) {
447
+ channel_s *ch = i->obj;
448
+ eng->subscribe(eng, ch->name, 1);
449
+ }
450
+ spn_unlock(&lock);
323
451
  }
324
452
 
325
453
  /* *****************************************************************************
326
- Hashing
454
+ PUBSUB_PROCESS_ENGINE: Single Process Engine and `pubsub_defer`
327
455
  ***************************************************************************** */
328
456
 
329
- #define pubsub_client_hash(p1, p2, p3) \
330
- (((((uint64_t)(p1) * ((uint64_t)p2 ^ 0x736f6d6570736575ULL)) >> 5) | \
331
- (((uint64_t)(p1) * ((uint64_t)p2 ^ 0x736f6d6570736575ULL)) << 59)) ^ \
332
- ((uint64_t)p3 ^ 0x646f72616e646f6dULL))
457
+ typedef struct {
458
+ size_t ref;
459
+ FIOBJ channel;
460
+ FIOBJ msg;
461
+ } msg_wrapper_s;
333
462
 
334
- /* *****************************************************************************
335
- API
336
- ***************************************************************************** */
463
+ typedef struct {
464
+ msg_wrapper_s *wrapper;
465
+ pubsub_message_s msg;
466
+ } msg_container_s;
337
467
 
338
- #undef pubsub_subscribe
339
- pubsub_sub_pt pubsub_subscribe(struct pubsub_subscribe_args args) {
340
- if (!args.on_message)
341
- goto err_unlocked;
342
- if (args.channel.name && !args.channel.len)
343
- args.channel.len = strlen(args.channel.name);
344
- if (args.channel.len > FIO_PUBBSUB_MAX_CHANNEL_LEN) {
345
- goto err_unlocked;
468
+ static void msg_wrapper_free(msg_wrapper_s *m) {
469
+ if (spn_sub(&m->ref, 1))
470
+ return;
471
+ fiobj_free(m->channel);
472
+ fiobj_free(m->msg);
473
+ fio_free(m);
474
+ }
475
+
476
+ /* calls a client's `on_message` callback */
477
+ void pubsub_en_process_deferred_on_message(void *cl_, void *m_) {
478
+ msg_wrapper_s *m = m_;
479
+ client_s *cl = cl_;
480
+ if (spn_trylock(&cl->lock)) {
481
+ defer(pubsub_en_process_deferred_on_message, cl, m);
482
+ return;
346
483
  }
347
- if (!args.engine)
348
- args.engine = PUBSUB_DEFAULT_ENGINE;
349
- channel_s *ch;
350
- client_s *client;
351
- uint64_t cl_hash =
352
- pubsub_client_hash(args.on_message, args.udata1, args.udata2);
353
-
354
- spn_lock(&pubsub_GIL);
355
- if (args.use_pattern) {
356
- fio_list_for_each(channel_s, channels.list, ch, pubsub_patterns) {
357
- if (ch->engine == args.engine && ch->len == args.channel.len &&
358
- !memcmp(ch->name, args.channel.name, args.channel.len))
359
- goto found_channel;
360
- }
361
- } else {
362
- ch = (void *)fio_dict_get(
363
- fio_dict_prefix(&pubsub_channels, (void *)&args.engine, sizeof(void *)),
364
- args.channel.name, args.channel.len);
484
+ msg_container_s arg = {.wrapper = m,
485
+ .msg = {
486
+ .channel = m->channel,
487
+ .message = m->msg,
488
+ .subscription = (pubsub_sub_pt)cl,
489
+ .udata1 = cl->udata1,
490
+ .udata2 = cl->udata2,
491
+ }};
492
+ cl->on_message(&arg.msg);
493
+ spn_unlock(&cl->lock);
494
+ msg_wrapper_free(m);
495
+ client_test4free(cl_);
496
+ }
497
+
498
+ /* Must subscribe channel. Failures are ignored. */
499
+ void pubsub_en_process_subscribe(const pubsub_engine_s *eng, FIOBJ channel,
500
+ uint8_t use_pattern) {
501
+ (void)eng;
502
+ (void)channel;
503
+ (void)use_pattern;
504
+ }
505
+
506
+ /* Must unsubscribe channel. Failures are ignored. */
507
+ void pubsub_en_process_unsubscribe(const pubsub_engine_s *eng, FIOBJ channel,
508
+ uint8_t use_pattern) {
509
+ (void)eng;
510
+ (void)channel;
511
+ (void)use_pattern;
512
+ }
513
+ /** Should return 0 on success and -1 on failure. */
514
+ int pubsub_en_process_publish(const pubsub_engine_s *eng, FIOBJ channel,
515
+ FIOBJ msg) {
516
+ uint64_t channel_hash = fiobj_obj2hash(channel);
517
+ msg_wrapper_s *m = fio_malloc(sizeof(*m));
518
+ int ret = -1;
519
+ if (!m) {
520
+ perror("FATAL ERROR: (pubsub) couldn't allocate message wrapper");
521
+ exit(errno);
522
+ }
523
+ *m = (msg_wrapper_s){
524
+ .ref = 1, .channel = fiobj_dup(channel), .msg = fiobj_dup(msg)};
525
+ spn_lock(&lock);
526
+ {
527
+ /* test for direct match */
528
+ channel_s *ch = fio_hash_find(
529
+ &channels, (fio_hash_key_s){.hash = channel_hash, .obj = channel});
365
530
  if (ch) {
366
- ch = fio_node2obj(channel_s, channels, ch);
367
- goto found_channel;
531
+ ret = 0;
532
+ FIO_LS_EMBD_FOR(&ch->clients, cl_) {
533
+ client_s *cl = FIO_LS_EMBD_OBJ(client_s, node, cl_);
534
+ spn_add(&m->ref, 1);
535
+ spn_add(&cl->ref, 1);
536
+ defer(pubsub_en_process_deferred_on_message, cl, m);
537
+ }
538
+ }
539
+ }
540
+ /* test for pattern match */
541
+ fio_cstr_s ch_str = fiobj_obj2cstr(channel);
542
+ FIO_HASH_FOR_LOOP(&patterns, ch_) {
543
+ channel_s *ch = (channel_s *)ch_->obj;
544
+ fio_cstr_s tmp = fiobj_obj2cstr(ch->name);
545
+ if (pubsub_glob_match(ch_str.bytes, ch_str.len, tmp.bytes, tmp.len)) {
546
+ ret = 0;
547
+ FIO_LS_EMBD_FOR(&ch->clients, cl_) {
548
+ client_s *cl = FIO_LS_EMBD_OBJ(client_s, node, cl_);
549
+ spn_add(&m->ref, 1);
550
+ spn_add(&cl->ref, 1);
551
+ defer(pubsub_en_process_deferred_on_message, cl, m);
552
+ }
368
553
  }
369
554
  }
555
+ spn_unlock(&lock);
556
+ msg_wrapper_free(m);
557
+ return ret;
558
+ (void)eng;
559
+ }
370
560
 
371
- if (args.engine->subscribe(args.engine, args.channel.name, args.channel.len,
372
- args.use_pattern))
373
- goto error;
374
- ch = malloc(sizeof(*ch) + args.channel.len + 1 + sizeof(void *));
375
- *ch = (channel_s){
376
- .clients = FIO_HASH_TABLE_INIT(ch->clients),
377
- .name = (char *)(ch + 1),
378
- .len = args.channel.len,
379
- .use_pattern = args.use_pattern,
380
- .engine = args.engine,
381
- };
382
- memcpy(ch->name, args.channel.name, args.channel.len);
561
+ const pubsub_engine_s PUBSUB_PROCESS_ENGINE_S = {
562
+ .subscribe = pubsub_en_process_subscribe,
563
+ .unsubscribe = pubsub_en_process_unsubscribe,
564
+ .publish = pubsub_en_process_publish,
565
+ };
383
566
 
384
- ch->name[args.channel.len + sizeof(void *)] = 0;
567
+ const pubsub_engine_s *PUBSUB_PROCESS_ENGINE = &PUBSUB_PROCESS_ENGINE_S;
385
568
 
386
- if (args.use_pattern) {
387
- fio_list_add(&pubsub_patterns, &ch->channels.list);
388
- } else {
389
- fio_dict_set(fio_dict_ensure_prefix(&pubsub_channels, (void *)&args.engine,
390
- sizeof(void *)),
391
- args.channel.name, args.channel.len, &ch->channels.dict);
569
+ /**
570
+ * defers message hadling if it can't be performed (i.e., resource is busy) or
571
+ * should be fragmented (allowing large tasks to be broken down).
572
+ *
573
+ * This should only be called from within the `on_message` callback.
574
+ *
575
+ * It's recommended that the `on_message` callback return immediately following
576
+ * this function call, as code might run concurrently.
577
+ *
578
+ * Uses reference counting for zero copy.
579
+ *
580
+ * It's impossible to use a different `on_message` callbck without resorting to
581
+ * memory allocations... so when in need, manage routing withing the
582
+ * `on_message` callback.
583
+ */
584
+ void pubsub_defer(pubsub_message_s *msg) {
585
+ msg_container_s *arg = FIO_LS_EMBD_OBJ(msg_container_s, msg, msg);
586
+ spn_add(&arg->wrapper->ref, 1);
587
+ spn_add(&((client_s *)arg->msg.subscription)->ref, 1);
588
+ defer(pubsub_en_process_deferred_on_message, arg->msg.subscription,
589
+ arg->wrapper);
590
+ }
591
+
592
+ /* *****************************************************************************
593
+ Cluster Engine
594
+ ***************************************************************************** */
595
+
596
+ /* Must subscribe channel. Failures are ignored. */
597
+ void pubsub_en_cluster_subscribe(const pubsub_engine_s *eng, FIOBJ channel,
598
+ uint8_t use_pattern) {
599
+ if (facil_is_running()) {
600
+ facil_cluster_send((use_pattern ? PUBSUB_FACIL_CLUSTER_PATTERN_SUB_FILTER
601
+ : PUBSUB_FACIL_CLUSTER_CHANNEL_SUB_FILTER),
602
+ channel, FIOBJ_INVALID);
392
603
  }
393
- // fprintf(stderr, "Created a new channel for %s\n", args.channel.name);
394
- found_channel:
604
+ (void)eng;
605
+ }
395
606
 
396
- client = (void *)fio_ht_find(&ch->clients, cl_hash);
397
- if (client) {
398
- client = fio_ht_object(client_s, clients, client);
399
- goto found_client;
607
+ /* Must unsubscribe channel. Failures are ignored. */
608
+ void pubsub_en_cluster_unsubscribe(const pubsub_engine_s *eng, FIOBJ channel,
609
+ uint8_t use_pattern) {
610
+ if (facil_is_running()) {
611
+ facil_cluster_send((use_pattern
612
+ ? PUBSUB_FACIL_CLUSTER_PATTERN_UNSUB_FILTER
613
+ : PUBSUB_FACIL_CLUSTER_CHANNEL_UNSUB_FILTER),
614
+ channel, FIOBJ_INVALID);
615
+ }
616
+ (void)eng;
617
+ }
618
+ /** Should return 0 on success and -1 on failure. */
619
+ int pubsub_en_cluster_publish(const pubsub_engine_s *eng, FIOBJ channel,
620
+ FIOBJ msg) {
621
+ if (facil_is_running()) {
622
+ facil_cluster_send(PUBSUB_FACIL_CLUSTER_CHANNEL_FILTER, channel, msg);
400
623
  }
624
+ return PUBSUB_PROCESS_ENGINE->publish(PUBSUB_PROCESS_ENGINE, channel, msg);
625
+ (void)eng;
626
+ }
401
627
 
402
- client = malloc(sizeof(*client));
403
- if (!client)
404
- goto error;
405
- *client = (client_s){
406
- .on_message = args.on_message,
407
- .on_unsubscribe = args.on_unsubscribe,
408
- .udata1 = args.udata1,
409
- .udata2 = args.udata2,
410
- .parent = ch,
411
- .active = 0,
412
- .ref = 0,
413
- };
414
- fio_ht_add(&ch->clients, &client->clients, cl_hash);
628
+ const pubsub_engine_s PUBSUB_CLUSTER_ENGINE_S = {
629
+ .subscribe = pubsub_en_cluster_subscribe,
630
+ .unsubscribe = pubsub_en_cluster_unsubscribe,
631
+ .publish = pubsub_en_cluster_publish,
632
+ };
415
633
 
416
- found_client:
634
+ pubsub_engine_s const *PUBSUB_CLUSTER_ENGINE = &PUBSUB_CLUSTER_ENGINE_S;
635
+ pubsub_engine_s *PUBSUB_DEFAULT_ENGINE =
636
+ (pubsub_engine_s *)&PUBSUB_CLUSTER_ENGINE_S;
637
+ /* *****************************************************************************
638
+ Cluster Initialization and Messaging Protocol
639
+ ***************************************************************************** */
417
640
 
418
- client->ref++;
419
- spn_add(&client->active, 1);
420
- spn_unlock(&pubsub_GIL);
421
- return client;
641
+ /* does nothing */
642
+ static void pubsub_cluster_on_message_noop(pubsub_message_s *msg) { (void)msg; }
422
643
 
423
- error:
424
- spn_unlock(&pubsub_GIL);
425
- err_unlocked:
426
- if (args.on_unsubscribe)
427
- args.on_unsubscribe(args.udata1, args.udata2);
428
- return NULL;
644
+ /* registers to the channel */
645
+ static void pubsub_cluster_subscribe2channel(void *ch, void *flag) {
646
+ channel_s channel = {
647
+ .name = (FIOBJ)ch,
648
+ .clients = FIO_LS_INIT(channel.clients),
649
+ .use_pattern = ((uintptr_t)flag & 1),
650
+ .publish2cluster = 0,
651
+ };
652
+ client_s client = {.on_message = pubsub_cluster_on_message_noop};
653
+ pubsub_client_new(client, channel);
429
654
  }
430
655
 
431
- /**
432
- * This helper searches for an existing subscription.
433
- * Use with care, NEVER call `pubsub_unsubscribe` more times than you have
434
- * called `pubsub_subscribe`, since the subscription handle memory is realesed
435
- * onnce the reference count reaches 0.
436
- *
437
- * Returns a subscription pointer or NULL (none found).
438
- */
439
- #undef pubsub_find_sub
440
- pubsub_sub_pt pubsub_find_sub(struct pubsub_subscribe_args args) {
441
- if (!args.on_message)
442
- return NULL;
443
- if (args.channel.name && !args.channel.len)
444
- args.channel.len = strlen(args.channel.name);
445
- if (args.channel.len > FIO_PUBBSUB_MAX_CHANNEL_LEN) {
446
- return NULL;
447
- }
448
- if (!args.engine)
449
- args.engine = PUBSUB_DEFAULT_ENGINE;
450
- channel_s *ch;
451
- client_s *client;
452
- uint64_t cl_hash =
453
- pubsub_client_hash(args.on_message, args.udata1, args.udata2);
454
-
455
- spn_lock(&pubsub_GIL);
456
- if (args.use_pattern) {
457
- fio_list_for_each(channel_s, channels.list, ch, pubsub_patterns) {
458
- if (ch->engine == args.engine && ch->len == args.channel.len &&
459
- !memcmp(ch->name, args.channel.name, args.channel.len))
460
- goto found_channel;
461
- }
462
- } else {
463
- ch = (void *)fio_dict_get(
464
- fio_dict_prefix(&pubsub_channels, (void *)&args.engine, sizeof(void *)),
465
- args.channel.name, args.channel.len);
466
- if (ch) {
467
- ch = fio_node2obj(channel_s, channels, ch);
468
- goto found_channel;
469
- }
470
- }
471
- goto not_found;
472
-
473
- found_channel:
656
+ /* deregisters from the channel if required */
657
+ static void pubsub_cluster_unsubscribe2channel(void *ch, void *flag) {
658
+ channel_s channel = {
659
+ .name = (FIOBJ)ch,
660
+ .clients = FIO_LS_INIT(channel.clients),
661
+ .use_pattern = ((uintptr_t)flag & 1),
662
+ .publish2cluster = 0,
663
+ };
664
+ client_s client = {.on_message = pubsub_cluster_on_message_noop};
665
+ client_s *sub = pubsub_client_find(client, channel);
666
+ pubsub_client_destroy(sub);
667
+ }
474
668
 
475
- client = (void *)fio_ht_find(&ch->clients, cl_hash);
476
- if (client) {
477
- client = fio_ht_object(client_s, clients, client);
478
- spn_unlock(&pubsub_GIL);
479
- return client;
669
+ static void pubsub_cluster_facil_message(int32_t filter, FIOBJ channel,
670
+ FIOBJ message) {
671
+ // fprintf(stderr, "(%d) pubsub message filter %d (%s)\n", getpid(), filter,
672
+ // fiobj_obj2cstr(channel).name);
673
+ switch (filter) {
674
+ case PUBSUB_FACIL_CLUSTER_CHANNEL_FILTER:
675
+ PUBSUB_PROCESS_ENGINE->publish(PUBSUB_PROCESS_ENGINE, channel, message);
676
+ break;
677
+ case PUBSUB_FACIL_CLUSTER_CHANNEL_SUB_FILTER:
678
+ pubsub_cluster_subscribe2channel((void *)channel, 0);
679
+ break;
680
+ case PUBSUB_FACIL_CLUSTER_PATTERN_SUB_FILTER:
681
+ pubsub_cluster_subscribe2channel((void *)channel, (void *)1);
682
+ break;
683
+ case PUBSUB_FACIL_CLUSTER_CHANNEL_UNSUB_FILTER:
684
+ pubsub_cluster_unsubscribe2channel((void *)channel, 0);
685
+ break;
686
+ case PUBSUB_FACIL_CLUSTER_PATTERN_UNSUB_FILTER:
687
+ pubsub_cluster_unsubscribe2channel((void *)channel, (void *)1);
688
+ break;
480
689
  }
690
+ (void)filter;
691
+ }
481
692
 
482
- not_found:
483
- spn_unlock(&pubsub_GIL);
484
- return NULL;
693
+ void pubsub_cluster_init(void) {
694
+ facil_cluster_set_handler(PUBSUB_FACIL_CLUSTER_CHANNEL_FILTER,
695
+ pubsub_cluster_facil_message);
696
+ facil_cluster_set_handler(PUBSUB_FACIL_CLUSTER_CHANNEL_SUB_FILTER,
697
+ pubsub_cluster_facil_message);
698
+ facil_cluster_set_handler(PUBSUB_FACIL_CLUSTER_PATTERN_SUB_FILTER,
699
+ pubsub_cluster_facil_message);
700
+ facil_cluster_set_handler(PUBSUB_FACIL_CLUSTER_CHANNEL_UNSUB_FILTER,
701
+ pubsub_cluster_facil_message);
702
+ facil_cluster_set_handler(PUBSUB_FACIL_CLUSTER_PATTERN_UNSUB_FILTER,
703
+ pubsub_cluster_facil_message);
485
704
  }
486
705
 
487
- void pubsub_unsubscribe(pubsub_sub_pt client) {
488
- if (!client)
489
- return;
490
- spn_lock(&pubsub_GIL);
491
- client->ref--;
492
- if (client->ref) {
493
- spn_unlock(&pubsub_GIL);
494
- spn_sub(&client->active, 1);
495
- return;
496
- }
497
- fio_ht_remove(&client->clients);
498
- if (client->parent->clients.count) {
499
- spn_unlock(&pubsub_GIL);
500
- defer(pubsub_free_client, client, NULL);
501
- return;
706
+ void pubsub_cluster_on_fork_start(void) {
707
+ lock = SPN_LOCK_INIT;
708
+ FIO_HASH_FOR_LOOP(&clients, pos) {
709
+ if (pos->obj) {
710
+ client_s *c = pos->obj;
711
+ c->lock = SPN_LOCK_INIT;
712
+ }
502
713
  }
503
- channel_s *ch = client->parent;
504
- if (ch->use_pattern) {
505
- fio_list_remove(&ch->channels.list);
506
- } else {
507
- fio_dict_remove(&ch->channels.dict);
714
+ }
715
+
716
+ void pubsub_cluster_on_fork_end(void) {
717
+ lock = SPN_LOCK_INIT;
718
+ FIO_HASH_FOR_LOOP(&engines, pos) {
719
+ if (pos->obj) {
720
+ pubsub_engine_s *e = pos->obj;
721
+ if (e->on_startup)
722
+ e->on_startup(e);
723
+ }
508
724
  }
509
- spn_unlock(&pubsub_GIL);
510
- defer(pubsub_free_client, client, NULL);
511
- ch->engine->unsubscribe(ch->engine, ch->name, ch->len, ch->use_pattern);
512
- fio_ht_rehash(&ch->clients, 0);
513
- free(ch);
514
725
  }
515
726
 
516
- #undef pubsub_publish
517
- int pubsub_publish(struct pubsub_publish_args args) {
518
- if (!args.msg.data)
519
- return -1;
520
- if (!args.msg.len)
521
- args.msg.len = strlen(args.msg.data);
522
- if (args.channel.name && !args.channel.len)
523
- args.channel.len = strlen(args.channel.name);
524
- if (!args.engine) {
525
- args.engine = PUBSUB_DEFAULT_ENGINE;
526
- args.push2cluster = 1;
527
- } else if (args.push2cluster)
528
- PUBSUB_CLUSTER_ENGINE_S.publish(args.engine, args.channel.name,
529
- args.channel.len, args.msg.data,
530
- args.msg.len, args.use_pattern);
531
- return args.engine->publish(args.engine, args.channel.name, args.channel.len,
532
- args.msg.data, args.msg.len, args.use_pattern);
727
+ void pubsub_cluster_cleanup(void) {
728
+ while (clients.count) {
729
+ pubsub_client_destroy(fio_hash_last(&clients, NULL));
730
+ }
731
+ FIO_HASH_FOR_FREE(&clients, pos) {}
732
+ fio_hash_free(&engines);
733
+ fio_hash_free(&channels);
734
+ fio_hash_free(&patterns);
735
+ clients = (fio_hash_s)FIO_HASH_INIT;
736
+ engines = (fio_hash_s)FIO_HASH_INIT;
737
+ channels = (fio_hash_s)FIO_HASH_INIT;
738
+ patterns = (fio_hash_s)FIO_HASH_INIT;
739
+ lock = SPN_LOCK_INIT;
533
740
  }
534
741
 
535
- /**
536
- * defers message hadling if it can't be performed (i.e., resource is busy) or
537
- * should be fragmented (allowing large tasks to be broken down).
538
- */
539
- void pubsub_defer(pubsub_message_s *msg_) {
540
- if (!msg_)
541
- return;
542
- msg_container_s *msg = fio_node2obj(msg_container_s, msg, msg_);
543
- spn_add(&msg->origin->ref, 1);
544
- spn_add(&msg->msg.subscription->active, 1);
545
- defer(pubsub_deliver_msg, msg->msg.subscription, msg->origin);
742
+ /* *****************************************************************************
743
+ Glob Matching Helper
744
+ ***************************************************************************** */
745
+
746
+ /** A binary glob matching helper. Returns 1 on match, otherwise returns 0. */
747
+ static int pubsub_glob_match(uint8_t *data, size_t data_len, uint8_t *pattern,
748
+ size_t pat_len) {
749
+ /* adapted and rewritten, with thankfulness, from the code at:
750
+ * https://github.com/opnfv/kvmfornfv/blob/master/kernel/lib/glob.c
751
+ *
752
+ * Original version's copyright:
753
+ * Copyright 2015 Open Platform for NFV Project, Inc. and its contributors
754
+ * Under the MIT license.
755
+ */
756
+
757
+ /*
758
+ * Backtrack to previous * on mismatch and retry starting one
759
+ * character later in the string. Because * matches all characters
760
+ * (no exception for /), it can be easily proved that there's
761
+ * never a need to backtrack multiple levels.
762
+ */
763
+ uint8_t *back_pat = NULL, *back_str = data;
764
+ size_t back_pat_len = 0, back_str_len = data_len;
765
+
766
+ /*
767
+ * Loop over each token (character or class) in pat, matching
768
+ * it against the remaining unmatched tail of str. Return false
769
+ * on mismatch, or true after matching the trailing nul bytes.
770
+ */
771
+ while (data_len) {
772
+ uint8_t c = *data++;
773
+ uint8_t d = *pattern++;
774
+ data_len--;
775
+ pat_len--;
776
+
777
+ switch (d) {
778
+ case '?': /* Wildcard: anything goes */
779
+ break;
780
+
781
+ case '*': /* Any-length wildcard */
782
+ if (!pat_len) /* Optimize trailing * case */
783
+ return 1;
784
+ back_pat = pattern;
785
+ back_pat_len = pat_len;
786
+ back_str = --data; /* Allow zero-length match */
787
+ back_str_len = ++data_len;
788
+ break;
789
+
790
+ case '[': { /* Character class */
791
+ uint8_t match = 0, inverted = (*pattern == '^');
792
+ uint8_t *cls = pattern + inverted;
793
+ uint8_t a = *cls++;
794
+
795
+ /*
796
+ * Iterate over each span in the character class.
797
+ * A span is either a single character a, or a
798
+ * range a-b. The first span may begin with ']'.
799
+ */
800
+ do {
801
+ uint8_t b = a;
802
+
803
+ if (cls[0] == '-' && cls[1] != ']') {
804
+ b = cls[1];
805
+
806
+ cls += 2;
807
+ if (a > b) {
808
+ uint8_t tmp = a;
809
+ a = b;
810
+ b = tmp;
811
+ }
812
+ }
813
+ match |= (a <= c && c <= b);
814
+ } while ((a = *cls++) != ']');
815
+
816
+ if (match == inverted)
817
+ goto backtrack;
818
+ pat_len -= cls - pattern;
819
+ pattern = cls;
820
+
821
+ } break;
822
+ case '\\':
823
+ d = *pattern++;
824
+ pat_len--;
825
+ /*FALLTHROUGH*/
826
+ default: /* Literal character */
827
+ if (c == d)
828
+ break;
829
+ backtrack:
830
+ if (!back_pat)
831
+ return 0; /* No point continuing */
832
+ /* Try again from last *, one character later in str. */
833
+ pattern = back_pat;
834
+ data = ++back_str;
835
+ data_len = --back_str_len;
836
+ pat_len = back_pat_len;
837
+ }
838
+ }
839
+ return !data_len && !pat_len;
546
840
  }