iodine 0.7.59 → 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 -20
- 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 -146
- 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,547 @@
|
|
|
1
|
+
#ifndef H___IODINE_PUBSUB_HISTORY___H
|
|
2
|
+
#define H___IODINE_PUBSUB_HISTORY___H
|
|
3
|
+
#include "iodine.h"
|
|
4
|
+
|
|
5
|
+
/* *****************************************************************************
|
|
6
|
+
Iodine PubSub History - Message History and Replay Support
|
|
7
|
+
|
|
8
|
+
This module provides the Iodine::PubSub::History Ruby module which allows
|
|
9
|
+
enabling message history caching and replay for the pub/sub system.
|
|
10
|
+
|
|
11
|
+
When history is enabled, published messages are cached in memory (up to a
|
|
12
|
+
configurable size limit). Subscribers can then request replay of missed
|
|
13
|
+
messages by providing a `since` timestamp when subscribing.
|
|
14
|
+
|
|
15
|
+
Ruby API:
|
|
16
|
+
|
|
17
|
+
Iodine::PubSub::History.cache(size_limit: 256 * 1024 * 1024)
|
|
18
|
+
- Enables the built-in in-memory history cache
|
|
19
|
+
- size_limit: Maximum cache size in bytes (default: 256MB)
|
|
20
|
+
- Returns true on success
|
|
21
|
+
- Memory cache has highest priority (255) for fastest replay
|
|
22
|
+
|
|
23
|
+
Iodine::PubSub::History.cache?
|
|
24
|
+
- Returns true if memory caching is enabled
|
|
25
|
+
|
|
26
|
+
Usage Example:
|
|
27
|
+
|
|
28
|
+
# Enable memory cache with 128MB limit
|
|
29
|
+
Iodine::PubSub::History.cache(size_limit: 128 * 1024 * 1024)
|
|
30
|
+
|
|
31
|
+
# Subscribe with history replay (get messages from last 60 seconds)
|
|
32
|
+
since_ms = (Time.now.to_i - 60) * 1000
|
|
33
|
+
Iodine.subscribe(channel: "chat", since: since_ms) do |msg|
|
|
34
|
+
puts "Message: #{msg.message}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Custom History Managers:
|
|
38
|
+
|
|
39
|
+
For advanced use cases (e.g., persistent storage, Redis-backed history),
|
|
40
|
+
you can create custom history managers by subclassing
|
|
41
|
+
Iodine::PubSub::History::Manager and implementing:
|
|
42
|
+
|
|
43
|
+
- push(message) - Store a message in history
|
|
44
|
+
- replay(channel:, filter:, since:, &block) - Replay messages
|
|
45
|
+
- oldest(channel:, filter:) - Get oldest available timestamp
|
|
46
|
+
|
|
47
|
+
***************************************************************************** */
|
|
48
|
+
|
|
49
|
+
/* Ruby class for Iodine::PubSub::History module */
|
|
50
|
+
static VALUE iodine_rb_IODINE_PUBSUB_HISTORY;
|
|
51
|
+
|
|
52
|
+
/* Track if built-in cache is enabled */
|
|
53
|
+
static uint8_t iodine_pubsub_history_cache_enabled = 0;
|
|
54
|
+
|
|
55
|
+
/* *****************************************************************************
|
|
56
|
+
Ruby Methods - History API
|
|
57
|
+
***************************************************************************** */
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Enables the built-in in-memory history cache.
|
|
61
|
+
*
|
|
62
|
+
* @param argc Argument count
|
|
63
|
+
* @param argv Arguments array
|
|
64
|
+
* @param self The History module
|
|
65
|
+
* @return Qtrue on success
|
|
66
|
+
*
|
|
67
|
+
* Ruby: Iodine::PubSub::History.cache(size_limit: 256 * 1024 * 1024)
|
|
68
|
+
*/
|
|
69
|
+
static VALUE iodine_pubsub_history_cache(int argc, VALUE *argv, VALUE self) {
|
|
70
|
+
size_t size_limit = 0;
|
|
71
|
+
|
|
72
|
+
iodine_rb2c_arg(argc,
|
|
73
|
+
argv,
|
|
74
|
+
IODINE_ARG_SIZE_T(size_limit, 0, "size_limit", 0));
|
|
75
|
+
|
|
76
|
+
/* Get the built-in cache manager (initializes with size_limit) */
|
|
77
|
+
const fio_pubsub_history_s *mgr = fio_pubsub_history_cache(size_limit);
|
|
78
|
+
if (!mgr) {
|
|
79
|
+
rb_raise(rb_eRuntimeError, "Failed to initialize history cache");
|
|
80
|
+
return Qfalse;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Attach with highest priority (255) - memory cache should be fastest */
|
|
84
|
+
if (fio_pubsub_history_attach(mgr, 255) != 0) {
|
|
85
|
+
rb_raise(rb_eRuntimeError, "Failed to attach history cache");
|
|
86
|
+
return Qfalse;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
iodine_pubsub_history_cache_enabled = 1;
|
|
90
|
+
return Qtrue;
|
|
91
|
+
(void)self;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns true if the built-in memory cache is enabled.
|
|
96
|
+
*
|
|
97
|
+
* @param self The History module
|
|
98
|
+
* @return Qtrue if cache is enabled, Qfalse otherwise
|
|
99
|
+
*
|
|
100
|
+
* Ruby: Iodine::PubSub::History.cache?
|
|
101
|
+
*/
|
|
102
|
+
static VALUE iodine_pubsub_history_cache_p(VALUE self) {
|
|
103
|
+
return iodine_pubsub_history_cache_enabled ? Qtrue : Qfalse;
|
|
104
|
+
(void)self;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* *****************************************************************************
|
|
108
|
+
Custom History Manager - Ruby TypedData Wrapper
|
|
109
|
+
|
|
110
|
+
Allows Ruby subclasses to implement custom history storage backends.
|
|
111
|
+
***************************************************************************** */
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Internal structure representing a custom PubSub history manager.
|
|
115
|
+
*
|
|
116
|
+
* Wraps a facil.io pubsub history manager with a Ruby handler object that
|
|
117
|
+
* receives callbacks for push/replay/oldest operations.
|
|
118
|
+
*/
|
|
119
|
+
typedef struct iodine_pubsub_history_s {
|
|
120
|
+
fio_pubsub_history_s manager; /**< The facil.io history manager callbacks */
|
|
121
|
+
VALUE handler; /**< Ruby handler object (self) */
|
|
122
|
+
uint8_t priority; /**< Manager priority (0-255) */
|
|
123
|
+
uint8_t attached; /**< Whether manager is attached */
|
|
124
|
+
} iodine_pubsub_history_s;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Arguments passed to GVL-wrapped callback functions.
|
|
128
|
+
*/
|
|
129
|
+
typedef struct iodine_pubsub_history___args_s {
|
|
130
|
+
iodine_pubsub_history_s *hist; /**< The history manager */
|
|
131
|
+
fio_pubsub_msg_s *msg; /**< Message for push callbacks */
|
|
132
|
+
fio_buf_info_s channel; /**< Channel name */
|
|
133
|
+
int16_t filter; /**< Filter value */
|
|
134
|
+
uint64_t since; /**< Timestamp for replay */
|
|
135
|
+
void (*on_message)(fio_pubsub_msg_s *msg, void *udata);
|
|
136
|
+
void (*on_done)(void *udata);
|
|
137
|
+
void *udata;
|
|
138
|
+
int result; /**< Return value from callback */
|
|
139
|
+
uint64_t oldest_result; /**< Return value for oldest() */
|
|
140
|
+
} iodine_pubsub_history___args_s;
|
|
141
|
+
|
|
142
|
+
/* Forward declarations */
|
|
143
|
+
static void iodine_pubsub_history___detached(const fio_pubsub_history_s *hist);
|
|
144
|
+
static int iodine_pubsub_history___push(const fio_pubsub_history_s *hist,
|
|
145
|
+
fio_pubsub_msg_s *msg);
|
|
146
|
+
static int iodine_pubsub_history___replay(
|
|
147
|
+
const fio_pubsub_history_s *hist,
|
|
148
|
+
fio_buf_info_s channel,
|
|
149
|
+
int16_t filter,
|
|
150
|
+
uint64_t since,
|
|
151
|
+
void (*on_message)(fio_pubsub_msg_s *msg, void *udata),
|
|
152
|
+
void (*on_done)(void *udata),
|
|
153
|
+
void *udata);
|
|
154
|
+
static uint64_t iodine_pubsub_history___oldest(const fio_pubsub_history_s *hist,
|
|
155
|
+
fio_buf_info_s channel,
|
|
156
|
+
int16_t filter);
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Called after the history manager was detached.
|
|
160
|
+
* Invokes the Ruby handler's `on_cleanup` method for resource cleanup.
|
|
161
|
+
*/
|
|
162
|
+
static void iodine_pubsub_history___detached(const fio_pubsub_history_s *hist) {
|
|
163
|
+
iodine_pubsub_history_s *h = (iodine_pubsub_history_s *)hist;
|
|
164
|
+
h->attached = 0;
|
|
165
|
+
iodine_ruby_call_anywhere(h->handler, rb_intern("on_cleanup"));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static void *iodine_pubsub_history___push__in_GC(void *a_) {
|
|
169
|
+
iodine_pubsub_history___args_s *args = (iodine_pubsub_history___args_s *)a_;
|
|
170
|
+
VALUE msg = iodine_pubsub_msg_new(args->msg);
|
|
171
|
+
iodine_caller_result_s r =
|
|
172
|
+
iodine_ruby_call_inside(args->hist->handler, rb_intern("push"), 1, &msg);
|
|
173
|
+
STORE.release(msg);
|
|
174
|
+
args->result = (r.exception || r.result == Qfalse) ? -1 : 0;
|
|
175
|
+
return NULL;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Stores a message in history.
|
|
180
|
+
* Invokes the Ruby handler's `push` method with the message.
|
|
181
|
+
*/
|
|
182
|
+
static int iodine_pubsub_history___push(const fio_pubsub_history_s *hist,
|
|
183
|
+
fio_pubsub_msg_s *msg) {
|
|
184
|
+
iodine_pubsub_history___args_s args = {
|
|
185
|
+
.hist = (iodine_pubsub_history_s *)hist,
|
|
186
|
+
.msg = msg,
|
|
187
|
+
.result = -1,
|
|
188
|
+
};
|
|
189
|
+
iodine_c_call_with(iodine_pubsub_history___push__in_GC, &args);
|
|
190
|
+
return args.result;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Replay callback wrapper - called for each message during replay */
|
|
194
|
+
typedef struct {
|
|
195
|
+
VALUE callback;
|
|
196
|
+
void (*on_message)(fio_pubsub_msg_s *msg, void *udata);
|
|
197
|
+
void (*on_done)(void *udata);
|
|
198
|
+
void *udata;
|
|
199
|
+
} iodine_pubsub_history_replay_ctx_s;
|
|
200
|
+
|
|
201
|
+
static VALUE iodine_pubsub_history___replay_each(VALUE msg_rb, VALUE ctx_) {
|
|
202
|
+
iodine_pubsub_history_replay_ctx_s *ctx =
|
|
203
|
+
(iodine_pubsub_history_replay_ctx_s *)ctx_;
|
|
204
|
+
/* Convert Ruby message back to C message for the callback */
|
|
205
|
+
iodine_pubsub_msg_wrapper_s *wrapper = iodine_pubsub_msg_get(msg_rb);
|
|
206
|
+
if (wrapper && ctx->on_message) {
|
|
207
|
+
/* Create a temporary message struct from the Ruby wrapper */
|
|
208
|
+
fio_pubsub_msg_s msg = {
|
|
209
|
+
.id = wrapper->store[IODINE_PUBSUB_MSG_STORE_id] != Qnil
|
|
210
|
+
? NUM2ULL(wrapper->store[IODINE_PUBSUB_MSG_STORE_id])
|
|
211
|
+
: 0,
|
|
212
|
+
.timestamp =
|
|
213
|
+
wrapper->store[IODINE_PUBSUB_MSG_STORE_published] != Qnil
|
|
214
|
+
? NUM2ULL(wrapper->store[IODINE_PUBSUB_MSG_STORE_published])
|
|
215
|
+
: 0,
|
|
216
|
+
.filter = wrapper->store[IODINE_PUBSUB_MSG_STORE_filter] != Qnil
|
|
217
|
+
? (int16_t)NUM2INT(
|
|
218
|
+
wrapper->store[IODINE_PUBSUB_MSG_STORE_filter])
|
|
219
|
+
: 0,
|
|
220
|
+
};
|
|
221
|
+
if (wrapper->store[IODINE_PUBSUB_MSG_STORE_channel] != Qnil) {
|
|
222
|
+
msg.channel = FIO_BUF_INFO2(
|
|
223
|
+
RSTRING_PTR(wrapper->store[IODINE_PUBSUB_MSG_STORE_channel]),
|
|
224
|
+
(size_t)RSTRING_LEN(wrapper->store[IODINE_PUBSUB_MSG_STORE_channel]));
|
|
225
|
+
}
|
|
226
|
+
if (wrapper->store[IODINE_PUBSUB_MSG_STORE_message] != Qnil) {
|
|
227
|
+
msg.message = FIO_BUF_INFO2(
|
|
228
|
+
RSTRING_PTR(wrapper->store[IODINE_PUBSUB_MSG_STORE_message]),
|
|
229
|
+
(size_t)RSTRING_LEN(wrapper->store[IODINE_PUBSUB_MSG_STORE_message]));
|
|
230
|
+
}
|
|
231
|
+
ctx->on_message(&msg, ctx->udata);
|
|
232
|
+
}
|
|
233
|
+
return Qnil;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
static void *iodine_pubsub_history___replay__in_GC(void *a_) {
|
|
237
|
+
iodine_pubsub_history___args_s *args = (iodine_pubsub_history___args_s *)a_;
|
|
238
|
+
VALUE argv[3];
|
|
239
|
+
argv[0] = args->channel.len ? rb_str_new(args->channel.buf, args->channel.len)
|
|
240
|
+
: Qnil;
|
|
241
|
+
argv[1] = INT2NUM(args->filter);
|
|
242
|
+
argv[2] = ULL2NUM(args->since);
|
|
243
|
+
|
|
244
|
+
/* Create a context for the replay iteration */
|
|
245
|
+
iodine_pubsub_history_replay_ctx_s ctx = {
|
|
246
|
+
.on_message = args->on_message,
|
|
247
|
+
.on_done = args->on_done,
|
|
248
|
+
.udata = args->udata,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/* Call Ruby replay method with named args */
|
|
252
|
+
VALUE kwargs = rb_hash_new();
|
|
253
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("channel")), argv[0]);
|
|
254
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("filter")), argv[1]);
|
|
255
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("since")), argv[2]);
|
|
256
|
+
|
|
257
|
+
VALUE rb_args[1] = {kwargs};
|
|
258
|
+
iodine_caller_result_s r = iodine_ruby_call_inside(args->hist->handler,
|
|
259
|
+
rb_intern("replay"),
|
|
260
|
+
1,
|
|
261
|
+
rb_args);
|
|
262
|
+
|
|
263
|
+
if (r.exception) {
|
|
264
|
+
args->result = -1;
|
|
265
|
+
} else if (r.result == Qnil || r.result == Qfalse) {
|
|
266
|
+
args->result = -1;
|
|
267
|
+
} else if (RB_TYPE_P(r.result, RUBY_T_ARRAY)) {
|
|
268
|
+
/* Iterate over returned messages */
|
|
269
|
+
for (long i = 0; i < RARRAY_LEN(r.result); ++i) {
|
|
270
|
+
iodine_pubsub_history___replay_each(RARRAY_AREF(r.result, i),
|
|
271
|
+
(VALUE)&ctx);
|
|
272
|
+
}
|
|
273
|
+
args->result = 0;
|
|
274
|
+
} else {
|
|
275
|
+
args->result = 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* Always call on_done */
|
|
279
|
+
if (args->on_done)
|
|
280
|
+
args->on_done(args->udata);
|
|
281
|
+
|
|
282
|
+
return NULL;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Replays messages since a timestamp.
|
|
287
|
+
* Invokes the Ruby handler's `replay` method.
|
|
288
|
+
*/
|
|
289
|
+
static int iodine_pubsub_history___replay(
|
|
290
|
+
const fio_pubsub_history_s *hist,
|
|
291
|
+
fio_buf_info_s channel,
|
|
292
|
+
int16_t filter,
|
|
293
|
+
uint64_t since,
|
|
294
|
+
void (*on_message)(fio_pubsub_msg_s *msg, void *udata),
|
|
295
|
+
void (*on_done)(void *udata),
|
|
296
|
+
void *udata) {
|
|
297
|
+
iodine_pubsub_history___args_s args = {
|
|
298
|
+
.hist = (iodine_pubsub_history_s *)hist,
|
|
299
|
+
.channel = channel,
|
|
300
|
+
.filter = filter,
|
|
301
|
+
.since = since,
|
|
302
|
+
.on_message = on_message,
|
|
303
|
+
.on_done = on_done,
|
|
304
|
+
.udata = udata,
|
|
305
|
+
.result = -1,
|
|
306
|
+
};
|
|
307
|
+
iodine_c_call_with(iodine_pubsub_history___replay__in_GC, &args);
|
|
308
|
+
return args.result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
static void *iodine_pubsub_history___oldest__in_GC(void *a_) {
|
|
312
|
+
iodine_pubsub_history___args_s *args = (iodine_pubsub_history___args_s *)a_;
|
|
313
|
+
VALUE argv[2];
|
|
314
|
+
argv[0] = args->channel.len ? rb_str_new(args->channel.buf, args->channel.len)
|
|
315
|
+
: Qnil;
|
|
316
|
+
argv[1] = INT2NUM(args->filter);
|
|
317
|
+
|
|
318
|
+
VALUE kwargs = rb_hash_new();
|
|
319
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("channel")), argv[0]);
|
|
320
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("filter")), argv[1]);
|
|
321
|
+
|
|
322
|
+
VALUE rb_args[1] = {kwargs};
|
|
323
|
+
iodine_caller_result_s r = iodine_ruby_call_inside(args->hist->handler,
|
|
324
|
+
rb_intern("oldest"),
|
|
325
|
+
1,
|
|
326
|
+
rb_args);
|
|
327
|
+
|
|
328
|
+
if (r.exception || r.result == Qnil) {
|
|
329
|
+
args->oldest_result = UINT64_MAX;
|
|
330
|
+
} else if (RB_INTEGER_TYPE_P(r.result)) {
|
|
331
|
+
/* millisecond timestamps are Bignums on Windows; never raise here */
|
|
332
|
+
uint64_t u = UINT64_MAX;
|
|
333
|
+
int sign = rb_integer_pack(
|
|
334
|
+
r.result,
|
|
335
|
+
&u,
|
|
336
|
+
1,
|
|
337
|
+
sizeof(u),
|
|
338
|
+
0,
|
|
339
|
+
INTEGER_PACK_LSWORD_FIRST | INTEGER_PACK_NATIVE_BYTE_ORDER);
|
|
340
|
+
args->oldest_result = (sign == 0 || sign == 1) ? u : UINT64_MAX;
|
|
341
|
+
} else {
|
|
342
|
+
args->oldest_result = UINT64_MAX;
|
|
343
|
+
}
|
|
344
|
+
return NULL;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Gets the oldest available timestamp for a channel.
|
|
349
|
+
* Invokes the Ruby handler's `oldest` method.
|
|
350
|
+
*/
|
|
351
|
+
static uint64_t iodine_pubsub_history___oldest(const fio_pubsub_history_s *hist,
|
|
352
|
+
fio_buf_info_s channel,
|
|
353
|
+
int16_t filter) {
|
|
354
|
+
iodine_pubsub_history___args_s args = {
|
|
355
|
+
.hist = (iodine_pubsub_history_s *)hist,
|
|
356
|
+
.channel = channel,
|
|
357
|
+
.filter = filter,
|
|
358
|
+
.oldest_result = UINT64_MAX,
|
|
359
|
+
};
|
|
360
|
+
iodine_c_call_with(iodine_pubsub_history___oldest__in_GC, &args);
|
|
361
|
+
return args.oldest_result;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Validates a Ruby object and creates a history manager struct.
|
|
366
|
+
*/
|
|
367
|
+
static fio_pubsub_history_s iodine_pubsub___history_validate(VALUE obj) {
|
|
368
|
+
fio_pubsub_history_s r = {
|
|
369
|
+
.detached = (rb_respond_to(obj, rb_intern("on_cleanup")))
|
|
370
|
+
? iodine_pubsub_history___detached
|
|
371
|
+
: NULL,
|
|
372
|
+
.push = (rb_respond_to(obj, rb_intern("push")))
|
|
373
|
+
? iodine_pubsub_history___push
|
|
374
|
+
: NULL,
|
|
375
|
+
.replay = (rb_respond_to(obj, rb_intern("replay")))
|
|
376
|
+
? iodine_pubsub_history___replay
|
|
377
|
+
: NULL,
|
|
378
|
+
.oldest = (rb_respond_to(obj, rb_intern("oldest")))
|
|
379
|
+
? iodine_pubsub_history___oldest
|
|
380
|
+
: NULL,
|
|
381
|
+
};
|
|
382
|
+
return r;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* *****************************************************************************
|
|
386
|
+
Ruby History Manager Object - Ruby TypedData Wrapper
|
|
387
|
+
***************************************************************************** */
|
|
388
|
+
|
|
389
|
+
static size_t iodine_pubsub_history_data_size(const void *ptr_) {
|
|
390
|
+
iodine_pubsub_history_s *h = (iodine_pubsub_history_s *)ptr_;
|
|
391
|
+
return sizeof(*h);
|
|
392
|
+
(void)h;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
static void iodine_pubsub_history_free(void *ptr_) {
|
|
396
|
+
iodine_pubsub_history_s *h = (iodine_pubsub_history_s *)ptr_;
|
|
397
|
+
if (h->attached)
|
|
398
|
+
fio_pubsub_history_detach(&h->manager);
|
|
399
|
+
ruby_xfree(h);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
static const rb_data_type_t IODINE_PUBSUB_HISTORY_DATA_TYPE = {
|
|
403
|
+
.wrap_struct_name = "IodinePSHistory",
|
|
404
|
+
.function =
|
|
405
|
+
{
|
|
406
|
+
.dfree = iodine_pubsub_history_free,
|
|
407
|
+
.dsize = iodine_pubsub_history_data_size,
|
|
408
|
+
},
|
|
409
|
+
.data = NULL,
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
static VALUE iodine_pubsub_history_alloc(VALUE klass) {
|
|
413
|
+
iodine_pubsub_history_s *h =
|
|
414
|
+
(iodine_pubsub_history_s *)ruby_xmalloc(sizeof(*h));
|
|
415
|
+
if (!h)
|
|
416
|
+
goto no_memory;
|
|
417
|
+
*h = (iodine_pubsub_history_s){0};
|
|
418
|
+
h->handler =
|
|
419
|
+
TypedData_Wrap_Struct(klass, &IODINE_PUBSUB_HISTORY_DATA_TYPE, h);
|
|
420
|
+
h->manager = iodine_pubsub___history_validate(h->handler);
|
|
421
|
+
return h->handler;
|
|
422
|
+
|
|
423
|
+
no_memory:
|
|
424
|
+
FIO_LOG_FATAL("Memory allocation failed");
|
|
425
|
+
fio_io_stop();
|
|
426
|
+
return Qnil;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
static iodine_pubsub_history_s *iodine_pubsub_history_get(VALUE self) {
|
|
430
|
+
iodine_pubsub_history_s *h;
|
|
431
|
+
TypedData_Get_Struct(self,
|
|
432
|
+
iodine_pubsub_history_s,
|
|
433
|
+
&IODINE_PUBSUB_HISTORY_DATA_TYPE,
|
|
434
|
+
h);
|
|
435
|
+
return h;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* *****************************************************************************
|
|
439
|
+
Ruby Methods - Manager API
|
|
440
|
+
***************************************************************************** */
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Initializes a new History manager and attaches it to the pubsub system.
|
|
444
|
+
*
|
|
445
|
+
* @param argc Argument count
|
|
446
|
+
* @param argv Arguments (priority: 0-255, default 128)
|
|
447
|
+
* @param self The Manager instance
|
|
448
|
+
* @return self
|
|
449
|
+
*
|
|
450
|
+
* Ruby: manager = Iodine::PubSub::History::Manager.new(priority: 128)
|
|
451
|
+
*/
|
|
452
|
+
static VALUE iodine_pubsub_history_manager_initialize(int argc,
|
|
453
|
+
VALUE *argv,
|
|
454
|
+
VALUE self) {
|
|
455
|
+
iodine_pubsub_history_s *h = iodine_pubsub_history_get(self);
|
|
456
|
+
uint8_t priority = 128;
|
|
457
|
+
|
|
458
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_U8(priority, 0, "priority", 0));
|
|
459
|
+
|
|
460
|
+
h->priority = priority;
|
|
461
|
+
if (fio_pubsub_history_attach(&h->manager, priority) != 0) {
|
|
462
|
+
rb_raise(rb_eRuntimeError, "Failed to attach history manager");
|
|
463
|
+
return Qnil;
|
|
464
|
+
}
|
|
465
|
+
h->attached = 1;
|
|
466
|
+
return self;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Detaches the history manager from the pubsub system.
|
|
471
|
+
*
|
|
472
|
+
* @param self The Manager instance
|
|
473
|
+
* @return self
|
|
474
|
+
*
|
|
475
|
+
* Ruby: manager.detach
|
|
476
|
+
*/
|
|
477
|
+
static VALUE iodine_pubsub_history_manager_detach(VALUE self) {
|
|
478
|
+
iodine_pubsub_history_s *h = iodine_pubsub_history_get(self);
|
|
479
|
+
if (h->attached) {
|
|
480
|
+
fio_pubsub_history_detach(&h->manager);
|
|
481
|
+
h->attached = 0;
|
|
482
|
+
}
|
|
483
|
+
return self;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Returns true if the manager is attached.
|
|
488
|
+
*
|
|
489
|
+
* @param self The Manager instance
|
|
490
|
+
* @return Qtrue if attached, Qfalse otherwise
|
|
491
|
+
*
|
|
492
|
+
* Ruby: manager.attached?
|
|
493
|
+
*/
|
|
494
|
+
static VALUE iodine_pubsub_history_manager_attached_p(VALUE self) {
|
|
495
|
+
iodine_pubsub_history_s *h = iodine_pubsub_history_get(self);
|
|
496
|
+
return h->attached ? Qtrue : Qfalse;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/* *****************************************************************************
|
|
500
|
+
Initialize - Ruby Class Registration
|
|
501
|
+
***************************************************************************** */
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Initializes the Iodine::PubSub::History Ruby module.
|
|
505
|
+
*
|
|
506
|
+
* Defines:
|
|
507
|
+
* - Iodine::PubSub::History.cache(size_limit:) - Enable built-in memory cache
|
|
508
|
+
* - Iodine::PubSub::History.cache? - Check if memory cache is enabled
|
|
509
|
+
* - Iodine::PubSub::History::Manager - Custom history manager base class
|
|
510
|
+
*/
|
|
511
|
+
static void Init_Iodine_PubSub_History(void) {
|
|
512
|
+
/* Initialize Iodine::PubSub::History module */
|
|
513
|
+
iodine_rb_IODINE_PUBSUB_HISTORY =
|
|
514
|
+
rb_define_module_under(iodine_rb_IODINE_PUBSUB, "History");
|
|
515
|
+
STORE.hold(iodine_rb_IODINE_PUBSUB_HISTORY);
|
|
516
|
+
|
|
517
|
+
rb_define_module_function(iodine_rb_IODINE_PUBSUB_HISTORY,
|
|
518
|
+
"cache",
|
|
519
|
+
iodine_pubsub_history_cache,
|
|
520
|
+
-1);
|
|
521
|
+
rb_define_module_function(iodine_rb_IODINE_PUBSUB_HISTORY,
|
|
522
|
+
"cache?",
|
|
523
|
+
iodine_pubsub_history_cache_p,
|
|
524
|
+
0);
|
|
525
|
+
|
|
526
|
+
/* Initialize Iodine::PubSub::History::Manager class */
|
|
527
|
+
VALUE manager_class = rb_define_class_under(iodine_rb_IODINE_PUBSUB_HISTORY,
|
|
528
|
+
"Manager",
|
|
529
|
+
rb_cObject);
|
|
530
|
+
STORE.hold(manager_class);
|
|
531
|
+
rb_define_alloc_func(manager_class, iodine_pubsub_history_alloc);
|
|
532
|
+
|
|
533
|
+
rb_define_method(manager_class,
|
|
534
|
+
"initialize",
|
|
535
|
+
iodine_pubsub_history_manager_initialize,
|
|
536
|
+
-1);
|
|
537
|
+
rb_define_method(manager_class,
|
|
538
|
+
"detach",
|
|
539
|
+
iodine_pubsub_history_manager_detach,
|
|
540
|
+
0);
|
|
541
|
+
rb_define_method(manager_class,
|
|
542
|
+
"attached?",
|
|
543
|
+
iodine_pubsub_history_manager_attached_p,
|
|
544
|
+
0);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
#endif /* H___IODINE_PUBSUB_HISTORY___H */
|