rage-iodine 1.7.58
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- data/.github/workflows/ruby.yml +42 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +1098 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/LIMITS.md +41 -0
- data/README.md +782 -0
- data/Rakefile +23 -0
- data/SPEC-PubSub-Draft.md +159 -0
- data/SPEC-WebSocket-Draft.md +239 -0
- data/bin/console +22 -0
- data/bin/info.md +353 -0
- data/bin/mustache_bench.rb +100 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/examples/async_task.ru +92 -0
- data/examples/bates/README.md +3 -0
- data/examples/bates/config.ru +342 -0
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +600 -0
- data/examples/config.ru +59 -0
- data/examples/echo.ru +59 -0
- data/examples/etag.ru +16 -0
- data/examples/hello.ru +29 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/rack3.ru +12 -0
- data/examples/redis.ru +70 -0
- data/examples/shootout.ru +73 -0
- data/examples/sub-protocols.ru +90 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +280 -0
- data/ext/iodine/extconf.rb +110 -0
- data/ext/iodine/fio.c +12096 -0
- data/ext/iodine/fio.h +6390 -0
- data/ext/iodine/fio_cli.c +431 -0
- data/ext/iodine/fio_cli.h +189 -0
- data/ext/iodine/fio_json_parser.h +687 -0
- data/ext/iodine/fio_siphash.c +157 -0
- data/ext/iodine/fio_siphash.h +37 -0
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +649 -0
- data/ext/iodine/fio_tls_openssl.c +1056 -0
- data/ext/iodine/fio_tmpfile.h +50 -0
- data/ext/iodine/fiobj.h +44 -0
- data/ext/iodine/fiobj4fio.h +21 -0
- data/ext/iodine/fiobj_ary.c +333 -0
- data/ext/iodine/fiobj_ary.h +139 -0
- data/ext/iodine/fiobj_data.c +1185 -0
- data/ext/iodine/fiobj_data.h +167 -0
- data/ext/iodine/fiobj_hash.c +409 -0
- data/ext/iodine/fiobj_hash.h +176 -0
- data/ext/iodine/fiobj_json.c +622 -0
- data/ext/iodine/fiobj_json.h +68 -0
- data/ext/iodine/fiobj_mem.h +71 -0
- data/ext/iodine/fiobj_mustache.c +317 -0
- data/ext/iodine/fiobj_mustache.h +62 -0
- data/ext/iodine/fiobj_numbers.c +344 -0
- data/ext/iodine/fiobj_numbers.h +127 -0
- data/ext/iodine/fiobj_str.c +433 -0
- data/ext/iodine/fiobj_str.h +172 -0
- data/ext/iodine/fiobject.c +620 -0
- data/ext/iodine/fiobject.h +654 -0
- data/ext/iodine/hpack.h +1923 -0
- data/ext/iodine/http.c +2736 -0
- data/ext/iodine/http.h +1019 -0
- data/ext/iodine/http1.c +825 -0
- data/ext/iodine/http1.h +29 -0
- data/ext/iodine/http1_parser.h +1835 -0
- data/ext/iodine/http_internal.c +1279 -0
- data/ext/iodine/http_internal.h +248 -0
- data/ext/iodine/http_mime_parser.h +350 -0
- data/ext/iodine/iodine.c +1433 -0
- data/ext/iodine/iodine.h +64 -0
- data/ext/iodine/iodine_caller.c +218 -0
- data/ext/iodine/iodine_caller.h +27 -0
- data/ext/iodine/iodine_connection.c +941 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +420 -0
- data/ext/iodine/iodine_defer.h +6 -0
- data/ext/iodine/iodine_fiobj2rb.h +120 -0
- data/ext/iodine/iodine_helpers.c +282 -0
- data/ext/iodine/iodine_helpers.h +12 -0
- data/ext/iodine/iodine_http.c +1280 -0
- data/ext/iodine/iodine_http.h +23 -0
- data/ext/iodine/iodine_json.c +302 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_mustache.c +567 -0
- data/ext/iodine/iodine_mustache.h +6 -0
- data/ext/iodine/iodine_pubsub.c +580 -0
- data/ext/iodine/iodine_pubsub.h +26 -0
- data/ext/iodine/iodine_rack_io.c +273 -0
- data/ext/iodine/iodine_rack_io.h +20 -0
- data/ext/iodine/iodine_store.c +142 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +346 -0
- data/ext/iodine/iodine_tcp.h +13 -0
- data/ext/iodine/iodine_tls.c +261 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1546 -0
- data/ext/iodine/redis_engine.c +957 -0
- data/ext/iodine/redis_engine.h +79 -0
- data/ext/iodine/resp_parser.h +317 -0
- data/ext/iodine/scheduler.c +173 -0
- data/ext/iodine/scheduler.h +6 -0
- data/ext/iodine/websocket_parser.h +506 -0
- data/ext/iodine/websockets.c +752 -0
- data/ext/iodine/websockets.h +185 -0
- data/iodine.gemspec +50 -0
- data/lib/iodine/connection.rb +61 -0
- data/lib/iodine/json.rb +42 -0
- data/lib/iodine/mustache.rb +113 -0
- data/lib/iodine/pubsub.rb +55 -0
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +3 -0
- data/lib/iodine.rb +274 -0
- data/lib/rack/handler/iodine.rb +33 -0
- data/logo.png +0 -0
- metadata +284 -0
@@ -0,0 +1,580 @@
|
|
1
|
+
#include "iodine_pubsub.h"
|
2
|
+
#include "iodine_fiobj2rb.h"
|
3
|
+
|
4
|
+
#include "redis_engine.h"
|
5
|
+
|
6
|
+
/*
|
7
|
+
NOTE:
|
8
|
+
|
9
|
+
This file defines Pub/Sub management and settings, not Pub/Sub usage.
|
10
|
+
|
11
|
+
This file doen't include the `Iodine.subscribe`, `Iodine.unsubscribe` and
|
12
|
+
`Iodine.publish` methods.
|
13
|
+
|
14
|
+
These methods are all defined in the Connection module (iodine_connection.h).
|
15
|
+
*/
|
16
|
+
|
17
|
+
/* *****************************************************************************
|
18
|
+
static consts
|
19
|
+
***************************************************************************** */
|
20
|
+
|
21
|
+
static ID subscribe_id;
|
22
|
+
static ID unsubscribe_id;
|
23
|
+
static ID publish_id;
|
24
|
+
static ID default_id;
|
25
|
+
static ID redis_id;
|
26
|
+
static ID call_id;
|
27
|
+
|
28
|
+
/**
|
29
|
+
The {Iodine::PubSub::Engine} class is the parent for all engines to inherit
|
30
|
+
from.
|
31
|
+
|
32
|
+
Engines should inherit this class and override the `subscribe`, `unsubscribe`
|
33
|
+
and `publish` callbacks (which shall be called by {Iodine}).
|
34
|
+
|
35
|
+
After creation, Engines should attach themselves to Iodine using
|
36
|
+
{Iodine::PubSub.attach} or their callbacks will never get called.
|
37
|
+
|
38
|
+
Engines can also set themselves to be the default engine using
|
39
|
+
{Iodine::PubSub.default=}.
|
40
|
+
*/
|
41
|
+
static VALUE EngineClass;
|
42
|
+
|
43
|
+
/* *****************************************************************************
|
44
|
+
Ruby <=> C Callbacks
|
45
|
+
***************************************************************************** */
|
46
|
+
|
47
|
+
typedef struct {
|
48
|
+
iodine_pubsub_s *eng;
|
49
|
+
fio_str_info_s ch;
|
50
|
+
fio_str_info_s msg;
|
51
|
+
fio_match_fn pattern;
|
52
|
+
} iodine_pubsub_task_s;
|
53
|
+
|
54
|
+
#define iodine_engine(eng) ((iodine_pubsub_s *)(eng))
|
55
|
+
|
56
|
+
/* calls an engine's `subscribe` callback within the GVL */
|
57
|
+
static void *iodine_pubsub_GIL_subscribe(void *tsk_) {
|
58
|
+
iodine_pubsub_task_s *task = tsk_;
|
59
|
+
VALUE args[2];
|
60
|
+
args[0] = rb_str_new(task->ch.data, task->ch.len);
|
61
|
+
args[1] = task->pattern ? Qtrue : Qnil; // TODO: Qtrue should be :redis
|
62
|
+
IodineCaller.call2(task->eng->handler, subscribe_id, 2, args);
|
63
|
+
return NULL;
|
64
|
+
}
|
65
|
+
|
66
|
+
/** Must subscribe channel. Failures are ignored. */
|
67
|
+
static void iodine_pubsub_on_subscribe(const fio_pubsub_engine_s *eng,
|
68
|
+
fio_str_info_s channel,
|
69
|
+
fio_match_fn match) {
|
70
|
+
if (iodine_engine(eng)->handler == Qnil) {
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
iodine_pubsub_task_s task = {
|
74
|
+
.eng = iodine_engine(eng), .ch = channel, .pattern = match};
|
75
|
+
IodineCaller.enterGVL(iodine_pubsub_GIL_subscribe, &task);
|
76
|
+
}
|
77
|
+
|
78
|
+
/* calls an engine's `unsubscribe` callback within the GVL */
|
79
|
+
static void *iodine_pubsub_GIL_unsubscribe(void *tsk_) {
|
80
|
+
iodine_pubsub_task_s *task = tsk_;
|
81
|
+
VALUE args[2];
|
82
|
+
args[0] = rb_str_new(task->ch.data, task->ch.len);
|
83
|
+
args[1] = task->pattern ? Qtrue : Qnil; // TODO: Qtrue should be :redis
|
84
|
+
IodineCaller.call2(task->eng->handler, unsubscribe_id, 2, args);
|
85
|
+
return NULL;
|
86
|
+
}
|
87
|
+
|
88
|
+
/** Must unsubscribe channel. Failures are ignored. */
|
89
|
+
static void iodine_pubsub_on_unsubscribe(const fio_pubsub_engine_s *eng,
|
90
|
+
fio_str_info_s channel,
|
91
|
+
fio_match_fn match) {
|
92
|
+
if (iodine_engine(eng)->handler == Qnil) {
|
93
|
+
return;
|
94
|
+
}
|
95
|
+
iodine_pubsub_task_s task = {
|
96
|
+
.eng = iodine_engine(eng), .ch = channel, .pattern = match};
|
97
|
+
IodineCaller.enterGVL(iodine_pubsub_GIL_unsubscribe, &task);
|
98
|
+
}
|
99
|
+
|
100
|
+
/* calls an engine's `unsubscribe` callback within the GVL */
|
101
|
+
static void *iodine_pubsub_GIL_publish(void *tsk_) {
|
102
|
+
iodine_pubsub_task_s *task = tsk_;
|
103
|
+
VALUE args[2];
|
104
|
+
args[0] = rb_str_new(task->ch.data, task->ch.len);
|
105
|
+
args[1] = rb_str_new(task->msg.data, task->msg.len);
|
106
|
+
IodineCaller.call2(task->eng->handler, publish_id, 2, args);
|
107
|
+
return NULL;
|
108
|
+
}
|
109
|
+
|
110
|
+
/** Should return 0 on success and -1 on failure. */
|
111
|
+
static void iodine_pubsub_on_publish(const fio_pubsub_engine_s *eng,
|
112
|
+
fio_str_info_s channel, fio_str_info_s msg,
|
113
|
+
uint8_t is_json) {
|
114
|
+
if (iodine_engine(eng)->handler == Qnil) {
|
115
|
+
return;
|
116
|
+
}
|
117
|
+
iodine_pubsub_task_s task = {
|
118
|
+
.eng = iodine_engine(eng), .ch = channel, .msg = msg};
|
119
|
+
IodineCaller.enterGVL(iodine_pubsub_GIL_publish, &task);
|
120
|
+
(void)is_json;
|
121
|
+
}
|
122
|
+
|
123
|
+
/* *****************************************************************************
|
124
|
+
Ruby methods
|
125
|
+
***************************************************************************** */
|
126
|
+
|
127
|
+
/**
|
128
|
+
OVERRIDE this callback - it will be called by {Iodine} whenever the process
|
129
|
+
CLUSTER (not just this process) subscribes to a stream / channel.
|
130
|
+
*/
|
131
|
+
static VALUE iodine_pubsub_subscribe(VALUE self, VALUE to, VALUE match) {
|
132
|
+
return Qnil;
|
133
|
+
(void)self;
|
134
|
+
(void)to;
|
135
|
+
(void)match;
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
OVERRIDE this callback - it will be called by {Iodine} whenever the whole
|
140
|
+
process CLUSTER (not just this process) unsubscribes from a stream / channel.
|
141
|
+
*/
|
142
|
+
static VALUE iodine_pubsub_unsubscribe(VALUE self, VALUE to, VALUE match) {
|
143
|
+
return Qnil;
|
144
|
+
(void)self;
|
145
|
+
(void)to;
|
146
|
+
(void)match;
|
147
|
+
}
|
148
|
+
|
149
|
+
/**
|
150
|
+
OVERRIDE this callback - it will be called by {Iodine} whenever the
|
151
|
+
{Iodine.publish} (or {Iodine::Connection#publish}) is called for this engine.
|
152
|
+
|
153
|
+
If this {Engine} is set as the default {Engine}, then any call to
|
154
|
+
{Iodine.publish} (or {Iodine::Connection#publish} will invoke this callback
|
155
|
+
(unless another {Engine} was specified).
|
156
|
+
|
157
|
+
NOTE: this callback is called per process event (not per cluster event) and the
|
158
|
+
{Engine} is responsible for message propagation.
|
159
|
+
*/
|
160
|
+
static VALUE iodine_pubsub_publish(VALUE self, VALUE to, VALUE message) {
|
161
|
+
iodine_pubsub_s *e = iodine_pubsub_CData(self);
|
162
|
+
if (!e || e->engine == &e->do_not_touch) {
|
163
|
+
/* this is a Ruby engine, nothing to do. */
|
164
|
+
return Qnil;
|
165
|
+
}
|
166
|
+
fio_publish(.engine = e->engine, .channel = IODINE_RSTRINFO(to),
|
167
|
+
.message = IODINE_RSTRINFO(message));
|
168
|
+
return self;
|
169
|
+
}
|
170
|
+
|
171
|
+
/* *****************************************************************************
|
172
|
+
Ruby <=> C Data Type
|
173
|
+
***************************************************************************** */
|
174
|
+
|
175
|
+
/* a callback for the GC (marking active objects) */
|
176
|
+
static void iodine_pubsub_data_mark(void *c_) {
|
177
|
+
iodine_pubsub_s *c = c_;
|
178
|
+
if (c->handler != Qnil) {
|
179
|
+
rb_gc_mark(c->handler);
|
180
|
+
}
|
181
|
+
}
|
182
|
+
/* a callback for the GC (marking active objects) */
|
183
|
+
static void iodine_pubsub_data_free(void *c_) {
|
184
|
+
FIO_LOG_DEBUG("iodine destroying engine");
|
185
|
+
iodine_pubsub_s *data = c_;
|
186
|
+
fio_pubsub_detach(data->engine);
|
187
|
+
IodineStore.remove(data->handler); /* redundant except during exit */
|
188
|
+
if (data->dealloc) {
|
189
|
+
data->dealloc(data->engine);
|
190
|
+
}
|
191
|
+
free(data);
|
192
|
+
}
|
193
|
+
|
194
|
+
static size_t iodine_pubsub_data_size(const void *c_) {
|
195
|
+
return sizeof(iodine_pubsub_s);
|
196
|
+
(void)c_;
|
197
|
+
}
|
198
|
+
|
199
|
+
const rb_data_type_t iodine_pubsub_data_type = {
|
200
|
+
.wrap_struct_name = "IodinePubSubData",
|
201
|
+
.function =
|
202
|
+
{
|
203
|
+
.dmark = iodine_pubsub_data_mark,
|
204
|
+
.dfree = iodine_pubsub_data_free,
|
205
|
+
.dsize = iodine_pubsub_data_size,
|
206
|
+
},
|
207
|
+
.data = NULL,
|
208
|
+
// .flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
209
|
+
};
|
210
|
+
|
211
|
+
/* Iodine::PubSub::Engine.allocate */
|
212
|
+
static VALUE iodine_pubsub_data_alloc_c(VALUE self) {
|
213
|
+
iodine_pubsub_s *c = malloc(sizeof(*c));
|
214
|
+
*c = (iodine_pubsub_s){
|
215
|
+
.do_not_touch =
|
216
|
+
{
|
217
|
+
.subscribe = iodine_pubsub_on_subscribe,
|
218
|
+
.unsubscribe = iodine_pubsub_on_unsubscribe,
|
219
|
+
.publish = iodine_pubsub_on_publish,
|
220
|
+
},
|
221
|
+
.handler = Qnil,
|
222
|
+
.engine = &c->do_not_touch,
|
223
|
+
};
|
224
|
+
return TypedData_Wrap_Struct(self, &iodine_pubsub_data_type, c);
|
225
|
+
}
|
226
|
+
|
227
|
+
/* *****************************************************************************
|
228
|
+
C engines
|
229
|
+
***************************************************************************** */
|
230
|
+
|
231
|
+
static VALUE iodine_pubsub_make_C_engine(const fio_pubsub_engine_s *e) {
|
232
|
+
VALUE engine = IodineCaller.call(EngineClass, rb_intern2("new", 3));
|
233
|
+
if (engine == Qnil) {
|
234
|
+
return Qnil;
|
235
|
+
}
|
236
|
+
iodine_pubsub_CData(engine)->engine = (fio_pubsub_engine_s *)e;
|
237
|
+
return engine;
|
238
|
+
}
|
239
|
+
|
240
|
+
/* *****************************************************************************
|
241
|
+
PubSub module methods
|
242
|
+
***************************************************************************** */
|
243
|
+
|
244
|
+
/** Sets the default {Iodine::PubSub::Engine} for pub/sub methods. */
|
245
|
+
static VALUE iodine_pubsub_default_set(VALUE self, VALUE engine) {
|
246
|
+
if (engine == Qnil) {
|
247
|
+
engine = rb_const_get(self, rb_intern2("CLUSTER", 7));
|
248
|
+
}
|
249
|
+
iodine_pubsub_s *e = iodine_pubsub_CData(engine);
|
250
|
+
if (!e) {
|
251
|
+
rb_raise(rb_eTypeError, "not a valid engine");
|
252
|
+
return Qnil;
|
253
|
+
}
|
254
|
+
if (e->handler == Qnil) {
|
255
|
+
e->handler = engine;
|
256
|
+
}
|
257
|
+
FIO_PUBSUB_DEFAULT = e->engine;
|
258
|
+
rb_ivar_set(self, rb_intern2("default_engine", 14), engine);
|
259
|
+
return engine;
|
260
|
+
}
|
261
|
+
|
262
|
+
/** Returns the default {Iodine::PubSub::Engine} for pub/sub methods. */
|
263
|
+
static VALUE iodine_pubsub_default_get(VALUE self) {
|
264
|
+
VALUE def = rb_ivar_get(self, rb_intern2("default_engine", 14));
|
265
|
+
if (def == Qnil) {
|
266
|
+
def = rb_const_get(self, rb_intern2("CLUSTER", 7));
|
267
|
+
iodine_pubsub_default_set(self, def);
|
268
|
+
}
|
269
|
+
return def;
|
270
|
+
}
|
271
|
+
|
272
|
+
/**
|
273
|
+
* Attaches an {Iodine::PubSub::Engine} to the pub/sub system (more than a
|
274
|
+
* single engine can be attached at the same time).
|
275
|
+
*
|
276
|
+
* After an engine was attached, it's callbacks will be called
|
277
|
+
* ({Iodine::PubSub::Engine#subscribe} and {Iodine::PubSub::Engine#unsubscribe})
|
278
|
+
* in response to Pub/Sub events.
|
279
|
+
*/
|
280
|
+
static VALUE iodine_pubsub_attach(VALUE self, VALUE engine) {
|
281
|
+
iodine_pubsub_s *e = iodine_pubsub_CData(engine);
|
282
|
+
if (!e) {
|
283
|
+
rb_raise(rb_eTypeError, "not a valid engine");
|
284
|
+
return Qnil;
|
285
|
+
}
|
286
|
+
if (e->handler == Qnil) {
|
287
|
+
e->handler = engine;
|
288
|
+
}
|
289
|
+
IodineStore.add(engine);
|
290
|
+
fio_pubsub_attach(e->engine);
|
291
|
+
return engine;
|
292
|
+
(void)self;
|
293
|
+
}
|
294
|
+
|
295
|
+
/**
|
296
|
+
* Removes an {Iodine::PubSub::Engine} from the pub/sub system.
|
297
|
+
*
|
298
|
+
* After an {Iodine::PubSub::Engine} was detached, Iodine will no longer call
|
299
|
+
* the {Iodine::PubSub::Engine}'s callbacks ({Iodine::PubSub::Engine#subscribe}
|
300
|
+
* and {Iodine::PubSub::Engine#unsubscribe})
|
301
|
+
*/
|
302
|
+
static VALUE iodine_pubsub_detach(VALUE self, VALUE engine) {
|
303
|
+
iodine_pubsub_s *e = iodine_pubsub_CData(engine);
|
304
|
+
if (!e) {
|
305
|
+
rb_raise(rb_eTypeError, "not a valid engine");
|
306
|
+
return Qnil;
|
307
|
+
}
|
308
|
+
if (e->handler == Qnil) {
|
309
|
+
e->handler = engine;
|
310
|
+
}
|
311
|
+
IodineStore.remove(engine);
|
312
|
+
fio_pubsub_detach(e->engine);
|
313
|
+
return engine;
|
314
|
+
(void)self;
|
315
|
+
}
|
316
|
+
|
317
|
+
/**
|
318
|
+
* Forces {Iodine} to call the {Iodine::PubSub::Engine#subscribe} callback for
|
319
|
+
* all existing subscriptions (i.e., when reconnecting to a Pub/Sub backend such
|
320
|
+
* as Redis).
|
321
|
+
*/
|
322
|
+
static VALUE iodine_pubsub_reset(VALUE self, VALUE engine) {
|
323
|
+
iodine_pubsub_s *e = iodine_pubsub_CData(engine);
|
324
|
+
if (!e) {
|
325
|
+
rb_raise(rb_eTypeError, "not a valid engine");
|
326
|
+
return Qnil;
|
327
|
+
}
|
328
|
+
if (e->handler == Qnil) {
|
329
|
+
e->handler = engine;
|
330
|
+
}
|
331
|
+
fio_pubsub_reattach(e->engine);
|
332
|
+
return engine;
|
333
|
+
(void)self;
|
334
|
+
}
|
335
|
+
|
336
|
+
/* *****************************************************************************
|
337
|
+
Redis Engine
|
338
|
+
***************************************************************************** */
|
339
|
+
|
340
|
+
/**
|
341
|
+
Initializes a new {Iodine::PubSub::Redis} engine.
|
342
|
+
|
343
|
+
Iodine::PubSub::Redis.new(url, opt = {})
|
344
|
+
|
345
|
+
use:
|
346
|
+
|
347
|
+
REDIS_URL = "redis://localhost:6379/"
|
348
|
+
Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
|
349
|
+
|
350
|
+
To use Redis authentication, add the password to the URL. i.e.:
|
351
|
+
|
352
|
+
REDIS_URL = "redis://redis:password@localhost:6379/"
|
353
|
+
Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
|
354
|
+
|
355
|
+
The options hash accepts:
|
356
|
+
|
357
|
+
:ping:: the PING interval up to 255 seconds. Default: 0 (~5 minutes).
|
358
|
+
*/
|
359
|
+
static VALUE iodine_pubsub_redis_new(int argc, VALUE *argv, VALUE self) {
|
360
|
+
if (!argc) {
|
361
|
+
rb_raise(rb_eArgError, "Iodine::PubSub::Redis.new(address, opt={}) "
|
362
|
+
"requires at least 1 argument.");
|
363
|
+
}
|
364
|
+
VALUE url = argv[0];
|
365
|
+
Check_Type(url, T_STRING);
|
366
|
+
if (RSTRING_LEN(url) > 4096) {
|
367
|
+
rb_raise(rb_eArgError, "Redis URL too long.");
|
368
|
+
}
|
369
|
+
uint8_t ping = 0;
|
370
|
+
|
371
|
+
iodine_pubsub_s *e = iodine_pubsub_CData(self);
|
372
|
+
if (!e) {
|
373
|
+
rb_raise(rb_eTypeError, "not a valid engine");
|
374
|
+
return Qnil;
|
375
|
+
}
|
376
|
+
|
377
|
+
/* extract options */
|
378
|
+
if (argc == 2) {
|
379
|
+
Check_Type(argv[1], T_HASH);
|
380
|
+
VALUE tmp = rb_hash_aref(argv[1], rb_id2sym(rb_intern2("ping", 4)));
|
381
|
+
if (tmp != Qnil) {
|
382
|
+
Check_Type(tmp, T_FIXNUM);
|
383
|
+
if (NUM2SIZET(tmp) > 255) {
|
384
|
+
rb_raise(rb_eArgError,
|
385
|
+
":ping must be a non-negative integer under 255 seconds.");
|
386
|
+
}
|
387
|
+
ping = (uint8_t)NUM2SIZET(tmp);
|
388
|
+
}
|
389
|
+
}
|
390
|
+
|
391
|
+
/* parse URL assume redis://redis:password@localhost:6379 */
|
392
|
+
fio_url_s info = fio_url_parse(RSTRING_PTR(url), RSTRING_LEN(url));
|
393
|
+
|
394
|
+
FIO_LOG_INFO("Initializing Redis engine for address: %.*s",
|
395
|
+
(int)RSTRING_LEN(url), RSTRING_PTR(url));
|
396
|
+
/* create engine */
|
397
|
+
e->engine = redis_engine_create(.address = info.host, .port = info.port,
|
398
|
+
.auth = info.password, .ping_interval = ping);
|
399
|
+
if (!e->engine) {
|
400
|
+
e->engine = &e->do_not_touch;
|
401
|
+
} else {
|
402
|
+
e->dealloc = redis_engine_destroy;
|
403
|
+
}
|
404
|
+
|
405
|
+
if (e->engine == &e->do_not_touch) {
|
406
|
+
rb_raise(rb_eArgError,
|
407
|
+
"Error initializing the Redis engine - malformed URL?");
|
408
|
+
}
|
409
|
+
return self;
|
410
|
+
(void)self;
|
411
|
+
(void)argc;
|
412
|
+
(void)argv;
|
413
|
+
}
|
414
|
+
|
415
|
+
struct redis_callback_data {
|
416
|
+
FIOBJ response;
|
417
|
+
VALUE block;
|
418
|
+
};
|
419
|
+
|
420
|
+
/** A callback for Redis commands. */
|
421
|
+
static void *iodine_pubsub_redis_callback_in_gil(void *data_) {
|
422
|
+
struct redis_callback_data *d = data_;
|
423
|
+
VALUE rb = Qnil;
|
424
|
+
if (!FIOBJ_IS_NULL(d->response)) {
|
425
|
+
rb = fiobj2rb_deep(d->response, 0);
|
426
|
+
}
|
427
|
+
IodineCaller.call2(d->block, call_id, 1, &rb);
|
428
|
+
IodineStore.remove(rb);
|
429
|
+
return NULL;
|
430
|
+
}
|
431
|
+
|
432
|
+
/** A callback for Redis commands. */
|
433
|
+
static void iodine_pubsub_redis_callback(fio_pubsub_engine_s *e, FIOBJ response,
|
434
|
+
void *udata) {
|
435
|
+
struct redis_callback_data d = {.response = response, .block = (VALUE)udata};
|
436
|
+
if (d.block == Qnil) {
|
437
|
+
return;
|
438
|
+
}
|
439
|
+
IodineCaller.enterGVL(iodine_pubsub_redis_callback_in_gil, &d);
|
440
|
+
IodineStore.remove(d.block);
|
441
|
+
(void)e;
|
442
|
+
}
|
443
|
+
|
444
|
+
// clang-format off
|
445
|
+
/**
|
446
|
+
Sends a Redis command. Accepts an optional block that will recieve the response.
|
447
|
+
|
448
|
+
i.e.:
|
449
|
+
|
450
|
+
REDIS_URL = "redis://redis:password@localhost:6379/"
|
451
|
+
redis = Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
|
452
|
+
Iodine::PubSub.default = redis
|
453
|
+
redis.cmd("KEYS", "*") {|result| p result
|
454
|
+
}
|
455
|
+
|
456
|
+
|
457
|
+
*/
|
458
|
+
static VALUE iodine_pubsub_redis_cmd(int argc, VALUE *argv, VALUE self) {
|
459
|
+
// clang-format on
|
460
|
+
if (argc <= 0) {
|
461
|
+
rb_raise(rb_eArgError, "Iodine::PubSub::Redis#cmd(command, ...) is missing "
|
462
|
+
"the required command argument.");
|
463
|
+
}
|
464
|
+
iodine_pubsub_s *e = iodine_pubsub_CData(self);
|
465
|
+
if (!e || !e->engine || e->engine == &e->do_not_touch) {
|
466
|
+
rb_raise(rb_eTypeError,
|
467
|
+
"Iodine::PubSub::Redis internal error - obsolete object?");
|
468
|
+
}
|
469
|
+
VALUE block = Qnil;
|
470
|
+
if (rb_block_given_p()) {
|
471
|
+
block = IodineStore.add(rb_block_proc());
|
472
|
+
}
|
473
|
+
FIOBJ data = fiobj_ary_new2((size_t)argc);
|
474
|
+
for (int i = 0; i < argc; ++i) {
|
475
|
+
switch (TYPE(argv[i])) {
|
476
|
+
case T_SYMBOL:
|
477
|
+
argv[i] = rb_sym2str(argv[i]);
|
478
|
+
/* overflow */
|
479
|
+
case T_STRING:
|
480
|
+
fiobj_ary_push(data,
|
481
|
+
fiobj_str_new(RSTRING_PTR(argv[i]), RSTRING_LEN(argv[i])));
|
482
|
+
break;
|
483
|
+
case T_FIXNUM:
|
484
|
+
fiobj_ary_push(data, fiobj_num_new(NUM2SSIZET(argv[i])));
|
485
|
+
break;
|
486
|
+
case T_FLOAT:
|
487
|
+
fiobj_ary_push(data, fiobj_float_new(rb_float_value(argv[i])));
|
488
|
+
break;
|
489
|
+
case T_NIL:
|
490
|
+
fiobj_ary_push(data, fiobj_null());
|
491
|
+
break;
|
492
|
+
case T_TRUE:
|
493
|
+
fiobj_ary_push(data, fiobj_true());
|
494
|
+
break;
|
495
|
+
case T_FALSE:
|
496
|
+
fiobj_ary_push(data, fiobj_false());
|
497
|
+
break;
|
498
|
+
default:
|
499
|
+
goto wrong_type;
|
500
|
+
}
|
501
|
+
}
|
502
|
+
if (redis_engine_send(e->engine, data, iodine_pubsub_redis_callback,
|
503
|
+
(void *)block)) {
|
504
|
+
iodine_pubsub_redis_callback(e->engine, fiobj_null(), (void *)block);
|
505
|
+
}
|
506
|
+
fiobj_free(data);
|
507
|
+
return self;
|
508
|
+
|
509
|
+
wrong_type:
|
510
|
+
fiobj_free(data);
|
511
|
+
rb_raise(rb_eArgError,
|
512
|
+
"only String, Number (with limits), Symbol, true, false and nil "
|
513
|
+
"arguments can be used.");
|
514
|
+
}
|
515
|
+
|
516
|
+
/* *****************************************************************************
|
517
|
+
Module initialization
|
518
|
+
***************************************************************************** */
|
519
|
+
|
520
|
+
/** Initializes the Connection Ruby class. */
|
521
|
+
void iodine_pubsub_init(void) {
|
522
|
+
subscribe_id = rb_intern2("subscribe", 9);
|
523
|
+
unsubscribe_id = rb_intern2("unsubscribe", 11);
|
524
|
+
publish_id = rb_intern2("publish", 7);
|
525
|
+
default_id = rb_intern2("default_engine", 14);
|
526
|
+
redis_id = rb_intern2("redis", 5);
|
527
|
+
call_id = rb_intern2("call", 4);
|
528
|
+
|
529
|
+
/* Define the PubSub module and it's methods */
|
530
|
+
|
531
|
+
VALUE PubSubModule = rb_define_module_under(IodineModule, "PubSub");
|
532
|
+
rb_define_module_function(PubSubModule, "default=", iodine_pubsub_default_set,
|
533
|
+
1);
|
534
|
+
rb_define_module_function(PubSubModule, "default", iodine_pubsub_default_get,
|
535
|
+
0);
|
536
|
+
rb_define_module_function(PubSubModule, "attach", iodine_pubsub_attach, 1);
|
537
|
+
rb_define_module_function(PubSubModule, "detach", iodine_pubsub_detach, 1);
|
538
|
+
rb_define_module_function(PubSubModule, "reset", iodine_pubsub_reset, 1);
|
539
|
+
|
540
|
+
/* Define the Engine class and it's methods */
|
541
|
+
|
542
|
+
/**
|
543
|
+
The {Iodine::PubSub::Engine} class is the parent for all engines to inherit
|
544
|
+
from.
|
545
|
+
|
546
|
+
Engines should inherit this class and override the `subscribe`, `unsubscribe`
|
547
|
+
and `publish` callbacks (which shall be called by {Iodine}).
|
548
|
+
|
549
|
+
After creation, Engines should attach themselves to Iodine using
|
550
|
+
{Iodine::PubSub.attach} or their callbacks will never get called.
|
551
|
+
|
552
|
+
Engines can also set themselves to be the default engine using
|
553
|
+
{Iodine::PubSub.default=}.
|
554
|
+
*/
|
555
|
+
EngineClass = rb_define_class_under(PubSubModule, "Engine", rb_cObject);
|
556
|
+
rb_define_alloc_func(EngineClass, iodine_pubsub_data_alloc_c);
|
557
|
+
rb_define_method(EngineClass, "subscribe", iodine_pubsub_subscribe, 2);
|
558
|
+
rb_define_method(EngineClass, "unsubscribe", iodine_pubsub_unsubscribe, 2);
|
559
|
+
rb_define_method(EngineClass, "publish", iodine_pubsub_publish, 2);
|
560
|
+
|
561
|
+
/* Define the CLUSTER and PROCESS engines */
|
562
|
+
|
563
|
+
/* CLUSTER publishes data to all the subscribers in the process cluster. */
|
564
|
+
rb_define_const(PubSubModule, "CLUSTER",
|
565
|
+
iodine_pubsub_make_C_engine(FIO_PUBSUB_CLUSTER));
|
566
|
+
/* PROCESS publishes data to all the subscribers in a single process. */
|
567
|
+
rb_define_const(PubSubModule, "PROCESS",
|
568
|
+
iodine_pubsub_make_C_engine(FIO_PUBSUB_PROCESS));
|
569
|
+
/* SIBLINGS publishes data to all the subscribers in the *other* processes
|
570
|
+
* process. */
|
571
|
+
rb_define_const(PubSubModule, "SIBLINGS",
|
572
|
+
iodine_pubsub_make_C_engine(FIO_PUBSUB_SIBLINGS));
|
573
|
+
/* PUBLISH2ROOT publishes data only to the root / master process. */
|
574
|
+
rb_define_const(PubSubModule, "PUBLISH2ROOT",
|
575
|
+
iodine_pubsub_make_C_engine(FIO_PUBSUB_ROOT));
|
576
|
+
|
577
|
+
VALUE RedisClass = rb_define_class_under(PubSubModule, "Redis", EngineClass);
|
578
|
+
rb_define_method(RedisClass, "initialize", iodine_pubsub_redis_new, -1);
|
579
|
+
rb_define_method(RedisClass, "cmd", iodine_pubsub_redis_cmd, -1);
|
580
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#ifndef H_IODINE_PUBSUB_H
|
2
|
+
#define H_IODINE_PUBSUB_H
|
3
|
+
|
4
|
+
#include "iodine.h"
|
5
|
+
|
6
|
+
#include "fio.h"
|
7
|
+
|
8
|
+
/** Initializes the PubSub::Engine Ruby class. */
|
9
|
+
void iodine_pubsub_init(void);
|
10
|
+
|
11
|
+
extern const rb_data_type_t iodine_pubsub_data_type;
|
12
|
+
|
13
|
+
typedef struct {
|
14
|
+
fio_pubsub_engine_s do_not_touch;
|
15
|
+
VALUE handler;
|
16
|
+
fio_pubsub_engine_s *engine;
|
17
|
+
void (*dealloc)(fio_pubsub_engine_s *engine);
|
18
|
+
} iodine_pubsub_s;
|
19
|
+
|
20
|
+
static inline iodine_pubsub_s *iodine_pubsub_CData(VALUE obj) {
|
21
|
+
iodine_pubsub_s *c = NULL;
|
22
|
+
TypedData_Get_Struct(obj, iodine_pubsub_s, &iodine_pubsub_data_type, c);
|
23
|
+
return c;
|
24
|
+
}
|
25
|
+
|
26
|
+
#endif
|