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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +22 -0
- data/LIMITS.md +19 -9
- data/README.md +92 -77
- data/SPEC-PubSub-Draft.md +113 -0
- data/SPEC-Websocket-Draft.md +127 -143
- data/bin/http-hello +0 -1
- data/bin/raw-rbhttp +1 -1
- data/bin/raw_broadcast +8 -10
- data/bin/updated api +2 -2
- data/bin/ws-broadcast +2 -4
- data/bin/ws-echo +2 -2
- data/examples/config.ru +13 -13
- data/examples/echo.ru +5 -6
- data/examples/hello.ru +2 -3
- data/examples/info.md +316 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/redis.ru +9 -9
- data/examples/shootout.ru +45 -11
- data/ext/iodine/defer.c +194 -297
- data/ext/iodine/defer.h +61 -53
- data/ext/iodine/evio.c +0 -260
- data/ext/iodine/evio.h +50 -22
- data/ext/iodine/evio_callbacks.c +26 -0
- data/ext/iodine/evio_epoll.c +251 -0
- data/ext/iodine/evio_kqueue.c +193 -0
- data/ext/iodine/extconf.rb +1 -1
- data/ext/iodine/facil.c +1420 -542
- data/ext/iodine/facil.h +151 -64
- data/ext/iodine/fio_ary.h +418 -0
- data/ext/iodine/{base64.c → fio_base64.c} +33 -24
- data/ext/iodine/{base64.h → fio_base64.h} +6 -7
- data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
- data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
- data/ext/iodine/fio_hashmap.h +759 -0
- data/ext/iodine/fio_json_parser.h +651 -0
- data/ext/iodine/fio_llist.h +257 -0
- data/ext/iodine/fio_mem.c +672 -0
- data/ext/iodine/fio_mem.h +140 -0
- data/ext/iodine/fio_random.c +248 -0
- data/ext/iodine/{random.h → fio_random.h} +11 -14
- data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
- data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
- data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
- data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
- data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
- data/ext/iodine/fio_siphash.h +18 -0
- data/ext/iodine/fio_tmpfile.h +38 -0
- data/ext/iodine/fiobj.h +24 -7
- data/ext/iodine/fiobj4sock.h +23 -0
- data/ext/iodine/fiobj_ary.c +143 -226
- data/ext/iodine/fiobj_ary.h +17 -16
- data/ext/iodine/fiobj_data.c +1160 -0
- data/ext/iodine/fiobj_data.h +164 -0
- data/ext/iodine/fiobj_hash.c +298 -406
- data/ext/iodine/fiobj_hash.h +101 -54
- data/ext/iodine/fiobj_json.c +478 -601
- data/ext/iodine/fiobj_json.h +34 -9
- data/ext/iodine/fiobj_numbers.c +383 -51
- data/ext/iodine/fiobj_numbers.h +87 -11
- data/ext/iodine/fiobj_str.c +423 -184
- data/ext/iodine/fiobj_str.h +81 -32
- data/ext/iodine/fiobject.c +273 -522
- data/ext/iodine/fiobject.h +477 -112
- data/ext/iodine/http.c +2243 -83
- data/ext/iodine/http.h +842 -121
- data/ext/iodine/http1.c +810 -385
- data/ext/iodine/http1.h +16 -39
- data/ext/iodine/http1_parser.c +146 -74
- data/ext/iodine/http1_parser.h +15 -4
- data/ext/iodine/http_internal.c +1258 -0
- data/ext/iodine/http_internal.h +226 -0
- data/ext/iodine/http_mime_parser.h +341 -0
- data/ext/iodine/iodine.c +86 -68
- data/ext/iodine/iodine.h +26 -11
- data/ext/iodine/iodine_helpers.c +8 -7
- data/ext/iodine/iodine_http.c +487 -324
- data/ext/iodine/iodine_json.c +304 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_protocol.c +107 -45
- data/ext/iodine/iodine_pubsub.c +526 -225
- data/ext/iodine/iodine_pubsub.h +10 -0
- data/ext/iodine/iodine_websockets.c +268 -510
- data/ext/iodine/iodine_websockets.h +2 -4
- data/ext/iodine/pubsub.c +726 -432
- data/ext/iodine/pubsub.h +85 -103
- data/ext/iodine/rb-call.c +4 -4
- data/ext/iodine/rb-defer.c +46 -22
- data/ext/iodine/rb-fiobj2rb.h +117 -0
- data/ext/iodine/rb-rack-io.c +73 -238
- data/ext/iodine/rb-rack-io.h +2 -2
- data/ext/iodine/rb-registry.c +35 -93
- data/ext/iodine/rb-registry.h +1 -0
- data/ext/iodine/redis_engine.c +742 -304
- data/ext/iodine/redis_engine.h +42 -39
- data/ext/iodine/resp_parser.h +311 -0
- data/ext/iodine/sock.c +627 -490
- data/ext/iodine/sock.h +345 -297
- data/ext/iodine/spnlock.inc +15 -4
- data/ext/iodine/websocket_parser.h +16 -20
- data/ext/iodine/websockets.c +188 -257
- data/ext/iodine/websockets.h +24 -133
- data/lib/iodine.rb +52 -7
- data/lib/iodine/cli.rb +6 -24
- data/lib/iodine/json.rb +40 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/iodine/websocket.rb +5 -3
- data/lib/rack/handler/iodine.rb +58 -13
- metadata +38 -48
- data/bin/ws-shootout +0 -107
- data/examples/broadcast.ru +0 -56
- data/ext/iodine/bscrypt-common.h +0 -116
- data/ext/iodine/bscrypt.h +0 -49
- data/ext/iodine/fio2resp.c +0 -60
- data/ext/iodine/fio2resp.h +0 -51
- data/ext/iodine/fio_dict.c +0 -446
- data/ext/iodine/fio_dict.h +0 -99
- data/ext/iodine/fio_hash_table.h +0 -370
- data/ext/iodine/fio_list.h +0 -111
- data/ext/iodine/fiobj_internal.h +0 -280
- data/ext/iodine/fiobj_primitives.c +0 -131
- data/ext/iodine/fiobj_primitives.h +0 -55
- data/ext/iodine/fiobj_sym.c +0 -135
- data/ext/iodine/fiobj_sym.h +0 -60
- data/ext/iodine/hex.c +0 -124
- data/ext/iodine/hex.h +0 -70
- data/ext/iodine/http1_request.c +0 -81
- data/ext/iodine/http1_request.h +0 -58
- data/ext/iodine/http1_response.c +0 -417
- data/ext/iodine/http1_response.h +0 -95
- data/ext/iodine/http_request.c +0 -111
- data/ext/iodine/http_request.h +0 -102
- data/ext/iodine/http_response.c +0 -1703
- data/ext/iodine/http_response.h +0 -250
- data/ext/iodine/misc.c +0 -182
- data/ext/iodine/misc.h +0 -74
- data/ext/iodine/random.c +0 -208
- data/ext/iodine/redis_connection.c +0 -278
- data/ext/iodine/redis_connection.h +0 -86
- data/ext/iodine/resp.c +0 -842
- data/ext/iodine/resp.h +0 -261
- data/ext/iodine/siphash.c +0 -154
- data/ext/iodine/siphash.h +0 -22
- data/ext/iodine/xor-crypt.c +0 -193
- 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
|
16
|
-
|
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 "
|
11
|
-
#include "
|
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
|
-
|
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
|
-
|
27
|
-
|
28
|
-
}
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
45
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
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
|
-
|
117
|
+
Channel and Client Management
|
67
118
|
***************************************************************************** */
|
68
119
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
(
|
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
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
220
|
-
pubsub_cluster_handle_publishing);
|
221
|
-
}
|
288
|
+
/* *****************************************************************************
|
289
|
+
Subscription API
|
290
|
+
***************************************************************************** */
|
222
291
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
.
|
268
|
-
.
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
374
|
+
Engine handling and Management
|
276
375
|
***************************************************************************** */
|
277
376
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
spn_lock(&
|
306
|
-
|
307
|
-
|
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
|
-
|
311
|
-
|
312
|
-
|
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
|
-
|
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
|
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
|
-
|
321
|
-
|
322
|
-
|
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
|
-
|
454
|
+
PUBSUB_PROCESS_ENGINE: Single Process Engine and `pubsub_defer`
|
327
455
|
***************************************************************************** */
|
328
456
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
457
|
+
typedef struct {
|
458
|
+
size_t ref;
|
459
|
+
FIOBJ channel;
|
460
|
+
FIOBJ msg;
|
461
|
+
} msg_wrapper_s;
|
333
462
|
|
334
|
-
|
335
|
-
|
336
|
-
|
463
|
+
typedef struct {
|
464
|
+
msg_wrapper_s *wrapper;
|
465
|
+
pubsub_message_s msg;
|
466
|
+
} msg_container_s;
|
337
467
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
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
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
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
|
-
|
367
|
-
|
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
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
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
|
-
|
567
|
+
const pubsub_engine_s *PUBSUB_PROCESS_ENGINE = &PUBSUB_PROCESS_ENGINE_S;
|
385
568
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
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
|
-
|
394
|
-
|
604
|
+
(void)eng;
|
605
|
+
}
|
395
606
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
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
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
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
|
-
|
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
|
-
|
419
|
-
|
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
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
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
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
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
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
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
|
-
|
483
|
-
|
484
|
-
|
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
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
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
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
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
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
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
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
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
|
}
|