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.
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 -20
  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 -146
  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,363 @@
1
+ #ifndef H___IODINE_CORE___H
2
+ #define H___IODINE_CORE___H
3
+ #include "iodine.h"
4
+
5
+ /* *****************************************************************************
6
+ Iodine Core - IO Reactor Control
7
+
8
+ This module provides the core functionality for controlling the Iodine IO
9
+ reactor, including:
10
+
11
+ - Starting and stopping the reactor event loop
12
+ - Querying reactor state (running, master/worker process)
13
+ - Configuring worker processes and threads
14
+ - Managing verbosity/logging levels
15
+ - Server secrets for cryptographic operations
16
+ - Graceful shutdown timeout configuration
17
+
18
+ The reactor uses a multi-process/multi-thread architecture where:
19
+ - Master process manages worker processes
20
+ - Each worker process runs its own event loop with multiple threads
21
+ - Thread pool handles async tasks within each worker
22
+
23
+ Ruby API Methods (defined on Iodine module):
24
+ - Iodine.start - Start the IO reactor
25
+ - Iodine.stop - Stop the current process' reactor
26
+ - Iodine.running? - Check if reactor is running
27
+ - Iodine.master? - Check if this is the master process
28
+ - Iodine.worker? - Check if this is a worker process
29
+ - Iodine.workers / = - Get/set number of worker processes
30
+ - Iodine.threads / = - Get/set number of threads per worker
31
+ - Iodine::Logger.level / = - Get/set logging level (Iodine.verbosity deprecated)
32
+ - Iodine.secret / = - Get/set server secret key
33
+ - Iodine.shutdown_timeout / = - Get/set graceful shutdown timeout
34
+ ***************************************************************************** */
35
+
36
+ /* *****************************************************************************
37
+ Starting / Stopping the IO Reactor
38
+ ***************************************************************************** */
39
+
40
+ static void iodine_connection_cache_common_strings(void);
41
+
42
+ /** C-only data passed from iodine_start() (GVL held) to iodine___start()
43
+ * (GVL released) so that iodine___start() never touches Ruby API. */
44
+ typedef struct {
45
+ const char *version; /* C string copy of Iodine::VERSION */
46
+ } iodine___start_args_s;
47
+
48
+ static void *iodine___start(void *arg_) {
49
+ iodine___start_args_s *args = (iodine___start_args_s *)arg_;
50
+ unsigned threads = (unsigned)fio_io_workers((int)fio_cli_get_i("-t"));
51
+ unsigned workers = (unsigned)fio_io_workers((int)fio_cli_get_i("-w"));
52
+ fio_io_async_attach(&IODINE_THREAD_POOL, (uint32_t)threads);
53
+
54
+ FIO_LOG_INFO("\n\tStarting the iodine server."
55
+ "\n\tVersion: %s"
56
+ "\n\tEngine: " FIO_POLL_ENGINE_STR "\n\tWorkers: %d\t(%s)"
57
+ "\n\tThreads: 1+%d\t(per worker)"
58
+ "\n\tPress ^C to exit.",
59
+ args->version,
60
+ workers,
61
+ (workers ? "cluster mode" : "single process"),
62
+ (int)IODINE_THREAD_POOL.count);
63
+
64
+ fio_io_start((int)fio_cli_get_i("-w"));
65
+ return NULL;
66
+ }
67
+
68
+ /**
69
+ * Starts the Iodine IO reactor.
70
+ *
71
+ * This is the main entry point that begins the event loop. It:
72
+ * - Attaches the async thread pool
73
+ * - Sets rack.multithread/rack.multiprocess environment values
74
+ * - Logs startup information (version, workers, threads)
75
+ * - Starts the IO event loop (blocking until stopped)
76
+ *
77
+ * @param self The Iodine module (VALUE)
78
+ * @return self
79
+ *
80
+ * Ruby: Iodine.start
81
+ */
82
+ static VALUE iodine_start(VALUE self) { // clang-format on
83
+ /* All Ruby API calls MUST happen here (GVL is held).
84
+ * iodine___start() runs WITHOUT the GVL and must only use pure C. */
85
+ iodine___start_args_s start_args = {.version = "unknown"};
86
+ VALUE ver = rb_const_get(iodine_rb_IODINE, rb_intern2("VERSION", 7));
87
+ if (ver != Qnil)
88
+ start_args.version = RSTRING_PTR(ver);
89
+
90
+ /* Set rack.multithread / rack.multiprocess env template values (Ruby API) */
91
+ {
92
+ VALUE keeper;
93
+ iodine_env_set_const_val(IODINE_CONNECTION_ENV_TEMPLATE,
94
+ FIO_STR_INFO1((char *)"rack.multithread"),
95
+ (fio_cli_get_i("-t") ? Qtrue : Qfalse),
96
+ &keeper);
97
+ iodine_env_set_const_val(IODINE_CONNECTION_ENV_TEMPLATE,
98
+ FIO_STR_INFO1((char *)"rack.multiprocess"),
99
+ (fio_cli_get_i("-w") ? Qtrue : Qfalse),
100
+ &keeper);
101
+ }
102
+
103
+ #ifdef _WIN32
104
+ /* Worker processes require fork(2), which is unavailable on Windows.
105
+ * Check here while the GVL is still held so rb_raise is safe. */
106
+ if (fio_cli_get_i("-w") != 0)
107
+ rb_raise(rb_eNotImpError,
108
+ "Worker processes (fork) are not supported on Windows. "
109
+ "Call Iodine.workers = 0 before Iodine.start.");
110
+ #endif /* _WIN32 */
111
+
112
+ rb_thread_call_without_gvl(iodine___start, &start_args, NULL, NULL);
113
+ return self;
114
+ }
115
+
116
+ /**
117
+ * Stops the current process' IO reactor.
118
+ *
119
+ * If this is a worker process, the process will exit and if Iodine is running
120
+ * in cluster mode, a new worker will be spawned by the master process.
121
+ *
122
+ * @param klass The Iodine module (VALUE)
123
+ * @return klass
124
+ *
125
+ * Ruby: Iodine.stop
126
+ */
127
+ static VALUE iodine_stop(VALUE klass) {
128
+ fio_io_stop();
129
+ return klass;
130
+ }
131
+
132
+ /**
133
+ * Returns `true` if the IO reactor is currently running.
134
+ *
135
+ * @param klass The Iodine module (VALUE)
136
+ * @return Qtrue if running, Qfalse otherwise
137
+ *
138
+ * Ruby: Iodine.running?
139
+ */
140
+ static VALUE iodine_is_running(VALUE klass) {
141
+ return fio_io_is_running() ? Qtrue : Qfalse;
142
+ }
143
+
144
+ /**
145
+ * Returns `true` if this is the master (root) process.
146
+ *
147
+ * In cluster mode, the master process spawns and monitors worker processes.
148
+ * In single-process mode, the process is both master and worker.
149
+ *
150
+ * @param klass The Iodine module (VALUE)
151
+ * @return Qtrue if master process, Qfalse otherwise
152
+ *
153
+ * Ruby: Iodine.master?
154
+ */
155
+ static VALUE iodine_is_master(VALUE klass) {
156
+ return fio_io_is_master() ? Qtrue : Qfalse;
157
+ }
158
+
159
+ /**
160
+ * Returns `true` if this is a worker process.
161
+ *
162
+ * Worker processes handle actual client connections and requests.
163
+ * In single-process mode, the process is both master and worker.
164
+ *
165
+ * @param klass The Iodine module (VALUE)
166
+ * @return Qtrue if worker process, Qfalse otherwise
167
+ *
168
+ * Ruby: Iodine.worker?
169
+ */
170
+ static VALUE iodine_is_worker(VALUE klass) {
171
+ return fio_io_is_worker() ? Qtrue : Qfalse;
172
+ }
173
+
174
+ /* *****************************************************************************
175
+ Workers - Process Pool Configuration
176
+ ***************************************************************************** */
177
+
178
+ /**
179
+ * Returns the number of worker processes that the reactor will use.
180
+ *
181
+ * Returns nil if workers haven't been configured yet.
182
+ * A value of 0 means single-process mode (no forking).
183
+ * A value > 0 enables cluster mode with that many worker processes.
184
+ *
185
+ * @param klass The Iodine module (VALUE)
186
+ * @return Number of workers as Fixnum, or Qnil if not configured
187
+ *
188
+ * Ruby: Iodine.workers
189
+ */
190
+ static VALUE iodine_workers(VALUE klass) {
191
+ unsigned long tmp = fio_io_workers((uint16_t)fio_cli_get_i("-w"));
192
+ return LL2NUM(tmp);
193
+ (void)klass;
194
+ }
195
+
196
+ /**
197
+ * Sets the number of worker processes that the reactor will use.
198
+ *
199
+ * Can only be set in the master process before starting the reactor.
200
+ * Set to 0 for single-process mode, or > 0 for cluster mode.
201
+ *
202
+ * @param klass The Iodine module (VALUE)
203
+ * @param workers Number of worker processes (Fixnum)
204
+ * @return The configured number of workers
205
+ *
206
+ * @note Raises TypeError if workers is not a Fixnum.
207
+ * @note Logs error if called from a worker process.
208
+ *
209
+ * Ruby: Iodine.workers = n
210
+ */
211
+ static VALUE iodine_workers_set(VALUE klass, VALUE workers) {
212
+ if (workers != Qnil && fio_io_is_master()) {
213
+ if (TYPE(workers) != T_FIXNUM) {
214
+ rb_raise(rb_eTypeError, "workers must be a number.");
215
+ return Qnil;
216
+ }
217
+ fio_cli_set_i("-w", NUM2LL(workers));
218
+ } else {
219
+ FIO_LOG_ERROR(
220
+ "cannot set workers except as a numeral value in the master process");
221
+ }
222
+ workers = LL2NUM(fio_cli_get_i("-w"));
223
+ return workers;
224
+ }
225
+
226
+ /* *****************************************************************************
227
+ Threads - Thread Pool Configuration
228
+ ***************************************************************************** */
229
+
230
+ /**
231
+ * Returns the number of threads per worker that the reactor will use.
232
+ *
233
+ * Returns nil if threads haven't been configured yet.
234
+ * Each worker process runs this many threads in its async thread pool.
235
+ *
236
+ * @param klass The Iodine module (VALUE)
237
+ * @return Number of threads as Fixnum, or Qnil if not configured
238
+ *
239
+ * Ruby: Iodine.threads
240
+ */
241
+ static VALUE iodine_threads(VALUE klass) {
242
+ unsigned long tmp = fio_io_workers((uint16_t)fio_cli_get_i("-t"));
243
+ return LL2NUM(tmp);
244
+ (void)klass;
245
+ }
246
+
247
+ /**
248
+ * Sets the number of threads per worker that the reactor will use.
249
+ *
250
+ * Can only be set in the master process before starting the reactor.
251
+ * Each worker process will run this many threads in its async pool.
252
+ *
253
+ * @param klass The Iodine module (VALUE)
254
+ * @param threads Number of threads per worker (Fixnum)
255
+ * @return The configured number of threads
256
+ *
257
+ * @note Raises TypeError if threads is not a Fixnum.
258
+ * @note Logs error if called from a worker process.
259
+ *
260
+ * Ruby: Iodine.threads = n
261
+ */
262
+ static VALUE iodine_threads_set(VALUE klass, VALUE threads) {
263
+ if (threads != Qnil && fio_io_is_master()) {
264
+ if (TYPE(threads) != T_FIXNUM) {
265
+ rb_raise(rb_eTypeError, "threads must be a number.");
266
+ return Qnil;
267
+ }
268
+ fio_cli_set_i("-t", NUM2LL(threads));
269
+ } else {
270
+ FIO_LOG_ERROR(
271
+ "cannot set threads except as a numeral value in the master process");
272
+ }
273
+ threads = LL2NUM(fio_cli_get_i("-t"));
274
+ return threads;
275
+ }
276
+
277
+ /* *****************************************************************************
278
+ Secrets - Server Secret Key for Cryptographic Operations
279
+ ***************************************************************************** */
280
+
281
+ /**
282
+ * Returns the server's secret as a 64-byte binary string.
283
+ *
284
+ * The secret is used for cryptographic operations like session signing,
285
+ * CSRF tokens, and other security-sensitive operations. It's derived
286
+ * from the secret key set via `Iodine.secret=` or auto-generated.
287
+ *
288
+ * @param klass The Iodine module (VALUE)
289
+ * @return 64-byte binary String containing the server secret
290
+ *
291
+ * Ruby: Iodine.secret
292
+ */
293
+ static VALUE iodine_secret(VALUE klass) {
294
+ fio_u512 s = fio_secret();
295
+ return rb_enc_str_new((const char *)s.u8, sizeof(s), IodineBinaryEncoding);
296
+ (void)klass;
297
+ }
298
+
299
+ /**
300
+ * Sets a new server secret based on the provided key.
301
+ *
302
+ * The key is hashed/expanded to produce the internal 512-bit secret.
303
+ * This should be set before starting the server for consistent
304
+ * cryptographic operations across restarts.
305
+ *
306
+ * @param klass The Iodine module (VALUE)
307
+ * @param key The secret key (String)
308
+ * @return The new 64-byte server secret
309
+ *
310
+ * @note Raises TypeError if key is not a String.
311
+ *
312
+ * Ruby: Iodine.secret = "my-secret-key"
313
+ */
314
+ static VALUE iodine_secret_set(VALUE klass, VALUE key) {
315
+ rb_check_type(key, RUBY_T_STRING);
316
+ fio_secret_set(RSTRING_PTR(key), RSTRING_LEN(key), 0);
317
+ return iodine_secret(klass);
318
+ }
319
+
320
+ /* *****************************************************************************
321
+ Shutdown Timeouts - Graceful Shutdown Configuration
322
+ ***************************************************************************** */
323
+
324
+ /**
325
+ * Returns the current graceful shutdown timeout in milliseconds.
326
+ *
327
+ * During graceful shutdown, the server waits for active connections
328
+ * to complete before forcing termination. This timeout controls
329
+ * how long to wait.
330
+ *
331
+ * @param klass The Iodine module (VALUE)
332
+ * @return Timeout in milliseconds as Fixnum
333
+ *
334
+ * Ruby: Iodine.shutdown_timeout
335
+ */
336
+ static VALUE iodine_shutdown_timeout(VALUE klass) {
337
+ return RB_SIZE2NUM(fio_io_shutdown_timeout());
338
+ (void)klass;
339
+ }
340
+
341
+ /**
342
+ * Sets the graceful shutdown timeout in milliseconds.
343
+ *
344
+ * Maximum allowed value is 5 minutes (300,000 ms).
345
+ *
346
+ * @param klass The Iodine module (VALUE)
347
+ * @param num Timeout in milliseconds (Fixnum)
348
+ * @return The new timeout value
349
+ *
350
+ * @note Raises TypeError if num is not a Fixnum.
351
+ * @note Raises RangeError if num exceeds 5 minutes.
352
+ *
353
+ * Ruby: Iodine.shutdown_timeout = 30000 # 30 seconds
354
+ */
355
+ static VALUE iodine_shutdown_timeout_set(VALUE klass, VALUE num) {
356
+ rb_check_type(num, RUBY_T_FIXNUM);
357
+ if (RB_NUM2SIZE(num) > (5U * 60U * 1000U))
358
+ rb_raise(rb_eRangeError, "shutdown timeout out of range");
359
+ fio_io_shutdown_timeout_set(RB_NUM2SIZE(num));
360
+ return num;
361
+ }
362
+
363
+ #endif /* H___IODINE_CORE___H */