iodine 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/LIMITS.md +25 -0
- data/README.md +39 -80
- data/SPEC-Websocket-Draft.md +129 -4
- data/bin/echo +2 -2
- data/bin/http-hello +1 -0
- data/bin/updated api +113 -0
- data/bin/ws-echo +0 -1
- data/examples/broadcast.ru +56 -0
- data/examples/echo.ru +57 -0
- data/examples/hello.ru +30 -0
- data/examples/redis.ru +69 -0
- data/examples/shootout.ru +53 -0
- data/exe/iodine +2 -80
- data/ext/iodine/defer.c +11 -5
- data/ext/iodine/empty.h +26 -0
- data/ext/iodine/evio.h +1 -1
- data/ext/iodine/facil.c +103 -61
- data/ext/iodine/facil.h +20 -12
- data/ext/iodine/fio_dict.c +446 -0
- data/ext/iodine/fio_dict.h +90 -0
- data/ext/iodine/fio_hash_table.h +370 -0
- data/ext/iodine/fio_list.h +30 -3
- data/ext/iodine/http.c +169 -37
- data/ext/iodine/http.h +33 -10
- data/ext/iodine/http1.c +78 -42
- data/ext/iodine/http_request.c +6 -0
- data/ext/iodine/http_request.h +3 -0
- data/ext/iodine/http_response.c +43 -11
- data/ext/iodine/iodine.c +380 -0
- data/ext/iodine/iodine.h +62 -0
- data/ext/iodine/iodine_helpers.c +235 -0
- data/ext/iodine/iodine_helpers.h +13 -0
- data/ext/iodine/iodine_http.c +409 -241
- data/ext/iodine/iodine_http.h +7 -14
- data/ext/iodine/iodine_protocol.c +626 -0
- data/ext/iodine/iodine_protocol.h +13 -0
- data/ext/iodine/iodine_pubsub.c +646 -0
- data/ext/iodine/iodine_pubsub.h +27 -0
- data/ext/iodine/iodine_websockets.c +796 -0
- data/ext/iodine/iodine_websockets.h +19 -0
- data/ext/iodine/pubsub.c +544 -0
- data/ext/iodine/pubsub.h +215 -0
- data/ext/iodine/random.c +4 -4
- data/ext/iodine/rb-call.c +1 -5
- data/ext/iodine/rb-defer.c +3 -20
- data/ext/iodine/rb-rack-io.c +22 -22
- data/ext/iodine/rb-rack-io.h +3 -4
- data/ext/iodine/rb-registry.c +111 -118
- data/ext/iodine/redis_connection.c +277 -0
- data/ext/iodine/redis_connection.h +77 -0
- data/ext/iodine/redis_engine.c +398 -0
- data/ext/iodine/redis_engine.h +68 -0
- data/ext/iodine/resp.c +842 -0
- data/ext/iodine/resp.h +253 -0
- data/ext/iodine/sock.c +26 -12
- data/ext/iodine/sock.h +14 -3
- data/ext/iodine/spnlock.inc +19 -2
- data/ext/iodine/websockets.c +299 -11
- data/ext/iodine/websockets.h +159 -6
- data/lib/iodine.rb +104 -1
- data/lib/iodine/cli.rb +106 -0
- data/lib/iodine/monkeypatch.rb +40 -0
- data/lib/iodine/pubsub.rb +70 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/iodine/websocket.rb +12 -0
- data/lib/rack/handler/iodine.rb +33 -7
- metadata +35 -7
- data/ext/iodine/iodine_core.c +0 -760
- data/ext/iodine/iodine_core.h +0 -79
- data/ext/iodine/iodine_websocket.c +0 -551
- data/ext/iodine/iodine_websocket.h +0 -22
- data/lib/iodine/http.rb +0 -4
@@ -0,0 +1,13 @@
|
|
1
|
+
#ifndef H_IODINE_PROTOCOL_H
|
2
|
+
#define H_IODINE_PROTOCOL_H
|
3
|
+
/*
|
4
|
+
Copyright: Boaz segev, 2016-2017
|
5
|
+
License: MIT
|
6
|
+
|
7
|
+
Feel free to copy, use and enjoy according to the license provided.
|
8
|
+
*/
|
9
|
+
#include "iodine.h"
|
10
|
+
|
11
|
+
void Iodine_init_protocol(void);
|
12
|
+
|
13
|
+
#endif
|
@@ -0,0 +1,646 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz segev, 2016-2017
|
3
|
+
License: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#include "iodine_pubsub.h"
|
8
|
+
#include "rb-call.h"
|
9
|
+
|
10
|
+
#include "pubsub.h"
|
11
|
+
#include "redis_engine.h"
|
12
|
+
|
13
|
+
VALUE IodineEngine;
|
14
|
+
|
15
|
+
static VALUE IodinePubSub;
|
16
|
+
static ID engine_varid;
|
17
|
+
static ID engine_subid;
|
18
|
+
static ID engine_pubid;
|
19
|
+
static ID engine_unsubid;
|
20
|
+
static ID default_pubsubid;
|
21
|
+
|
22
|
+
static VALUE channel_var_id;
|
23
|
+
static VALUE pattern_var_id;
|
24
|
+
static VALUE message_var_id;
|
25
|
+
|
26
|
+
/* *****************************************************************************
|
27
|
+
Mock Functions
|
28
|
+
***************************************************************************** */
|
29
|
+
|
30
|
+
/**
|
31
|
+
Override this method to handle (un)subscription requests.
|
32
|
+
|
33
|
+
This function will be called by Iodine during pub/sub (un)subscription. Don't
|
34
|
+
call this function from your own code / application.
|
35
|
+
|
36
|
+
The function should return `true` on success and `nil` or `false` on failure.
|
37
|
+
*/
|
38
|
+
static VALUE engine_sub_placeholder(VALUE self, VALUE channel,
|
39
|
+
VALUE use_pattern) {
|
40
|
+
return Qnil;
|
41
|
+
(void)self;
|
42
|
+
(void)channel;
|
43
|
+
(void)use_pattern;
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
Override this method to handle message publishing to the underlying engine (i.e.
|
48
|
+
from Ruby to Redis or from Ruby to MongoDB).
|
49
|
+
|
50
|
+
This function will be called by Iodine during pub/sub publication. Don't
|
51
|
+
call this function from your own code / application.
|
52
|
+
|
53
|
+
The function should return `true` on success and `nil` or `false` on failure.
|
54
|
+
*/
|
55
|
+
static VALUE engine_pub_placeholder(VALUE self, VALUE channel, VALUE msg,
|
56
|
+
VALUE use_pattern) {
|
57
|
+
return Qnil;
|
58
|
+
(void)self;
|
59
|
+
(void)msg;
|
60
|
+
(void)channel;
|
61
|
+
(void)use_pattern;
|
62
|
+
}
|
63
|
+
|
64
|
+
/* *****************************************************************************
|
65
|
+
Ruby API
|
66
|
+
***************************************************************************** */
|
67
|
+
|
68
|
+
/** @!visibility public
|
69
|
+
Called by the engine to distribute a `message` to a `channel`. Supports
|
70
|
+
`pattern` channel matching as well.
|
71
|
+
|
72
|
+
i.e.
|
73
|
+
|
74
|
+
# Regular message distribution
|
75
|
+
self.distribute "My Channel", "Hello!"
|
76
|
+
# Pattern message distribution
|
77
|
+
self.distribute "My Ch*", "Hello!", true
|
78
|
+
|
79
|
+
Returns `self`, always.
|
80
|
+
|
81
|
+
This is the ONLY method inherited from {Iodine::PubSub::Engine} that
|
82
|
+
should be called from within your code (by the engine itself).
|
83
|
+
|
84
|
+
**Notice:**
|
85
|
+
|
86
|
+
Message distribution requires both the {Iodine::PubSub::Engine} instance and the
|
87
|
+
channel to be the same.
|
88
|
+
|
89
|
+
If a client subscribed to "channel 1" on engine A, they will NOT receive
|
90
|
+
messages from "channel 1" on engine B.
|
91
|
+
*/
|
92
|
+
static VALUE engine_distribute(int argc, VALUE *argv, VALUE self) {
|
93
|
+
if (argc < 2 || argc > 3)
|
94
|
+
rb_raise(rb_eArgError,
|
95
|
+
"wrong number of arguments (given %d, expected 2..3).", argc);
|
96
|
+
VALUE channel = argv[0];
|
97
|
+
VALUE msg = argv[1];
|
98
|
+
VALUE pattern = argc >= 3 ? argv[2] : Qnil;
|
99
|
+
Check_Type(channel, T_STRING);
|
100
|
+
Check_Type(msg, T_STRING);
|
101
|
+
|
102
|
+
iodine_engine_s *engine;
|
103
|
+
Data_Get_Struct(self, iodine_engine_s, engine);
|
104
|
+
|
105
|
+
pubsub_engine_distribute(.engine = engine->p,
|
106
|
+
.channel.name = RSTRING_PTR(channel),
|
107
|
+
.channel.len = RSTRING_LEN(channel),
|
108
|
+
.msg.data = RSTRING_PTR(msg),
|
109
|
+
.msg.len = RSTRING_LEN(msg),
|
110
|
+
.use_pattern =
|
111
|
+
(pattern != Qnil && pattern != Qfalse));
|
112
|
+
return self;
|
113
|
+
}
|
114
|
+
|
115
|
+
pubsub_engine_s *iodine_engine_ruby2facil(VALUE ruby_engine) {
|
116
|
+
if (ruby_engine == Qnil || ruby_engine == Qfalse)
|
117
|
+
return NULL;
|
118
|
+
iodine_engine_s *engine;
|
119
|
+
Data_Get_Struct(ruby_engine, iodine_engine_s, engine);
|
120
|
+
if (engine)
|
121
|
+
return engine->p;
|
122
|
+
return NULL;
|
123
|
+
}
|
124
|
+
|
125
|
+
/* *****************************************************************************
|
126
|
+
C => Ruby Bridge
|
127
|
+
***************************************************************************** */
|
128
|
+
|
129
|
+
struct engine_gvl_args_s {
|
130
|
+
const pubsub_engine_s *eng;
|
131
|
+
const char *ch;
|
132
|
+
size_t ch_len;
|
133
|
+
const char *msg;
|
134
|
+
size_t msg_len;
|
135
|
+
uint8_t use_pattern;
|
136
|
+
};
|
137
|
+
|
138
|
+
static void *engine_subscribe_inGVL(void *a_) {
|
139
|
+
struct engine_gvl_args_s *args = a_;
|
140
|
+
VALUE data[2];
|
141
|
+
data[0] = rb_str_new(args->ch, args->ch_len);
|
142
|
+
data[1] = args->use_pattern ? Qtrue : Qnil;
|
143
|
+
VALUE eng = ((iodine_engine_s *)args->eng)->handler;
|
144
|
+
eng = RubyCaller.call2(eng, engine_subid, 2, data);
|
145
|
+
return ((eng == Qfalse || eng == Qnil) ? (void *)-1 : (void *)0);
|
146
|
+
}
|
147
|
+
|
148
|
+
/* Should return 0 on success and -1 on failure. */
|
149
|
+
static int engine_subscribe(const pubsub_engine_s *eng, const char *ch,
|
150
|
+
size_t ch_len, uint8_t use_pattern) {
|
151
|
+
struct engine_gvl_args_s args = {
|
152
|
+
.eng = eng, .ch = ch, .ch_len = ch_len, .use_pattern = use_pattern,
|
153
|
+
};
|
154
|
+
return RubyCaller.call_c(engine_subscribe_inGVL, &args) ? 0 : -1;
|
155
|
+
}
|
156
|
+
|
157
|
+
static void *engine_unsubscribe_inGVL(void *a_) {
|
158
|
+
struct engine_gvl_args_s *args = a_;
|
159
|
+
VALUE data[2];
|
160
|
+
data[0] = rb_str_new(args->ch, args->ch_len);
|
161
|
+
data[1] = args->use_pattern ? Qtrue : Qnil;
|
162
|
+
VALUE eng = ((iodine_engine_s *)args->eng)->handler;
|
163
|
+
RubyCaller.call2(eng, engine_unsubid, 2, data);
|
164
|
+
return NULL;
|
165
|
+
}
|
166
|
+
|
167
|
+
/* Return value is ignored - nothing should be returned. */
|
168
|
+
static void engine_unsubscribe(const pubsub_engine_s *eng, const char *ch,
|
169
|
+
size_t ch_len, uint8_t use_pattern) {
|
170
|
+
struct engine_gvl_args_s args = {
|
171
|
+
.eng = eng, .ch = ch, .ch_len = ch_len, .use_pattern = use_pattern,
|
172
|
+
};
|
173
|
+
RubyCaller.call_c(engine_unsubscribe_inGVL, &args);
|
174
|
+
}
|
175
|
+
|
176
|
+
static void *engine_publish_inGVL(void *a_) {
|
177
|
+
struct engine_gvl_args_s *args = a_;
|
178
|
+
VALUE data[3];
|
179
|
+
data[0] = rb_str_new(args->ch, args->ch_len);
|
180
|
+
data[1] = rb_str_new(args->msg, args->msg_len);
|
181
|
+
data[2] = args->use_pattern ? Qtrue : Qnil;
|
182
|
+
VALUE eng = ((iodine_engine_s *)args->eng)->handler;
|
183
|
+
eng = RubyCaller.call2(eng, engine_pubid, 3, data);
|
184
|
+
return ((eng == Qfalse || eng == Qnil) ? (void *)-1 : 0);
|
185
|
+
}
|
186
|
+
|
187
|
+
/* Should return 0 on success and -1 on failure. */
|
188
|
+
static int engine_publish(const pubsub_engine_s *eng, const char *ch,
|
189
|
+
size_t ch_len, const char *msg, size_t msg_len,
|
190
|
+
uint8_t use_pattern) {
|
191
|
+
struct engine_gvl_args_s args = {
|
192
|
+
.eng = eng,
|
193
|
+
.ch = ch,
|
194
|
+
.ch_len = ch_len,
|
195
|
+
.msg = msg,
|
196
|
+
.msg_len = msg_len,
|
197
|
+
.use_pattern = use_pattern,
|
198
|
+
};
|
199
|
+
return RubyCaller.call_c(engine_publish_inGVL, &args) ? 0 : -1;
|
200
|
+
}
|
201
|
+
|
202
|
+
/* *****************************************************************************
|
203
|
+
C <=> Ruby Data allocation
|
204
|
+
***************************************************************************** */
|
205
|
+
|
206
|
+
/* a callback for the GC (marking active objects) */
|
207
|
+
static void engine_mark(void *eng_) {
|
208
|
+
iodine_engine_s *eng = eng_;
|
209
|
+
rb_gc_mark(eng->handler);
|
210
|
+
}
|
211
|
+
/* a callback for the GC (marking active objects) */
|
212
|
+
static void engine_free(void *eng_) {
|
213
|
+
iodine_engine_s *eng = eng_;
|
214
|
+
if (eng->dealloc)
|
215
|
+
eng->dealloc(eng->p);
|
216
|
+
free(eng);
|
217
|
+
}
|
218
|
+
|
219
|
+
/* GMP::Integer.allocate */
|
220
|
+
static VALUE engine_alloc_c(VALUE self) {
|
221
|
+
iodine_engine_s *eng = malloc(sizeof(*eng));
|
222
|
+
if (TYPE(self) == T_CLASS)
|
223
|
+
*eng = (iodine_engine_s){
|
224
|
+
.handler = self,
|
225
|
+
.engine =
|
226
|
+
{
|
227
|
+
.subscribe = engine_subscribe,
|
228
|
+
.unsubscribe = engine_unsubscribe,
|
229
|
+
.publish = engine_publish,
|
230
|
+
},
|
231
|
+
.p = &eng->engine,
|
232
|
+
};
|
233
|
+
|
234
|
+
return Data_Wrap_Struct(self, engine_mark, engine_free, eng);
|
235
|
+
}
|
236
|
+
|
237
|
+
static VALUE engine_initialize(VALUE self) {
|
238
|
+
iodine_engine_s *engine;
|
239
|
+
Data_Get_Struct(self, iodine_engine_s, engine);
|
240
|
+
engine->handler = self;
|
241
|
+
return self;
|
242
|
+
}
|
243
|
+
|
244
|
+
/* *****************************************************************************
|
245
|
+
Redis
|
246
|
+
***************************************************************************** */
|
247
|
+
|
248
|
+
struct redis_callback_data {
|
249
|
+
resp_object_s *msg;
|
250
|
+
VALUE block;
|
251
|
+
};
|
252
|
+
|
253
|
+
/*
|
254
|
+
populate
|
255
|
+
*/
|
256
|
+
int populate_redis_callback_reply(resp_parser_pt p, resp_object_s *o,
|
257
|
+
void *rep) {
|
258
|
+
switch (o->type) {
|
259
|
+
case RESP_ARRAY:
|
260
|
+
case RESP_PUBSUB:
|
261
|
+
break;
|
262
|
+
case RESP_NULL:
|
263
|
+
rb_ary_push((VALUE)rep, Qnil);
|
264
|
+
break;
|
265
|
+
case RESP_NUMBER:
|
266
|
+
rb_ary_push((VALUE)rep, LONG2FIX(resp_obj2num(o)->number));
|
267
|
+
break;
|
268
|
+
case RESP_ERR:
|
269
|
+
case RESP_STRING:
|
270
|
+
rb_ary_push((VALUE)rep, rb_str_new((char *)resp_obj2str(o)->string,
|
271
|
+
resp_obj2str(o)->len));
|
272
|
+
break;
|
273
|
+
case RESP_OK:
|
274
|
+
rb_ary_push((VALUE)rep, rb_str_new("OK", 2));
|
275
|
+
break;
|
276
|
+
}
|
277
|
+
return 0;
|
278
|
+
(void)p;
|
279
|
+
}
|
280
|
+
/*
|
281
|
+
Perform a Redis message callback in the GVL
|
282
|
+
*/
|
283
|
+
static void *perform_redis_callback_inGVL(void *data) {
|
284
|
+
struct redis_callback_data *a = data;
|
285
|
+
VALUE reply = rb_ary_new();
|
286
|
+
resp_obj_each(NULL, a->msg, populate_redis_callback_reply, (void *)reply);
|
287
|
+
rb_funcallv(a->block, iodine_call_proc_id, 1, &reply);
|
288
|
+
Registry.remove(a->block);
|
289
|
+
return NULL;
|
290
|
+
}
|
291
|
+
|
292
|
+
/*
|
293
|
+
Redis message callback
|
294
|
+
*/
|
295
|
+
static void redis_callback(pubsub_engine_s *e, resp_object_s *msg,
|
296
|
+
void *block) {
|
297
|
+
struct redis_callback_data d = {
|
298
|
+
.msg = msg, .block = (VALUE)block,
|
299
|
+
};
|
300
|
+
RubyCaller.call_c(perform_redis_callback_inGVL, &d);
|
301
|
+
(void)e;
|
302
|
+
}
|
303
|
+
|
304
|
+
/**
|
305
|
+
Sends commands / messages to the underlying Redis Pub connection.
|
306
|
+
|
307
|
+
The method accepts an optional callback block. i.e.:
|
308
|
+
|
309
|
+
redis.send("Echo", "Hello World!") do |reply|
|
310
|
+
p reply # => ["Hello World!"]
|
311
|
+
end
|
312
|
+
|
313
|
+
This connection is only for publishing and database commands. The Sub commands,
|
314
|
+
such as SUBSCRIBE and PSUBSCRIBE, will break the engine.
|
315
|
+
*/
|
316
|
+
static VALUE redis_send(int argc, VALUE *argv, VALUE self) {
|
317
|
+
if (argc < 1)
|
318
|
+
rb_raise(rb_eArgError,
|
319
|
+
"wrong number of arguments (given %d, expected at least 1).",
|
320
|
+
argc);
|
321
|
+
resp_object_s *cmd = NULL;
|
322
|
+
Check_Type(argv[0], T_STRING);
|
323
|
+
|
324
|
+
iodine_engine_s *e;
|
325
|
+
Data_Get_Struct(self, iodine_engine_s, e);
|
326
|
+
cmd = resp_arr2obj(argc, NULL);
|
327
|
+
for (int i = 0; i < argc; i++) {
|
328
|
+
switch (TYPE(argv[i])) {
|
329
|
+
case T_SYMBOL:
|
330
|
+
argv[i] = rb_sym2str(argv[i]);
|
331
|
+
/* Fallthrough */
|
332
|
+
case T_STRING:
|
333
|
+
resp_obj2arr(cmd)->array[i] =
|
334
|
+
resp_str2obj(RSTRING_PTR(argv[i]), RSTRING_LEN(argv[i]));
|
335
|
+
break;
|
336
|
+
case T_FIXNUM:
|
337
|
+
resp_obj2arr(cmd)->array[i] = resp_num2obj(FIX2LONG(argv[i]));
|
338
|
+
break;
|
339
|
+
default:
|
340
|
+
goto error;
|
341
|
+
break;
|
342
|
+
}
|
343
|
+
}
|
344
|
+
|
345
|
+
if (rb_block_given_p()) {
|
346
|
+
VALUE block = rb_block_proc();
|
347
|
+
Registry.add(block);
|
348
|
+
redis_engine_send(e->p, cmd, redis_callback, (void *)block);
|
349
|
+
return block;
|
350
|
+
} else {
|
351
|
+
redis_engine_send(e->p, cmd, NULL, NULL);
|
352
|
+
}
|
353
|
+
return Qtrue;
|
354
|
+
error:
|
355
|
+
if (cmd)
|
356
|
+
resp_free_object(cmd);
|
357
|
+
rb_raise(rb_eArgError, "Arguments can only include Strings, Symbols and "
|
358
|
+
"Integers - no arrays or hashes or other objects can "
|
359
|
+
"be sent.");
|
360
|
+
return self;
|
361
|
+
}
|
362
|
+
|
363
|
+
/**
|
364
|
+
Initializes a new RedisEngine for Pub/Sub.
|
365
|
+
|
366
|
+
use:
|
367
|
+
|
368
|
+
RedisEngine.new(address, port = 6379, ping_interval = 0)
|
369
|
+
|
370
|
+
Accepts:
|
371
|
+
|
372
|
+
address:: the Redis server's address. Required.
|
373
|
+
port:: the Redis Server port. Default: 6379
|
374
|
+
ping:: the PING interval. Default: 0 (~5 minutes).
|
375
|
+
auth:: authentication password. Default: none.
|
376
|
+
*/
|
377
|
+
static VALUE redis_engine_initialize(int argc, VALUE *argv, VALUE self) {
|
378
|
+
if (argc < 1 || argc > 4)
|
379
|
+
rb_raise(rb_eArgError,
|
380
|
+
"wrong number of arguments (given %d, expected 1..4).", argc);
|
381
|
+
VALUE address = argv[0];
|
382
|
+
VALUE port = argc >= 2 ? argv[1] : Qnil;
|
383
|
+
VALUE ping = argc >= 3 ? argv[2] : Qnil;
|
384
|
+
VALUE auth = argc >= 4 ? argv[3] : Qnil;
|
385
|
+
Check_Type(address, T_STRING);
|
386
|
+
if (port != Qnil) {
|
387
|
+
if (TYPE(port) == T_FIXNUM)
|
388
|
+
port = rb_fix2str(port, 10);
|
389
|
+
Check_Type(port, T_STRING);
|
390
|
+
}
|
391
|
+
if (ping != Qnil)
|
392
|
+
Check_Type(ping, T_FIXNUM);
|
393
|
+
if (auth != Qnil) {
|
394
|
+
Check_Type(auth, T_STRING);
|
395
|
+
}
|
396
|
+
size_t iping = FIX2LONG(ping);
|
397
|
+
if (iping > 255)
|
398
|
+
rb_raise(rb_eRangeError, "ping_interval too big (0..255)");
|
399
|
+
|
400
|
+
iodine_engine_s *engine;
|
401
|
+
Data_Get_Struct(self, iodine_engine_s, engine);
|
402
|
+
engine->handler = self;
|
403
|
+
engine->p =
|
404
|
+
redis_engine_create(.address = StringValueCStr(address),
|
405
|
+
.port =
|
406
|
+
(port == Qnil ? "6379" : StringValueCStr(port)),
|
407
|
+
.ping_interval = iping,
|
408
|
+
.auth = (auth == Qnil ? NULL : StringValueCStr(auth)),
|
409
|
+
.auth_len = (auth == Qnil ? 0 : RSTRING_LEN(auth)));
|
410
|
+
if (!engine->p)
|
411
|
+
rb_raise(rb_eRuntimeError, "unknown error, can't initialize RedisEngine.");
|
412
|
+
engine->dealloc = redis_engine_destroy;
|
413
|
+
return self;
|
414
|
+
}
|
415
|
+
|
416
|
+
/* *****************************************************************************
|
417
|
+
PubSub settings
|
418
|
+
***************************************************************************** */
|
419
|
+
|
420
|
+
/**
|
421
|
+
Sets the default Pub/Sub engine to be used.
|
422
|
+
|
423
|
+
See {Iodine::PubSub} and {Iodine::PubSub::Engine} for more details.
|
424
|
+
*/
|
425
|
+
static VALUE ips_set_default(VALUE self, VALUE en) {
|
426
|
+
iodine_engine_s *e;
|
427
|
+
Data_Get_Struct(en, iodine_engine_s, e);
|
428
|
+
if (!e)
|
429
|
+
rb_raise(rb_eArgError, "deafult engine must be an Iodine::PubSub::Engine.");
|
430
|
+
if (!e->p)
|
431
|
+
rb_raise(rb_eArgError, "This Iodine::PubSub::Engine is broken.");
|
432
|
+
rb_ivar_set(self, default_pubsubid, en);
|
433
|
+
PUBSUB_DEFAULT_ENGINE = e->p;
|
434
|
+
return en;
|
435
|
+
}
|
436
|
+
|
437
|
+
/**
|
438
|
+
Returns the default Pub/Sub engine (if any).
|
439
|
+
|
440
|
+
See {Iodine::PubSub} and {Iodine::PubSub::Engine} for more details.
|
441
|
+
*/
|
442
|
+
static VALUE ips_get_default(VALUE self) {
|
443
|
+
return rb_ivar_get(self, default_pubsubid);
|
444
|
+
}
|
445
|
+
|
446
|
+
/* *****************************************************************************
|
447
|
+
Pub/Sub API
|
448
|
+
***************************************************************************** */
|
449
|
+
|
450
|
+
static void iodine_on_unsubscribe(void *u1, void *u2) {
|
451
|
+
if (u1 && (VALUE)u1 != Qnil && u1 != (VALUE)Qfalse)
|
452
|
+
Registry.remove((VALUE)u1);
|
453
|
+
(void)u2;
|
454
|
+
}
|
455
|
+
|
456
|
+
static void *on_pubsub_notificationinGVL(pubsub_message_s *n) {
|
457
|
+
VALUE rbn[2];
|
458
|
+
rbn[0] = rb_str_new(n->channel.name, n->channel.len);
|
459
|
+
Registry.add(rbn[0]);
|
460
|
+
rbn[1] = rb_str_new(n->msg.data, n->msg.len);
|
461
|
+
Registry.add(rbn[1]);
|
462
|
+
RubyCaller.call2((VALUE)n->udata1, iodine_call_proc_id, 2, rbn);
|
463
|
+
Registry.remove(rbn[0]);
|
464
|
+
Registry.remove(rbn[1]);
|
465
|
+
return NULL;
|
466
|
+
}
|
467
|
+
|
468
|
+
static void on_pubsub_notificationin(pubsub_message_s *n) {
|
469
|
+
RubyCaller.call_c((void *(*)(void *))on_pubsub_notificationinGVL, n);
|
470
|
+
}
|
471
|
+
|
472
|
+
/**
|
473
|
+
Subscribes the process to a channel belonging to a specific pub/sub service
|
474
|
+
(using an {Iodine::PubSub::Engine} to connect Iodine to the service).
|
475
|
+
|
476
|
+
The function accepts a single argument (a Hash) and a required block.
|
477
|
+
|
478
|
+
Accepts a single Hash argument with the following possible options:
|
479
|
+
|
480
|
+
:engine :: If provided, the engine to use for pub/sub. Otherwise the default
|
481
|
+
engine is used.
|
482
|
+
|
483
|
+
:channel :: Required (unless :pattern). The channel to subscribe to.
|
484
|
+
|
485
|
+
:pattern :: An alternative to the required :channel, subscribes to a pattern.
|
486
|
+
|
487
|
+
*/
|
488
|
+
static VALUE iodine_subscribe(VALUE self, VALUE args) {
|
489
|
+
Check_Type(args, T_HASH);
|
490
|
+
rb_need_block();
|
491
|
+
|
492
|
+
uint8_t use_pattern = 0;
|
493
|
+
|
494
|
+
VALUE rb_ch = rb_hash_aref(args, channel_var_id);
|
495
|
+
if (rb_ch == Qnil || rb_ch == Qfalse) {
|
496
|
+
use_pattern = 1;
|
497
|
+
rb_ch = rb_hash_aref(args, pattern_var_id);
|
498
|
+
if (rb_ch == Qnil || rb_ch == Qfalse)
|
499
|
+
rb_raise(rb_eArgError, "a channel is required for pub/sub methods.");
|
500
|
+
}
|
501
|
+
if (TYPE(rb_ch) == T_SYMBOL)
|
502
|
+
rb_ch = rb_sym2str(rb_ch);
|
503
|
+
Check_Type(rb_ch, T_STRING);
|
504
|
+
|
505
|
+
VALUE block = rb_block_proc();
|
506
|
+
|
507
|
+
pubsub_engine_s *engine =
|
508
|
+
iodine_engine_ruby2facil(rb_hash_aref(args, engine_varid));
|
509
|
+
|
510
|
+
uintptr_t subid = (uintptr_t)
|
511
|
+
pubsub_subscribe(.channel.name = RSTRING_PTR(rb_ch),
|
512
|
+
.channel.len = RSTRING_LEN(rb_ch), .engine = engine,
|
513
|
+
.use_pattern = use_pattern,
|
514
|
+
.on_message = (block ? on_pubsub_notificationin : NULL),
|
515
|
+
.on_unsubscribe = (block ? iodine_on_unsubscribe : NULL),
|
516
|
+
.udata1 = (void *)block);
|
517
|
+
if (!subid)
|
518
|
+
return Qnil;
|
519
|
+
return ULL2NUM(subid);
|
520
|
+
(void)self;
|
521
|
+
}
|
522
|
+
|
523
|
+
/**
|
524
|
+
Cancels the subscription matching `sub_id`.
|
525
|
+
*/
|
526
|
+
static VALUE iodine_unsubscribe(VALUE self, VALUE sub_id) {
|
527
|
+
if (sub_id == Qnil || sub_id == Qfalse)
|
528
|
+
return Qnil;
|
529
|
+
Check_Type(sub_id, T_FIXNUM);
|
530
|
+
pubsub_unsubscribe((pubsub_sub_pt)NUM2LONG(sub_id));
|
531
|
+
return Qnil;
|
532
|
+
(void)self;
|
533
|
+
}
|
534
|
+
|
535
|
+
/**
|
536
|
+
Publishes a message to a channel.
|
537
|
+
|
538
|
+
Accepts a single Hash argument with the following possible options:
|
539
|
+
|
540
|
+
:engine :: If provided, the engine to use for pub/sub. Otherwise the default
|
541
|
+
engine is used.
|
542
|
+
|
543
|
+
:channel :: Required (unless :pattern). The channel to publish to.
|
544
|
+
|
545
|
+
:pattern :: An alternative to the required :channel, publishes to a pattern.
|
546
|
+
This is NOT supported by Redis and it's limited to the local process cluster.
|
547
|
+
|
548
|
+
:message :: REQUIRED. The message to be published.
|
549
|
+
:
|
550
|
+
*/
|
551
|
+
static VALUE iodine_publish(VALUE self, VALUE args) {
|
552
|
+
Check_Type(args, T_HASH);
|
553
|
+
uint8_t use_pattern = 0;
|
554
|
+
|
555
|
+
VALUE rb_ch = rb_hash_aref(args, channel_var_id);
|
556
|
+
if (rb_ch == Qnil || rb_ch == Qfalse) {
|
557
|
+
use_pattern = 1;
|
558
|
+
rb_ch = rb_hash_aref(args, pattern_var_id);
|
559
|
+
if (rb_ch == Qnil || rb_ch == Qfalse)
|
560
|
+
rb_raise(rb_eArgError, "channel is required for pub/sub methods.");
|
561
|
+
}
|
562
|
+
if (TYPE(rb_ch) == T_SYMBOL)
|
563
|
+
rb_ch = rb_sym2str(rb_ch);
|
564
|
+
Check_Type(rb_ch, T_STRING);
|
565
|
+
|
566
|
+
VALUE rb_msg = rb_hash_aref(args, message_var_id);
|
567
|
+
if (rb_msg == Qnil || rb_msg == Qfalse) {
|
568
|
+
rb_raise(rb_eArgError, "message is required for the :publish method.");
|
569
|
+
}
|
570
|
+
Check_Type(rb_msg, T_STRING);
|
571
|
+
|
572
|
+
pubsub_engine_s *engine =
|
573
|
+
iodine_engine_ruby2facil(rb_hash_aref(args, engine_varid));
|
574
|
+
|
575
|
+
intptr_t ret =
|
576
|
+
pubsub_publish(.engine = engine, .channel.name = (RSTRING_PTR(rb_ch)),
|
577
|
+
.channel.len = (RSTRING_LEN(rb_ch)),
|
578
|
+
.msg.data = (RSTRING_PTR(rb_msg)),
|
579
|
+
.msg.len = (RSTRING_LEN(rb_msg)),
|
580
|
+
.use_pattern = use_pattern);
|
581
|
+
if (!ret)
|
582
|
+
return Qfalse;
|
583
|
+
return Qtrue;
|
584
|
+
(void)self;
|
585
|
+
}
|
586
|
+
|
587
|
+
/* *****************************************************************************
|
588
|
+
Initialization
|
589
|
+
***************************************************************************** */
|
590
|
+
void Iodine_init_pubsub(void) {
|
591
|
+
engine_varid = rb_intern("engine");
|
592
|
+
engine_subid = rb_intern("subscribe");
|
593
|
+
engine_unsubid = rb_intern("unsubscribe");
|
594
|
+
engine_pubid = rb_intern("publish");
|
595
|
+
default_pubsubid = rb_intern("default_pubsub");
|
596
|
+
channel_var_id = ID2SYM(rb_intern("channel"));
|
597
|
+
pattern_var_id = ID2SYM(rb_intern("pattern"));
|
598
|
+
message_var_id = ID2SYM(rb_intern("message"));
|
599
|
+
|
600
|
+
IodinePubSub = rb_define_module_under(Iodine, "PubSub");
|
601
|
+
IodineEngine = rb_define_class_under(IodinePubSub, "Engine", rb_cObject);
|
602
|
+
|
603
|
+
rb_define_alloc_func(IodineEngine, engine_alloc_c);
|
604
|
+
rb_define_method(IodineEngine, "initialize", engine_initialize, 0);
|
605
|
+
|
606
|
+
rb_define_method(IodineEngine, "distribute", engine_distribute, -1);
|
607
|
+
rb_define_method(IodineEngine, "subscribe", engine_sub_placeholder, 2);
|
608
|
+
rb_define_method(IodineEngine, "unsubscribe", engine_sub_placeholder, 2);
|
609
|
+
rb_define_method(IodineEngine, "publish", engine_pub_placeholder, 3);
|
610
|
+
|
611
|
+
rb_define_module_function(Iodine, "default_pubsub=", ips_set_default, 1);
|
612
|
+
rb_define_module_function(Iodine, "default_pubsub", ips_get_default, 0);
|
613
|
+
|
614
|
+
rb_define_module_function(Iodine, "subscribe", iodine_subscribe, 1);
|
615
|
+
rb_define_module_function(Iodine, "unsubscribe", iodine_unsubscribe, 1);
|
616
|
+
rb_define_module_function(Iodine, "publish", iodine_publish, 1);
|
617
|
+
|
618
|
+
/* *************************
|
619
|
+
Initialize C pubsub engines
|
620
|
+
************************** */
|
621
|
+
VALUE engine_in_c;
|
622
|
+
iodine_engine_s *engine;
|
623
|
+
|
624
|
+
engine_in_c = rb_funcallv(IodineEngine, iodine_new_func_id, 0, NULL);
|
625
|
+
Data_Get_Struct(engine_in_c, iodine_engine_s, engine);
|
626
|
+
engine->p = (pubsub_engine_s *)PUBSUB_CLUSTER_ENGINE;
|
627
|
+
|
628
|
+
/** This is the (currently) default pub/sub engine. It will distribute
|
629
|
+
* messages to all subscribers in the process cluster. */
|
630
|
+
rb_define_const(IodinePubSub, "CLUSTER", engine_in_c);
|
631
|
+
// rb_const_set(IodineEngine, rb_intern("CLUSTER"), e);
|
632
|
+
|
633
|
+
engine_in_c = rb_funcallv(IodineEngine, iodine_new_func_id, 0, NULL);
|
634
|
+
Data_Get_Struct(engine_in_c, iodine_engine_s, engine);
|
635
|
+
engine->p = (pubsub_engine_s *)PUBSUB_PROCESS_ENGINE;
|
636
|
+
|
637
|
+
/** This is a single process pub/sub engine. It will distribute messages to
|
638
|
+
* all subscribers sharing the same process. */
|
639
|
+
rb_define_const(IodinePubSub, "SINGLE_PROCESS", engine_in_c);
|
640
|
+
// rb_const_set(IodineEngine, rb_intern("SINGLE_PROCESS"), e);
|
641
|
+
|
642
|
+
engine_in_c =
|
643
|
+
rb_define_class_under(IodinePubSub, "RedisEngine", IodineEngine);
|
644
|
+
rb_define_method(engine_in_c, "initialize", redis_engine_initialize, -1);
|
645
|
+
rb_define_method(engine_in_c, "send", redis_send, -1);
|
646
|
+
}
|