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.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -21
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -148
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. 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 */