iodine 0.7.60 → 0.8.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.
- checksums.yaml +4 -4
- data/.clang-format +15 -0
- data/.rspec +1 -0
- data/.yardopts +1 -3
- data/CHANGELOG.md +132 -12
- data/Gemfile +5 -7
- data/LICENSE.txt +1 -1
- data/README.md +147 -671
- data/Rakefile +9 -16
- data/examples/chatroom.nru +9 -0
- data/examples/client +74 -0
- data/examples/config-hello.nru +9 -0
- data/examples/config-hello.ru +11 -0
- data/examples/config-hijack.ru +13 -0
- data/examples/config-shootout.ru +43 -0
- data/examples/config.nru +120 -0
- data/examples/config.rb +37 -0
- data/examples/config.ru +42 -53
- data/examples/fake +24 -0
- data/examples/hello_neorack +137 -0
- data/examples/routes.nru +29 -0
- data/examples/routes.ru +42 -0
- data/exe/iodine +218 -258
- data/ext/iodine/extconf.rb +27 -100
- data/ext/iodine/fio-stl.h +128336 -0
- data/ext/iodine/fio-stl.md +26396 -0
- data/ext/iodine/iodine.c +213 -1393
- data/ext/iodine/iodine.h +250 -55
- data/ext/iodine/iodine_arg_helper.h +329 -0
- data/ext/iodine/iodine_caller.h +234 -25
- data/ext/iodine/iodine_cli.h +434 -0
- data/ext/iodine/iodine_compression.h +312 -0
- data/ext/iodine/iodine_connection.h +3484 -34
- data/ext/iodine/iodine_core.h +363 -0
- data/ext/iodine/iodine_crypto.h +1441 -0
- data/ext/iodine/iodine_defer.h +225 -4
- data/ext/iodine/iodine_json.h +738 -4
- data/ext/iodine/iodine_listener.h +263 -0
- data/ext/iodine/iodine_log.h +297 -0
- data/ext/iodine/iodine_minimap.h +543 -0
- data/ext/iodine/iodine_mustache.h +461 -4
- data/ext/iodine/iodine_pubsub_eng.h +729 -0
- data/ext/iodine/iodine_pubsub_history.h +547 -0
- data/ext/iodine/iodine_pubsub_msg.h +228 -0
- data/ext/iodine/iodine_redis.h +322 -0
- data/ext/iodine/iodine_store.h +418 -16
- data/ext/iodine/iodine_threads.h +279 -0
- data/ext/iodine/iodine_tls.h +515 -6
- data/ext/iodine/iodine_utils.h +923 -0
- data/iodine.gemspec +37 -42
- data/lib/iodine/benchmark.rb +1690 -0
- data/lib/iodine/documentation.rb +3085 -0
- data/lib/iodine/version.rb +17 -1
- data/lib/iodine.rb +54 -255
- data/logo.svg +36 -0
- data/mustache.md +55 -0
- data/sig/iodine.rbs +4 -0
- metadata +58 -184
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- data/.github/workflows/ruby.yml +0 -42
- data/.gitignore +0 -21
- data/LIMITS.md +0 -41
- data/SPEC-PubSub-Draft.md +0 -159
- data/SPEC-WebSocket-Draft.md +0 -239
- data/bin/console +0 -22
- data/bin/info.md +0 -353
- data/bin/mustache_bench.rb +0 -100
- data/bin/poc/Gemfile.lock +0 -23
- data/bin/poc/README.md +0 -37
- data/bin/poc/config.ru +0 -66
- data/bin/poc/gemfile +0 -1
- data/bin/poc/www/index.html +0 -57
- data/examples/async_task.ru +0 -92
- data/examples/bates/README.md +0 -3
- data/examples/bates/config.ru +0 -342
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +0 -600
- data/examples/echo.ru +0 -59
- data/examples/etag.ru +0 -16
- data/examples/hello.ru +0 -29
- data/examples/pubsub_engine.ru +0 -81
- data/examples/rack3.ru +0 -12
- data/examples/redis.ru +0 -70
- data/examples/shootout.ru +0 -73
- data/examples/sub-protocols.ru +0 -90
- data/examples/tcp_client.rb +0 -66
- data/examples/x-sendfile.ru +0 -14
- data/ext/iodine/fio.c +0 -11995
- data/ext/iodine/fio.h +0 -6371
- data/ext/iodine/fio_cli.c +0 -431
- data/ext/iodine/fio_cli.h +0 -189
- data/ext/iodine/fio_json_parser.h +0 -688
- data/ext/iodine/fio_siphash.c +0 -157
- data/ext/iodine/fio_siphash.h +0 -37
- data/ext/iodine/fio_tls.h +0 -129
- data/ext/iodine/fio_tls_missing.c +0 -649
- data/ext/iodine/fio_tls_openssl.c +0 -1106
- data/ext/iodine/fio_tmpfile.h +0 -50
- data/ext/iodine/fiobj.h +0 -44
- data/ext/iodine/fiobj4fio.h +0 -21
- data/ext/iodine/fiobj_ary.c +0 -333
- data/ext/iodine/fiobj_ary.h +0 -139
- data/ext/iodine/fiobj_data.c +0 -1187
- data/ext/iodine/fiobj_data.h +0 -167
- data/ext/iodine/fiobj_hash.c +0 -409
- data/ext/iodine/fiobj_hash.h +0 -176
- data/ext/iodine/fiobj_json.c +0 -622
- data/ext/iodine/fiobj_json.h +0 -68
- data/ext/iodine/fiobj_mem.h +0 -71
- data/ext/iodine/fiobj_mustache.c +0 -317
- data/ext/iodine/fiobj_mustache.h +0 -62
- data/ext/iodine/fiobj_numbers.c +0 -344
- data/ext/iodine/fiobj_numbers.h +0 -127
- data/ext/iodine/fiobj_str.c +0 -433
- data/ext/iodine/fiobj_str.h +0 -172
- data/ext/iodine/fiobject.c +0 -620
- data/ext/iodine/fiobject.h +0 -655
- data/ext/iodine/hpack.h +0 -1923
- data/ext/iodine/http.c +0 -2743
- data/ext/iodine/http.h +0 -1002
- data/ext/iodine/http1.c +0 -910
- data/ext/iodine/http1.h +0 -29
- data/ext/iodine/http1_parser.h +0 -878
- data/ext/iodine/http_internal.c +0 -1279
- data/ext/iodine/http_internal.h +0 -237
- data/ext/iodine/http_mime_parser.h +0 -350
- data/ext/iodine/iodine_caller.c +0 -230
- data/ext/iodine/iodine_connection.c +0 -941
- data/ext/iodine/iodine_defer.c +0 -420
- data/ext/iodine/iodine_fiobj2rb.h +0 -120
- data/ext/iodine/iodine_helpers.c +0 -282
- data/ext/iodine/iodine_helpers.h +0 -12
- data/ext/iodine/iodine_http.c +0 -1181
- data/ext/iodine/iodine_http.h +0 -23
- data/ext/iodine/iodine_json.c +0 -302
- data/ext/iodine/iodine_mustache.c +0 -567
- data/ext/iodine/iodine_pubsub.c +0 -580
- data/ext/iodine/iodine_pubsub.h +0 -26
- data/ext/iodine/iodine_rack_io.c +0 -273
- data/ext/iodine/iodine_rack_io.h +0 -20
- data/ext/iodine/iodine_store.c +0 -148
- data/ext/iodine/iodine_tcp.c +0 -346
- data/ext/iodine/iodine_tcp.h +0 -13
- data/ext/iodine/iodine_tls.c +0 -261
- data/ext/iodine/mustache_parser.h +0 -1546
- data/ext/iodine/redis_engine.c +0 -957
- data/ext/iodine/redis_engine.h +0 -79
- data/ext/iodine/resp_parser.h +0 -317
- data/ext/iodine/websocket_parser.h +0 -506
- data/ext/iodine/websockets.c +0 -743
- data/ext/iodine/websockets.h +0 -185
- data/lib/iodine/connection.rb +0 -61
- data/lib/iodine/json.rb +0 -42
- data/lib/iodine/mustache.rb +0 -113
- data/lib/iodine/pubsub.rb +0 -55
- data/lib/iodine/rack_utils.rb +0 -43
- data/lib/iodine/tls.rb +0 -16
- data/lib/rack/handler/iodine.rb +0 -33
- data/logo.png +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#ifndef H___IODINE_PUBSUB_MSG___H
|
|
2
|
+
#define H___IODINE_PUBSUB_MSG___H
|
|
3
|
+
#include "iodine.h"
|
|
4
|
+
|
|
5
|
+
/* *****************************************************************************
|
|
6
|
+
Iodine PubSub Message - Published Message Wrapper
|
|
7
|
+
|
|
8
|
+
This module provides the Iodine::PubSub::Message Ruby class which represents
|
|
9
|
+
a message received through the pub/sub system. Message objects are passed
|
|
10
|
+
to subscription callbacks and custom engine publish handlers.
|
|
11
|
+
|
|
12
|
+
Message Properties (readable and writable):
|
|
13
|
+
- id - Unique message identifier (Integer)
|
|
14
|
+
- channel - Channel name the message was published to (String)
|
|
15
|
+
- event - Alias for channel
|
|
16
|
+
- filter - Filter value (Integer, reserved)
|
|
17
|
+
- message - The message payload (String)
|
|
18
|
+
- msg - Alias for message
|
|
19
|
+
- data - Alias for message
|
|
20
|
+
- published - Timestamp when message was published (Integer)
|
|
21
|
+
- to_s - Returns the message payload (String)
|
|
22
|
+
|
|
23
|
+
Ruby API (Iodine::PubSub::Message):
|
|
24
|
+
- message.id - Get message ID
|
|
25
|
+
- message.channel - Get channel name
|
|
26
|
+
- message.message - Get message payload
|
|
27
|
+
- message.published - Get publish timestamp
|
|
28
|
+
- message.id = val - Set message ID (for custom engines)
|
|
29
|
+
- etc.
|
|
30
|
+
***************************************************************************** */
|
|
31
|
+
|
|
32
|
+
/* *****************************************************************************
|
|
33
|
+
Ruby PubSub Message Object - Internal Types
|
|
34
|
+
***************************************************************************** */
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Enum for indexing into the message's VALUE store array.
|
|
38
|
+
* Each property is stored at a specific index for fast access.
|
|
39
|
+
*/
|
|
40
|
+
typedef enum {
|
|
41
|
+
IODINE_PUBSUB_MSG_STORE_id, /**< Message unique ID */
|
|
42
|
+
IODINE_PUBSUB_MSG_STORE_channel, /**< Channel name */
|
|
43
|
+
IODINE_PUBSUB_MSG_STORE_filter, /**< Filter value */
|
|
44
|
+
IODINE_PUBSUB_MSG_STORE_message, /**< Message payload */
|
|
45
|
+
IODINE_PUBSUB_MSG_STORE_published, /**< Publish timestamp */
|
|
46
|
+
IODINE_PUBSUB_MSG_STORE_FINISH, /**< Sentinel - array size */
|
|
47
|
+
} iodine_pubsub_msg_store_e;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Internal structure representing a PubSub message.
|
|
51
|
+
*
|
|
52
|
+
* Stores Ruby VALUE objects for each message property in an array
|
|
53
|
+
* indexed by iodine_pubsub_msg_store_e values.
|
|
54
|
+
*/
|
|
55
|
+
typedef struct iodine_pubsub_msg_wrapper_s {
|
|
56
|
+
const fio_pubsub_msg_s *msg; /**< Original C message (may be NULL) */
|
|
57
|
+
VALUE
|
|
58
|
+
store[IODINE_PUBSUB_MSG_STORE_FINISH]; /**< Ruby values for properties */
|
|
59
|
+
} iodine_pubsub_msg_wrapper_s;
|
|
60
|
+
|
|
61
|
+
static size_t iodine_pubsub_msg_data_size(const void *ptr_) {
|
|
62
|
+
iodine_pubsub_msg_wrapper_s *m = (iodine_pubsub_msg_wrapper_s *)ptr_;
|
|
63
|
+
return sizeof(*m) + (m->msg ? (sizeof(m->msg[0]) + m->msg->message.len +
|
|
64
|
+
m->msg->channel.len)
|
|
65
|
+
: 0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static void iodine_pubsub_msg_mark(void *m_) {
|
|
69
|
+
iodine_pubsub_msg_wrapper_s *m = (iodine_pubsub_msg_wrapper_s *)m_;
|
|
70
|
+
for (size_t i = 0; i < IODINE_PUBSUB_MSG_STORE_FINISH; ++i)
|
|
71
|
+
if (!IODINE_STORE_IS_SKIP(m->store[i]))
|
|
72
|
+
rb_gc_mark(m->store[i]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static void iodine_pubsub_msg_free(void *ptr_) {
|
|
76
|
+
iodine_pubsub_msg_wrapper_s *c = (iodine_pubsub_msg_wrapper_s *)ptr_;
|
|
77
|
+
ruby_xfree(c);
|
|
78
|
+
FIO_LEAK_COUNTER_ON_FREE(iodine_pubsub_msg);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static const rb_data_type_t IODINE_PUBSUB_MSG_DATA_TYPE = {
|
|
82
|
+
.wrap_struct_name = "IodinePSMessage",
|
|
83
|
+
.function =
|
|
84
|
+
{
|
|
85
|
+
.dmark = iodine_pubsub_msg_mark,
|
|
86
|
+
.dfree = iodine_pubsub_msg_free,
|
|
87
|
+
.dsize = iodine_pubsub_msg_data_size,
|
|
88
|
+
},
|
|
89
|
+
.data = NULL,
|
|
90
|
+
// .flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
static VALUE iodine_pubsub_msg_alloc(VALUE klass) {
|
|
94
|
+
iodine_pubsub_msg_wrapper_s *m =
|
|
95
|
+
(iodine_pubsub_msg_wrapper_s *)ruby_xmalloc(sizeof(*m));
|
|
96
|
+
if (!m)
|
|
97
|
+
goto no_memory;
|
|
98
|
+
*m = (iodine_pubsub_msg_wrapper_s){0};
|
|
99
|
+
for (size_t i = 0; i < IODINE_PUBSUB_MSG_STORE_FINISH; ++i)
|
|
100
|
+
m->store[i] = Qnil;
|
|
101
|
+
FIO_LEAK_COUNTER_ON_ALLOC(iodine_pubsub_msg);
|
|
102
|
+
return TypedData_Wrap_Struct(klass, &IODINE_PUBSUB_MSG_DATA_TYPE, m);
|
|
103
|
+
no_memory:
|
|
104
|
+
FIO_LOG_FATAL("Memory allocation failed");
|
|
105
|
+
fio_io_stop();
|
|
106
|
+
return Qnil;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static iodine_pubsub_msg_wrapper_s *iodine_pubsub_msg_get(VALUE self) {
|
|
110
|
+
iodine_pubsub_msg_wrapper_s *m;
|
|
111
|
+
TypedData_Get_Struct(self,
|
|
112
|
+
iodine_pubsub_msg_wrapper_s,
|
|
113
|
+
&IODINE_PUBSUB_MSG_DATA_TYPE,
|
|
114
|
+
m);
|
|
115
|
+
return m;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Creates a new Iodine::PubSub::Message Ruby object from a C message.
|
|
120
|
+
*
|
|
121
|
+
* Copies all message properties from the C struct into Ruby VALUE objects.
|
|
122
|
+
* The returned message is held in the STORE to prevent GC.
|
|
123
|
+
*
|
|
124
|
+
* @param msg The C message struct to wrap
|
|
125
|
+
* @return New Iodine::PubSub::Message VALUE
|
|
126
|
+
*/
|
|
127
|
+
static VALUE iodine_pubsub_msg_new(const fio_pubsub_msg_s *msg) {
|
|
128
|
+
VALUE m = rb_obj_alloc(iodine_rb_IODINE_PUBSUB_MSG);
|
|
129
|
+
STORE.hold(m);
|
|
130
|
+
iodine_pubsub_msg_wrapper_s *c = iodine_pubsub_msg_get(m);
|
|
131
|
+
c->store[IODINE_PUBSUB_MSG_STORE_id] = ULL2NUM(msg->id);
|
|
132
|
+
c->store[IODINE_PUBSUB_MSG_STORE_channel] =
|
|
133
|
+
(msg->channel.len ? rb_enc_str_new(msg->channel.buf,
|
|
134
|
+
msg->channel.len,
|
|
135
|
+
IodineBinaryEncoding)
|
|
136
|
+
: Qnil);
|
|
137
|
+
;
|
|
138
|
+
c->store[IODINE_PUBSUB_MSG_STORE_filter] =
|
|
139
|
+
(msg->filter ? INT2NUM(((int16_t)(msg->filter))) : Qnil);
|
|
140
|
+
c->store[IODINE_PUBSUB_MSG_STORE_message] =
|
|
141
|
+
(msg->message.len ? rb_enc_str_new(msg->message.buf,
|
|
142
|
+
msg->message.len,
|
|
143
|
+
IodineBinaryEncoding)
|
|
144
|
+
: Qnil);
|
|
145
|
+
c->store[IODINE_PUBSUB_MSG_STORE_published] =
|
|
146
|
+
(msg->timestamp ? ULL2NUM(msg->timestamp) : Qnil);
|
|
147
|
+
return m;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Macro to define getter and setter functions for message properties.
|
|
152
|
+
*
|
|
153
|
+
* Generates:
|
|
154
|
+
* - iodine_pubsub_msg_<name>_get(self) - Returns the property value
|
|
155
|
+
* - iodine_pubsub_msg_<name>_set(self, val) - Sets the property value
|
|
156
|
+
*
|
|
157
|
+
* @param val_name The property name (id, channel, filter, message, published)
|
|
158
|
+
*/
|
|
159
|
+
#define IODINE_DEF_GET_SET_FUNC(val_name, ...) \
|
|
160
|
+
/** Returns the message's val_name */ \
|
|
161
|
+
static VALUE iodine_pubsub_msg_##val_name##_get(VALUE self) { \
|
|
162
|
+
iodine_pubsub_msg_wrapper_s *c = iodine_pubsub_msg_get(self); \
|
|
163
|
+
if (!c) \
|
|
164
|
+
return Qnil; \
|
|
165
|
+
return c->store[IODINE_PUBSUB_MSG_STORE_##val_name]; \
|
|
166
|
+
} \
|
|
167
|
+
/** Sets the message's val_name */ \
|
|
168
|
+
static VALUE iodine_pubsub_msg_##val_name##_set(VALUE self, VALUE val) { \
|
|
169
|
+
iodine_pubsub_msg_wrapper_s *c = iodine_pubsub_msg_get(self); \
|
|
170
|
+
if (!c) \
|
|
171
|
+
return Qnil; \
|
|
172
|
+
return (c->store[IODINE_PUBSUB_MSG_STORE_##val_name] = val); \
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Generate getter/setter functions for all message properties */
|
|
176
|
+
IODINE_DEF_GET_SET_FUNC(id); /* message.id / message.id= */
|
|
177
|
+
IODINE_DEF_GET_SET_FUNC(channel); /* message.channel / message.channel= */
|
|
178
|
+
IODINE_DEF_GET_SET_FUNC(filter); /* message.filter / message.filter= */
|
|
179
|
+
IODINE_DEF_GET_SET_FUNC(message); /* message.message / message.message= */
|
|
180
|
+
IODINE_DEF_GET_SET_FUNC(published); /* message.published / message.published= */
|
|
181
|
+
|
|
182
|
+
#undef IODINE_DEF_GET_SET_FUNC
|
|
183
|
+
|
|
184
|
+
/* *****************************************************************************
|
|
185
|
+
Initialize - Ruby Class Registration
|
|
186
|
+
***************************************************************************** */
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Initializes the Iodine::PubSub::Message Ruby class.
|
|
190
|
+
*
|
|
191
|
+
* Defines the Message class under Iodine::PubSub with:
|
|
192
|
+
* - Getter methods: id, channel, event, filter, message, msg, data, published,
|
|
193
|
+
* to_s
|
|
194
|
+
* - Setter methods: id=, channel=, event=, filter=, message=, msg=, data=,
|
|
195
|
+
* published=
|
|
196
|
+
*
|
|
197
|
+
* Note: event/msg/data are aliases for channel/message respectively.
|
|
198
|
+
*/
|
|
199
|
+
static void Init_Iodine_PubSub_Message(void) {
|
|
200
|
+
// clang-format off
|
|
201
|
+
iodine_rb_IODINE_PUBSUB_MSG = rb_define_class_under(iodine_rb_IODINE_PUBSUB, "Message", rb_cObject);
|
|
202
|
+
STORE.hold(iodine_rb_IODINE_PUBSUB_MSG);
|
|
203
|
+
rb_define_alloc_func(iodine_rb_IODINE_PUBSUB_MSG, iodine_pubsub_msg_alloc);
|
|
204
|
+
|
|
205
|
+
/* Getter methods */
|
|
206
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "id", iodine_pubsub_msg_id_get, 0);
|
|
207
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "channel", iodine_pubsub_msg_channel_get, 0);
|
|
208
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "event", iodine_pubsub_msg_channel_get, 0);
|
|
209
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "filter", iodine_pubsub_msg_filter_get, 0);
|
|
210
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "message", iodine_pubsub_msg_message_get, 0);
|
|
211
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "msg", iodine_pubsub_msg_message_get, 0);
|
|
212
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "data", iodine_pubsub_msg_message_get, 0);
|
|
213
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "published", iodine_pubsub_msg_published_get, 0);
|
|
214
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "to_s", iodine_pubsub_msg_message_get, 0);
|
|
215
|
+
|
|
216
|
+
/* Setter methods */
|
|
217
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "id=", iodine_pubsub_msg_id_set, 1);
|
|
218
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "channel=", iodine_pubsub_msg_channel_set, 1);
|
|
219
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "event=", iodine_pubsub_msg_channel_set, 1);
|
|
220
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "filter=", iodine_pubsub_msg_filter_set, 1);
|
|
221
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "message=", iodine_pubsub_msg_message_set, 1);
|
|
222
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "msg=", iodine_pubsub_msg_message_set, 1);
|
|
223
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "data=", iodine_pubsub_msg_message_set, 1);
|
|
224
|
+
rb_define_method(iodine_rb_IODINE_PUBSUB_MSG, "published=", iodine_pubsub_msg_published_set, 1);
|
|
225
|
+
// clang-format on
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#endif /* H___IODINE_PUBSUB_MSG___H */
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#ifndef H___IODINE_REDIS___H
|
|
2
|
+
#define H___IODINE_REDIS___H
|
|
3
|
+
|
|
4
|
+
/* *****************************************************************************
|
|
5
|
+
RESP3 PubSub Engine Wrapper for Ruby
|
|
6
|
+
|
|
7
|
+
Exposes the facil.io Redis engine as Iodine::PubSub::Engine::RESP3.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
resp3 = Iodine::PubSub::Engine::RESP3.new("redis://localhost:6379/", ping: 50)
|
|
11
|
+
Iodine::PubSub.default = resp3
|
|
12
|
+
resp3.cmd("SET", "key", "value") { |result| puts result }
|
|
13
|
+
|
|
14
|
+
***************************************************************************** */
|
|
15
|
+
|
|
16
|
+
static VALUE iodine_rb_IODINE_REDIS;
|
|
17
|
+
|
|
18
|
+
/* *****************************************************************************
|
|
19
|
+
Redis Command Callback Context
|
|
20
|
+
***************************************************************************** */
|
|
21
|
+
|
|
22
|
+
typedef struct iodine_redis_cmd_ctx_s {
|
|
23
|
+
VALUE block;
|
|
24
|
+
fio_pubsub_engine_s *engine;
|
|
25
|
+
} iodine_redis_cmd_ctx_s;
|
|
26
|
+
|
|
27
|
+
/* *****************************************************************************
|
|
28
|
+
Redis Command Callback - Called from IO thread
|
|
29
|
+
***************************************************************************** */
|
|
30
|
+
|
|
31
|
+
typedef struct iodine_redis_callback_args_s {
|
|
32
|
+
VALUE block;
|
|
33
|
+
FIOBJ reply;
|
|
34
|
+
} iodine_redis_callback_args_s;
|
|
35
|
+
|
|
36
|
+
static void *iodine_redis_callback_in_gvl(void *arg_) {
|
|
37
|
+
iodine_redis_callback_args_s *args = (iodine_redis_callback_args_s *)arg_;
|
|
38
|
+
VALUE result = iodine_fiobj2ruby(args->reply);
|
|
39
|
+
STORE.hold(result);
|
|
40
|
+
iodine_ruby_call_inside(args->block, IODINE_CALL_ID, 1, &result);
|
|
41
|
+
STORE.release(result);
|
|
42
|
+
return NULL;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static void iodine_redis_cmd_callback(fio_pubsub_engine_s *e,
|
|
46
|
+
FIOBJ reply,
|
|
47
|
+
void *udata) {
|
|
48
|
+
iodine_redis_cmd_ctx_s *ctx = (iodine_redis_cmd_ctx_s *)udata;
|
|
49
|
+
if (ctx->block != Qnil) {
|
|
50
|
+
iodine_redis_callback_args_s args = {
|
|
51
|
+
.block = ctx->block,
|
|
52
|
+
.reply = reply,
|
|
53
|
+
};
|
|
54
|
+
rb_thread_call_with_gvl(iodine_redis_callback_in_gvl, &args);
|
|
55
|
+
STORE.release(ctx->block);
|
|
56
|
+
}
|
|
57
|
+
FIO_MEM_FREE(ctx, sizeof(*ctx));
|
|
58
|
+
(void)e;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* *****************************************************************************
|
|
62
|
+
Ruby Methods
|
|
63
|
+
***************************************************************************** */
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @!method initialize(url, ping: 0)
|
|
67
|
+
* Creates a new RESP3 Pub/Sub engine.
|
|
68
|
+
*
|
|
69
|
+
* @param url [String] Redis-compatible server URL (e.g., "redis://localhost:6379/")
|
|
70
|
+
* @param ping [Integer] Ping interval in seconds (0-255, default: 300)
|
|
71
|
+
* @return [Iodine::PubSub::Engine::RESP3]
|
|
72
|
+
*
|
|
73
|
+
* URL formats supported:
|
|
74
|
+
* - "redis://host:port"
|
|
75
|
+
* - "redis://user:password@host:port/db"
|
|
76
|
+
* - "host:port"
|
|
77
|
+
* - "host" (default port 6379)
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* resp3 = Iodine::PubSub::Engine::RESP3.new("redis://localhost:6379/")
|
|
81
|
+
* resp3 = Iodine::PubSub::Engine::RESP3.new("redis://secret@host:6379/", ping: 60)
|
|
82
|
+
*/
|
|
83
|
+
static VALUE iodine_redis_initialize(int argc, VALUE *argv, VALUE self) {
|
|
84
|
+
/* Get the engine struct from parent class */
|
|
85
|
+
iodine_pubsub_eng_s *e = iodine_pubsub_eng_get(self);
|
|
86
|
+
|
|
87
|
+
VALUE url_val;
|
|
88
|
+
VALUE opts = Qnil;
|
|
89
|
+
|
|
90
|
+
/* Parse arguments: url, optional keyword args */
|
|
91
|
+
rb_scan_args(argc, argv, "1:", &url_val, &opts);
|
|
92
|
+
|
|
93
|
+
/* Validate URL */
|
|
94
|
+
if (url_val == Qnil || url_val == Qfalse) {
|
|
95
|
+
rb_raise(rb_eArgError, "Redis URL is required");
|
|
96
|
+
return Qnil;
|
|
97
|
+
}
|
|
98
|
+
Check_Type(url_val, T_STRING);
|
|
99
|
+
const char *url = StringValueCStr(url_val);
|
|
100
|
+
|
|
101
|
+
/* Parse ping interval from keyword args */
|
|
102
|
+
uint8_t ping_interval = 0;
|
|
103
|
+
if (opts != Qnil) {
|
|
104
|
+
VALUE ping_val = rb_hash_aref(opts, ID2SYM(rb_intern("ping")));
|
|
105
|
+
if (ping_val != Qnil) {
|
|
106
|
+
long ping = NUM2LONG(ping_val);
|
|
107
|
+
if (ping < 0 || ping > 255) {
|
|
108
|
+
rb_raise(rb_eArgError, "ping must be between 0 and 255");
|
|
109
|
+
return Qnil;
|
|
110
|
+
}
|
|
111
|
+
ping_interval = (uint8_t)ping;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Create the Redis engine */
|
|
116
|
+
e->ptr = fio_redis_new(.url = url, .ping_interval = ping_interval);
|
|
117
|
+
if (!e->ptr) {
|
|
118
|
+
rb_raise(rb_eRuntimeError, "Failed to create RESP3 engine");
|
|
119
|
+
return Qnil;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Attach to pub/sub system */
|
|
123
|
+
fio_pubsub_engine_attach(e->ptr);
|
|
124
|
+
|
|
125
|
+
return self;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @!method connection_state
|
|
130
|
+
* Returns the RESP3 connection state.
|
|
131
|
+
*
|
|
132
|
+
* @return [Symbol] `:connecting`, `:connected`, or `:error`
|
|
133
|
+
*/
|
|
134
|
+
static VALUE iodine_redis_connection_state(VALUE self) {
|
|
135
|
+
iodine_pubsub_eng_s *e = iodine_pubsub_eng_get(self);
|
|
136
|
+
switch (fio_redis_state(e->ptr)) {
|
|
137
|
+
case FIO_REDIS_STATE_CONNECTING:
|
|
138
|
+
return ID2SYM(rb_intern("connecting"));
|
|
139
|
+
case FIO_REDIS_STATE_CONNECTED:
|
|
140
|
+
return ID2SYM(rb_intern("connected"));
|
|
141
|
+
default: return ID2SYM(rb_intern("error"));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @!method cmd(*args, &block)
|
|
147
|
+
* Sends a Redis command and optionally receives the response via callback.
|
|
148
|
+
*
|
|
149
|
+
* @param args [Array<String, Integer, Float, Symbol, Boolean, nil>] Command and arguments
|
|
150
|
+
* @yield [result] Called with the Redis response
|
|
151
|
+
* @yieldparam result [Object] The parsed Redis response
|
|
152
|
+
* @return [Boolean] true on success, false on error
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* redis.cmd("SET", "key", "value") { |r| puts "SET result: #{r}" }
|
|
156
|
+
* redis.cmd("GET", "key") { |value| puts "Value: #{value}" }
|
|
157
|
+
* redis.cmd("KEYS", "*") { |keys| p keys }
|
|
158
|
+
* redis.cmd("INCR", "counter") { |new_val| puts new_val }
|
|
159
|
+
*
|
|
160
|
+
* @note Do NOT use SUBSCRIBE/PSUBSCRIBE/UNSUBSCRIBE/PUNSUBSCRIBE commands.
|
|
161
|
+
* These are handled internally by the pub/sub system.
|
|
162
|
+
*/
|
|
163
|
+
static VALUE iodine_redis_cmd(int argc, VALUE *argv, VALUE self) {
|
|
164
|
+
iodine_pubsub_eng_s *e = iodine_pubsub_eng_get(self);
|
|
165
|
+
|
|
166
|
+
if (!e->ptr) {
|
|
167
|
+
rb_raise(rb_eRuntimeError, "RESP3 engine not initialized");
|
|
168
|
+
return Qfalse;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (argc < 1) {
|
|
172
|
+
rb_raise(rb_eArgError, "At least one argument (command) is required");
|
|
173
|
+
return Qfalse;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Build FIOBJ array from Ruby arguments */
|
|
177
|
+
FIOBJ cmd = fiobj_array_new();
|
|
178
|
+
fiobj_array_reserve(cmd, argc);
|
|
179
|
+
|
|
180
|
+
for (int i = 0; i < argc; ++i) {
|
|
181
|
+
VALUE arg = argv[i];
|
|
182
|
+
FIOBJ item = FIOBJ_INVALID;
|
|
183
|
+
|
|
184
|
+
switch (rb_type(arg)) {
|
|
185
|
+
case RUBY_T_STRING:
|
|
186
|
+
item = fiobj_str_new_cstr(RSTRING_PTR(arg), RSTRING_LEN(arg));
|
|
187
|
+
break;
|
|
188
|
+
case RUBY_T_SYMBOL:
|
|
189
|
+
arg = rb_sym2str(arg);
|
|
190
|
+
item = fiobj_str_new_cstr(RSTRING_PTR(arg), RSTRING_LEN(arg));
|
|
191
|
+
break;
|
|
192
|
+
case RUBY_T_FIXNUM:
|
|
193
|
+
item = fiobj_num_new((intptr_t)NUM2LL(arg));
|
|
194
|
+
break;
|
|
195
|
+
case RUBY_T_FLOAT: item = fiobj_float_new(RFLOAT_VALUE(arg)); break;
|
|
196
|
+
case RUBY_T_TRUE: item = fiobj_str_new_cstr("1", 1); break;
|
|
197
|
+
case RUBY_T_FALSE: item = fiobj_str_new_cstr("0", 1); break;
|
|
198
|
+
case RUBY_T_NIL: item = fiobj_null(); break;
|
|
199
|
+
default:
|
|
200
|
+
/* Try to convert to string */
|
|
201
|
+
arg = rb_funcallv(arg, IODINE_TO_S_ID, 0, NULL);
|
|
202
|
+
if (RB_TYPE_P(arg, RUBY_T_STRING)) {
|
|
203
|
+
item = fiobj_str_new_cstr(RSTRING_PTR(arg), RSTRING_LEN(arg));
|
|
204
|
+
} else {
|
|
205
|
+
fiobj_free(cmd);
|
|
206
|
+
rb_raise(rb_eTypeError,
|
|
207
|
+
"Cannot convert argument %d to Redis command element",
|
|
208
|
+
i);
|
|
209
|
+
return Qfalse;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
fiobj_array_push(cmd, item);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Get the block if provided */
|
|
216
|
+
VALUE block = Qnil;
|
|
217
|
+
if (rb_block_given_p()) {
|
|
218
|
+
block = rb_block_proc();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Prepare callback context */
|
|
222
|
+
iodine_redis_cmd_ctx_s *ctx = NULL;
|
|
223
|
+
void (*callback)(fio_pubsub_engine_s *, FIOBJ, void *) = NULL;
|
|
224
|
+
|
|
225
|
+
if (block != Qnil) {
|
|
226
|
+
ctx =
|
|
227
|
+
(iodine_redis_cmd_ctx_s *)FIO_MEM_REALLOC(NULL, 0, sizeof(*ctx), 0);
|
|
228
|
+
if (!ctx) {
|
|
229
|
+
fiobj_free(cmd);
|
|
230
|
+
rb_raise(rb_eNoMemError, "Failed to allocate callback context");
|
|
231
|
+
return Qfalse;
|
|
232
|
+
}
|
|
233
|
+
ctx->block = block;
|
|
234
|
+
ctx->engine = e->ptr;
|
|
235
|
+
STORE.hold(block);
|
|
236
|
+
callback = iodine_redis_cmd_callback;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Send the command */
|
|
240
|
+
int result = fio_redis_send(e->ptr, cmd, callback, ctx);
|
|
241
|
+
fiobj_free(cmd);
|
|
242
|
+
|
|
243
|
+
if (result != 0) {
|
|
244
|
+
if (ctx) {
|
|
245
|
+
STORE.release(block);
|
|
246
|
+
FIO_MEM_FREE(ctx, sizeof(*ctx));
|
|
247
|
+
}
|
|
248
|
+
return Qfalse;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return Qtrue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* *****************************************************************************
|
|
255
|
+
Custom dealloc for Redis - need to call fio_redis_free instead of default
|
|
256
|
+
***************************************************************************** */
|
|
257
|
+
|
|
258
|
+
static void iodine_redis_free(void *ptr_) {
|
|
259
|
+
iodine_pubsub_eng_s *e = (iodine_pubsub_eng_s *)ptr_;
|
|
260
|
+
if (e->ptr) {
|
|
261
|
+
fio_pubsub_engine_detach(e->ptr);
|
|
262
|
+
fio_redis_free(e->ptr);
|
|
263
|
+
e->ptr = NULL;
|
|
264
|
+
}
|
|
265
|
+
ruby_xfree(e);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static const rb_data_type_t IODINE_REDIS_DATA_TYPE = {
|
|
269
|
+
.wrap_struct_name = "IodineRedis",
|
|
270
|
+
.function =
|
|
271
|
+
{
|
|
272
|
+
.dfree = iodine_redis_free,
|
|
273
|
+
.dsize = iodine_pubsub_eng_data_size,
|
|
274
|
+
},
|
|
275
|
+
.parent = &IODINE_PUBSUB_ENG_DATA_TYPE,
|
|
276
|
+
.data = NULL,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
static VALUE iodine_redis_alloc(VALUE klass) {
|
|
280
|
+
iodine_pubsub_eng_s *e =
|
|
281
|
+
(iodine_pubsub_eng_s *)ruby_xmalloc(sizeof(iodine_pubsub_eng_s));
|
|
282
|
+
if (!e)
|
|
283
|
+
goto no_memory;
|
|
284
|
+
*e = (iodine_pubsub_eng_s){0};
|
|
285
|
+
return TypedData_Wrap_Struct(klass, &IODINE_REDIS_DATA_TYPE, e);
|
|
286
|
+
|
|
287
|
+
no_memory:
|
|
288
|
+
FIO_LOG_FATAL("Memory allocation failed for Redis engine");
|
|
289
|
+
fio_io_stop();
|
|
290
|
+
return Qnil;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* *****************************************************************************
|
|
294
|
+
Initialization
|
|
295
|
+
***************************************************************************** */
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Initializes the Iodine::PubSub::Engine::RESP3 class.
|
|
299
|
+
*/
|
|
300
|
+
static void Init_Iodine_RESP3(void) {
|
|
301
|
+
/* Define Iodine::PubSub::Engine::RESP3 as a subclass of Engine. */
|
|
302
|
+
iodine_rb_IODINE_REDIS = rb_define_class_under(iodine_rb_IODINE_PUBSUB_ENG,
|
|
303
|
+
"RESP3",
|
|
304
|
+
iodine_rb_IODINE_PUBSUB_ENG);
|
|
305
|
+
STORE.hold(iodine_rb_IODINE_REDIS);
|
|
306
|
+
|
|
307
|
+
/* Set up allocation function */
|
|
308
|
+
rb_define_alloc_func(iodine_rb_IODINE_REDIS, iodine_redis_alloc);
|
|
309
|
+
|
|
310
|
+
/* Define instance methods */
|
|
311
|
+
rb_define_method(iodine_rb_IODINE_REDIS,
|
|
312
|
+
"initialize",
|
|
313
|
+
iodine_redis_initialize,
|
|
314
|
+
-1);
|
|
315
|
+
rb_define_method(iodine_rb_IODINE_REDIS,
|
|
316
|
+
"connection_state",
|
|
317
|
+
iodine_redis_connection_state,
|
|
318
|
+
0);
|
|
319
|
+
rb_define_method(iodine_rb_IODINE_REDIS, "cmd", iodine_redis_cmd, -1);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#endif /* H___IODINE_REDIS___H */
|