iodine 0.4.19 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
data/ext/iodine/facil.h CHANGED
@@ -10,20 +10,73 @@ Feel free to copy, use and enjoy according to the license provided.
10
10
  */
11
11
  #define H_FACIL_H
12
12
  #define FACIL_VERSION_MAJOR 0
13
- #define FACIL_VERSION_MINOR 5
14
- #define FACIL_VERSION_PATCH 8
13
+ #define FACIL_VERSION_MINOR 6
14
+ #define FACIL_VERSION_PATCH 4
15
15
 
16
16
  #ifndef FACIL_PRINT_STATE
17
17
  /**
18
- When FACIL_PRINT_STATE is set to 1, facil.io will print out common messages
19
- regarding the server state (start / finish / listen messages).
20
- */
18
+ * When FACIL_PRINT_STATE is set to 1, facil.io will print out common messages
19
+ * regarding the server state (start / finish / listen messages).
20
+ */
21
21
  #define FACIL_PRINT_STATE 1
22
22
  #endif
23
+
24
+ #ifndef FACIL_CPU_CORES_LIMIT
25
+ /**
26
+ * If facil.io detects more CPU cores than the number of cores stated in the
27
+ * FACIL_CPU_CORES_LIMIT, it will assume an error and cap the number of cores
28
+ * detected to the assigned limit.
29
+ *
30
+ * This is only relevant to automated values, when running facil.io with zero
31
+ * threads and processes, which invokes a large matrix of workers and threads
32
+ * (see {facil_run})
33
+ *
34
+ * The default auto-detection cap is set at 8 cores. The number is arbitrary
35
+ * (historically the number 7 was used after testing `malloc` race conditions on
36
+ * a MacBook Pro).
37
+ *
38
+ * The does NOT effect manually set (non-zero) worker/thread values.
39
+ */
40
+ #define FACIL_CPU_CORES_LIMIT 8
41
+ #endif
42
+
43
+ #ifndef FIO_DEDICATED_SYSTEM
44
+ /**
45
+ * If FIO_DEDICATED_SYSTEM is false, threads will be used (mostly) for
46
+ * non-prallel concurrency (protection against slow user code / high load) and
47
+ * processes will be used for parallelism. Otherwise, both threads and processes
48
+ * will be used for prallel concurrency (at the expense of increased polling).
49
+ *
50
+ * If FIO_DEDICATED_SYSTEM is true, facil.io assumes that the whole system is at
51
+ * it's service and that no other process is using the CPU cores.
52
+ *
53
+ * Accordingly, facil.io will poll the IO more often in an attempt to activate
54
+ * the threads and utilize all the cores whenever events occur.
55
+ *
56
+ * My tests show that the non-polling approach is faster, but it may be system
57
+ * specific.
58
+ */
59
+ #define FIO_DEDICATED_SYSTEM 0
60
+ #endif
61
+
62
+ #ifndef FACIL_DISABLE_HOT_RESTART
63
+ /**
64
+ * Disables the hot restart reaction to the SIGUSR1 signal
65
+ *
66
+ * The hot restart will attempt to shut down all workers, and spawn new workers,
67
+ * cleaning up any data cached by any of the workers.
68
+ *
69
+ * It's quite useless unless the workers are running their own VMs which are
70
+ * initialized in the listening socket's `on_start` callback.
71
+ */
72
+ #define FACIL_DISABLE_HOT_RESTART 0
73
+ #endif
74
+
23
75
  /* *****************************************************************************
24
76
  Required facil libraries
25
77
  ***************************************************************************** */
26
78
  #include "defer.h"
79
+ #include "fiobj.h"
27
80
  #include "sock.h"
28
81
 
29
82
  /* support C++ */
@@ -60,14 +113,19 @@ struct FacilIOProtocol {
60
113
  * used (not `strcmp`).
61
114
  */
62
115
  const char *service;
63
- /** called when a data is available, but will not run concurrently */
116
+ /** Called when a data is available, but will not run concurrently */
64
117
  void (*on_data)(intptr_t uuid, protocol_s *protocol);
65
118
  /** called when the socket is ready to be written to. */
66
119
  void (*on_ready)(intptr_t uuid, protocol_s *protocol);
67
- /** called when the server is shutting down,
68
- * but before closing the connection. */
120
+ /**
121
+ * Called when the server is shutting down, immediately before closing the
122
+ * connection.
123
+ *
124
+ * The callback runs within a {FIO_PR_LOCK_TACK} lock, so it will never run
125
+ * concurrently wil {on_data} or other connection specific tasks.
126
+ */
69
127
  void (*on_shutdown)(intptr_t uuid, protocol_s *protocol);
70
- /** called when the connection was closed, but will not run concurrently */
128
+ /** Called when the connection was closed, but will not run concurrently */
71
129
  void (*on_close)(intptr_t uuid, protocol_s *protocol);
72
130
  /** called when a connection's timeout was reached */
73
131
  void (*ping)(intptr_t uuid, protocol_s *protocol);
@@ -144,8 +202,10 @@ static protocol_s *echo_on_open(intptr_t uuid, void *udata) {
144
202
 
145
203
  int main() {
146
204
  // Setup a listening socket
147
- if (facil_listen(.port = "8888", .on_open = echo_on_open))
148
- perror("No listening socket available on port 8888"), exit(-1);
205
+ if (facil_listen(.port = "8888", .on_open = echo_on_open)) {
206
+ perror("No listening socket available on port 8888");
207
+ exit(-1);
208
+ }
149
209
  // Run the server and hang until a stop signal is received.
150
210
  facil_run(.threads = 4, .processes = 1);
151
211
  }
@@ -155,40 +215,30 @@ int main() {
155
215
  struct facil_listen_args {
156
216
  /**
157
217
  * Called whenever a new connection is accepted.
158
- * Should return a pointer to the connection's protocol
218
+ *
219
+ * Should either call `facil_attach` or close the connection.
159
220
  */
160
- protocol_s *(*on_open)(intptr_t fduuid, void *udata);
221
+ void (*on_open)(intptr_t fduuid, void *udata);
161
222
  /** The network service / port. Defaults to "3000". */
162
223
  const char *port;
163
224
  /** The socket binding address. Defaults to the recommended NULL. */
164
225
  const char *address;
165
226
  /** Opaque user data. */
166
227
  void *udata;
167
- /** Opaque user data for `set_rw_hooks`. */
168
- void *rw_udata;
169
- /** (optional)
170
- * Called before `on_open`, allowing Read/Write hook initialization.
171
- * Should return a pointer to the new RW hooks or NULL (to use default hooks).
172
- *
173
- * This allows a seperation between the transport layer (i.e. TLS) and the
174
- * protocol (i.e. HTTP).
175
- */
176
- sock_rw_hook_s *(*set_rw_hooks)(intptr_t fduuid, void *rw_udata);
177
228
  /**
178
- * Called when the server starts, allowing for further initialization, such as
179
- * timed event scheduling.
229
+ * Called when the server starts (or a worker process is respawned), allowing
230
+ * for further initialization, such as timed event scheduling or VM
231
+ * initialization.
180
232
  *
181
- * This will be called seperately for every process. */
233
+ * This will be called seperately for every worker process whenever it is
234
+ * spawned.
235
+ */
182
236
  void (*on_start)(intptr_t uuid, void *udata);
183
237
  /**
184
238
  * Called when the server is done, usable for cleanup.
185
239
  *
186
240
  * This will be called seperately for every process. */
187
241
  void (*on_finish)(intptr_t uuid, void *udata);
188
- /**
189
- * A cleanup callback for the `rw_udata`.
190
- */
191
- void (*on_finish_rw)(intptr_t uuid, void *rw_udata);
192
242
  };
193
243
 
194
244
  /** Schedule a network service on a listening socket. */
@@ -217,8 +267,10 @@ struct facil_connect_args {
217
267
  /**
218
268
  * The `on_connect` callback should return a pointer to a protocol object
219
269
  * that will handle any connection related events.
270
+ *
271
+ * Should either call `facil_attach` or close the connection.
220
272
  */
221
- protocol_s *(*on_connect)(intptr_t uuid, void *udata);
273
+ void (*on_connect)(intptr_t uuid, void *udata);
222
274
  /**
223
275
  * The `on_fail` is called when a socket fails to connect. The old sock UUID
224
276
  * is passed along.
@@ -226,16 +278,8 @@ struct facil_connect_args {
226
278
  void (*on_fail)(intptr_t uuid, void *udata);
227
279
  /** Opaque user data. */
228
280
  void *udata;
229
- /** Opaque user data for `set_rw_hooks`. */
230
- void *rw_udata;
231
- /** (optional)
232
- * Called before `on_connect`, allowing Read/Write hook initialization.
233
- * Should return a pointer to the new RW hooks or NULL (to use default hooks).
234
- *
235
- * This allows a seperation between the transport layer (i.e. TLS) and the
236
- * protocol (i.e. HTTP).
237
- */
238
- sock_rw_hook_s *(*set_rw_hooks)(intptr_t fduuid, void *udata);
281
+ /** A non-system timeout after which connection is assumed to have failed. */
282
+ uint8_t timeout;
239
283
  };
240
284
 
241
285
  /**
@@ -267,10 +311,26 @@ Core API
267
311
  ***************************************************************************** */
268
312
 
269
313
  struct facil_run_args {
270
- /** The number of threads to run in the thread pool. Has "smart" defaults. */
271
- uint16_t threads;
272
- /** The number of processes to run (including this one). "smart" defaults. */
273
- uint16_t processes;
314
+ /**
315
+ * The number of threads to run in the thread pool. Has "smart" defaults.
316
+ *
317
+ *
318
+ * A positive value will indicate a set number of threads (or processes).
319
+ *
320
+ * Zeros and negative values are fun and include an interesting shorthand:
321
+ *
322
+ * * Negative values indicate a fraction of the number of CPU cores. i.e.
323
+ * -2 will normally indicate "half" (1/2) the number of cores.
324
+ *
325
+ * * If the other option (i.e. `.processes` when setting `.threads`) is zero,
326
+ * it will be automatically updated to reflect the option's absolute value.
327
+ * i.e.:
328
+ * if .threads == -2 and .processes == 0,
329
+ * than facil.io will run 2 processes with (cores/2) threads per process.
330
+ */
331
+ int16_t threads;
332
+ /** The number of processes to run (including this one). See `threads`. */
333
+ int16_t processes;
274
334
  /** called if the event loop in cycled with no pending events. */
275
335
  void (*on_idle)(void);
276
336
  /** called when the server is done, to clean up any leftovers. */
@@ -290,13 +350,21 @@ void facil_run(struct facil_run_args args);
290
350
  */
291
351
  int facil_is_running(void);
292
352
 
353
+ /**
354
+ OVERRIDE THIS to replace the default `fork` implementation or to inject hooks
355
+ into the forking function.
356
+
357
+ Behaves like the system's `fork`.
358
+ */
359
+ int facil_fork(void);
360
+
293
361
  /**
294
362
  * Attaches (or updates) a protocol object to a socket UUID.
295
363
  *
296
- * The new protocol object can be NULL, which will practically "hijack" the
297
- * socket away from facil.
364
+ * The new protocol object can be NULL, which will detach ("hijack"), the
365
+ * socket.
298
366
  *
299
- * The old protocol's `on_close` will be scheduled, if they both exist.
367
+ * The old protocol's `on_close` (if any) will be scheduled.
300
368
  *
301
369
  * Returns -1 on error and 0 on success.
302
370
  *
@@ -310,10 +378,7 @@ int facil_attach(intptr_t uuid, protocol_s *protocol);
310
378
  * The protocol will be attached in the FIO_PR_LOCK_TASK state, requiring a
311
379
  * furthur call to `facil_protocol_unlock`.
312
380
  *
313
- * The new protocol object can be NULL, which will practically "hijack" the
314
- * socket away from facil.
315
- *
316
- * The old protocol's `on_close` will be scheduled, if they both exist.
381
+ * The old protocol's `on_close` (if any) will be scheduled.
317
382
  *
318
383
  * Returns -1 on error and 0 on success.
319
384
  *
@@ -335,14 +400,33 @@ enum facil_io_event {
335
400
  /** Schedules an IO event, even id it did not occur. */
336
401
  void facil_force_event(intptr_t uuid, enum facil_io_event);
337
402
 
403
+ /**
404
+ * Temporarily prevents `on_data` events from firing.
405
+ *
406
+ * The `on_data` event will be automatically rescheduled when (if) the socket's
407
+ * outgoing buffer fills up or when `facil_force_event` is called with
408
+ * `FIO_EVENT_ON_DATA`.
409
+ *
410
+ * Note: the function will work as expected when called within the protocol's
411
+ * `on_data` callback and the `uuid` refers to a valid socket. Otherwise the
412
+ * function might quitely fail.
413
+ */
414
+ void facil_quite(intptr_t uuid);
415
+
338
416
  /* *****************************************************************************
339
417
  Helper API
340
418
  ***************************************************************************** */
341
419
 
342
420
  /**
343
- Returns the last time the server reviewed any pending IO events.
344
- */
345
- time_t facil_last_tick(void);
421
+ * Initializes zombie reaping for the process. Call before `facil_run` to enable
422
+ * global zombie reaping.
423
+ */
424
+ void facil_reap_children(void);
425
+
426
+ /**
427
+ * Returns the last time the server reviewed any pending IO events.
428
+ */
429
+ struct timespec facil_last_tick(void);
346
430
 
347
431
  /** Counts all the connections of a specific type `service`. */
348
432
  size_t facil_count(void *service);
@@ -392,7 +476,7 @@ struct facil_defer_args_s {
392
476
  intptr_t uuid;
393
477
  /** The type of task to be performed. Defaults to `FIO_PR_LOCK_TASK` but could
394
478
  * also be seto to `FIO_PR_LOCK_WRITE`. */
395
- enum facil_protocol_lock_e task_type;
479
+ enum facil_protocol_lock_e type;
396
480
  /** The task (function) to be performed. This is required. */
397
481
  void (*task)(intptr_t uuid, protocol_s *, void *arg);
398
482
  /** An opaque user data that will be passed along to the task. */
@@ -441,11 +525,14 @@ int facil_each(struct facil_each_args_s args);
441
525
  #define facil_each(...) facil_each((struct facil_each_args_s){__VA_ARGS__})
442
526
 
443
527
  /* *****************************************************************************
444
- Cluster specific API - local cluster messaging.
528
+ * Cluster specific API - local cluster messaging.
529
+ *
530
+ * Facil supports message process clustering, so that a multi-process
531
+ * application can easily send and receive messages across process boundries.
532
+ **************************************************************************** */
445
533
 
446
- Facil supports message process clustering, so that a multi-process application
447
- can easily send and receive messages across process boundries.
448
- ***************************************************************************** */
534
+ /** returns facil.io's parent (root) process pid. */
535
+ pid_t facil_parent_pid(void);
449
536
 
450
537
  /**
451
538
  Sets a callback / handler for a message of type `msg_type`.
@@ -456,9 +543,9 @@ registered callbacks.
456
543
  The `msg_type` value can be any positive number up to 2^31-1 (2,147,483,647).
457
544
  All values less than 0 are reserved for internal use.
458
545
  */
459
- void facil_cluster_set_handler(int32_t msg_type,
460
- void (*on_message)(void *data, uint32_t len));
461
-
546
+ void facil_cluster_set_handler(int32_t filter,
547
+ void (*on_message)(int32_t filter, FIOBJ ch,
548
+ FIOBJ msg));
462
549
  /** Sends a message of type `msg_type` to the **other** cluster processes.
463
550
 
464
551
  `msg_type` should match a message type used when calling
@@ -472,7 +559,7 @@ starting at 1,073,741,824 are reserved for internal use.
472
559
  Callbacks are invoked using an O(n) matching, where `n` is the number of
473
560
  registered callbacks.
474
561
  */
475
- int facil_cluster_send(int32_t msg_type, void *data, uint32_t len);
562
+ int facil_cluster_send(int32_t filter, FIOBJ ch, FIOBJ msg);
476
563
 
477
564
  /* *****************************************************************************
478
565
  Lower Level API - for special circumstances, use with care under .
@@ -0,0 +1,418 @@
1
+ #ifndef H_FIO_ARRAY_H
2
+ /*
3
+ Copyright: Boaz Segev, 2017-2018
4
+ License: MIT
5
+ */
6
+
7
+ /**
8
+ * A Dynamic Array for general use (void * pointers).
9
+ *
10
+ * The file was written to be compatible with C++ as well as C, hence some
11
+ * pointer casting.
12
+ *
13
+ * Use:
14
+
15
+ fio_ary_s ary; // a container can be placed on the stack.
16
+ fio_ary_new(&ary); // initialize the container
17
+ fio_ary_push(&ary, (void*)1 ); // add / remove / read data...
18
+ fio_ary_free(&ary) // free any resources, not the container.
19
+
20
+
21
+ */
22
+ #define H_FIO_ARRAY_H
23
+
24
+ #include <errno.h>
25
+ #include <stdint.h>
26
+ #include <stdio.h>
27
+ #include <stdlib.h>
28
+ #include <string.h>
29
+ #ifndef _GNU_SOURCE
30
+ #define _GNU_SOURCE
31
+ #endif
32
+
33
+ #ifndef FIO_FUNC
34
+ #define FIO_FUNC static __attribute__((unused))
35
+ #endif
36
+
37
+ #ifdef __cplusplus
38
+ #define register
39
+ #endif
40
+
41
+ typedef struct fio_ary_s {
42
+ size_t start;
43
+ size_t end;
44
+ size_t capa;
45
+ void **arry;
46
+ } fio_ary_s;
47
+
48
+ /** this value can be used for lazy initialization. */
49
+ #define FIO_ARY_INIT ((fio_ary_s){.arry = NULL})
50
+
51
+ /* *****************************************************************************
52
+ Array API
53
+ ***************************************************************************** */
54
+
55
+ /** Initializes the array and allocates memory for it's internal data. */
56
+ FIO_FUNC inline void fio_ary_new(fio_ary_s *ary, size_t capa);
57
+
58
+ /** Frees the array's internal data. */
59
+ FIO_FUNC inline void fio_ary_free(fio_ary_s *ary);
60
+
61
+ /** Returns the number of elements in the Array. */
62
+ FIO_FUNC inline size_t fio_ary_count(fio_ary_s *ary);
63
+
64
+ /** Returns the current, temporary, array capacity (it's dynamic). */
65
+ FIO_FUNC inline size_t fio_ary_capa(fio_ary_s *ary);
66
+
67
+ /**
68
+ * Returns the object placed in the Array, if any. Returns NULL if no data or if
69
+ * the index is out of bounds.
70
+ *
71
+ * Negative values are retrived from the end of the array. i.e., `-1`
72
+ * is the last item.
73
+ */
74
+ FIO_FUNC inline void *fio_ary_index(fio_ary_s *ary, intptr_t pos);
75
+
76
+ /** alias for `fiobj_ary_index` */
77
+ #define fio_ary_entry(a, p) fiobj_ary_index((a), (p))
78
+
79
+ /**
80
+ * Sets an object at the requested position.
81
+ *
82
+ * Returns the old value, if any.
83
+ *
84
+ * If an error occurs, the same data passed to the function is returned.
85
+ */
86
+ FIO_FUNC inline void *fio_ary_set(fio_ary_s *ary, void *data, intptr_t pos);
87
+
88
+ /**
89
+ * Pushes an object to the end of the Array. Returns -1 on error.
90
+ */
91
+ FIO_FUNC inline int fio_ary_push(fio_ary_s *ary, void *data);
92
+
93
+ /** Pops an object from the end of the Array. */
94
+ FIO_FUNC inline void *fio_ary_pop(fio_ary_s *ary);
95
+
96
+ /**
97
+ * Unshifts an object to the beginning of the Array. Returns -1 on error.
98
+ *
99
+ * This could be expensive, causing `memmove`.
100
+ */
101
+ FIO_FUNC inline int fio_ary_unshift(fio_ary_s *ary, void *data);
102
+
103
+ /** Shifts an object from the beginning of the Array. */
104
+ FIO_FUNC inline void *fio_ary_shift(fio_ary_s *ary);
105
+
106
+ /**
107
+ * Iteration using a callback for each entry in the array.
108
+ *
109
+ * The callback task function must accept an the entry data as well as an opaque
110
+ * user pointer.
111
+ *
112
+ * If the callback returns -1, the loop is broken. Any other value is ignored.
113
+ *
114
+ * Returns the relative "stop" position, i.e., the number of items processed +
115
+ * the starting point.
116
+ */
117
+ FIO_FUNC inline size_t fio_ary_each(fio_ary_s *ary, size_t start_at,
118
+ int (*task)(void *pt, void *arg),
119
+ void *arg);
120
+ /**
121
+ * Removes any NULL *pointers* from an Array, keeping all other data in the
122
+ * array.
123
+ *
124
+ * This action is O(n) where n in the length of the array.
125
+ * It could get expensive.
126
+ */
127
+ FIO_FUNC inline void fio_ary_compact(fio_ary_s *ary);
128
+
129
+ /**
130
+ * Iterates through the list using a `for` loop.
131
+ *
132
+ * Access the data with `pos.obj` and it's index with `pos.i`. the `pos`
133
+ * variable can be named however you please.
134
+ */
135
+ #define FIO_ARY_FOR(ary, pos) \
136
+ if ((ary)->arry) \
137
+ for (struct fio_ary_pos_for_loop_s pos = {0, (ary)->arry[(ary)->start]}; \
138
+ (pos.i + (ary)->start) < (ary)->end && \
139
+ ((pos.obj = (ary)->arry[pos.i + (ary)->start]), 1); \
140
+ (++pos.i))
141
+ struct fio_ary_pos_for_loop_s {
142
+ unsigned long i;
143
+ void *obj;
144
+ };
145
+
146
+ /* *****************************************************************************
147
+ Array creation API
148
+ ***************************************************************************** */
149
+
150
+ FIO_FUNC inline void fio_ary_new(fio_ary_s *ary, size_t capa) {
151
+ if (!capa)
152
+ capa = 32;
153
+ *ary = (fio_ary_s){.arry = (void **)malloc(capa * sizeof(*ary->arry)),
154
+ .capa = capa};
155
+ if (!ary->arry) {
156
+ perror("ERROR: facil.io dynamic array couldn't be allocated");
157
+ exit(errno);
158
+ }
159
+ }
160
+ FIO_FUNC inline void fio_ary_free(fio_ary_s *ary) {
161
+ if (ary)
162
+ free(ary->arry);
163
+ }
164
+
165
+ /* *****************************************************************************
166
+ Array memory management
167
+ ***************************************************************************** */
168
+
169
+ /* This funcation manages the Array's memory. */
170
+ FIO_FUNC void fio_ary_getmem(fio_ary_s *ary, intptr_t needed) {
171
+ /* we have enough memory, but we need to re-organize it. */
172
+ if (needed == -1) {
173
+ if (ary->end < ary->capa) {
174
+ /* since allocation can be cheaper than memmove (depending on size),
175
+ * we'll just shove everything to the end...
176
+ */
177
+ size_t len = ary->end - ary->start;
178
+ memmove(ary->arry + ary->capa - len, ary->arry + ary->start,
179
+ len * sizeof(*ary->arry));
180
+ ary->start = ary->capa - len;
181
+ ary->end = ary->capa;
182
+ return;
183
+ }
184
+ /* add some breathing room for future `unshift`s */
185
+ needed = 0 - ((ary->capa < 1024) ? (ary->capa >> 1) : 1024);
186
+
187
+ } else if (needed == 1 && ary->start >= (ary->capa >> 1)) {
188
+ /* FIFO support optimizes smaller FIFO ranges over bloating allocations. */
189
+ size_t len = ary->end - ary->start;
190
+ memmove(ary->arry + 2, ary->arry + ary->start, len * sizeof(*ary->arry));
191
+ ary->start = 2;
192
+ ary->end = len + 2;
193
+ return;
194
+ }
195
+
196
+ /* alocate using exponential growth, up to single page size. */
197
+ size_t updated_capa = ary->capa;
198
+ size_t minimum = ary->capa + ((needed < 0) ? (0 - needed) : needed);
199
+ while (updated_capa <= minimum)
200
+ updated_capa =
201
+ (updated_capa <= 4096) ? (updated_capa << 1) : (updated_capa + 4096);
202
+
203
+ /* we assume memory allocation works. it's better to crash than to continue
204
+ * living without memory... besides, malloc is optimistic these days. */
205
+ ary->arry = (void **)realloc(ary->arry, updated_capa * sizeof(*ary->arry));
206
+ ary->capa = updated_capa;
207
+ if (!ary->arry) {
208
+ perror("ERROR: facil.io dynamic array couldn't be reallocated");
209
+ exit(errno);
210
+ }
211
+
212
+ if (needed >= 0) /* we're done, realloc grows the top of the address space*/
213
+ return;
214
+
215
+ /* move everything to the max, since memmove could get expensive */
216
+ size_t len = ary->end - ary->start;
217
+ memmove((ary->arry + ary->capa) - len, ary->arry + ary->start,
218
+ len * sizeof(*ary->arry));
219
+ ary->end = ary->capa;
220
+ ary->start = ary->capa - len;
221
+ }
222
+ /** Creates a mutable empty Array object with the requested capacity. */
223
+ FIO_FUNC inline void fiobj_ary_init(fio_ary_s *ary) {
224
+ *ary = (fio_ary_s){.arry = NULL};
225
+ }
226
+
227
+ /* *****************************************************************************
228
+ Array direct entry access API
229
+ ***************************************************************************** */
230
+
231
+ /** Returns the number of elements in the Array. */
232
+ FIO_FUNC inline size_t fio_ary_count(fio_ary_s *ary) {
233
+ return ary->end - ary->start;
234
+ }
235
+
236
+ /** Returns the current, temporary, array capacity (it's dynamic). */
237
+ FIO_FUNC inline size_t fio_ary_capa(fio_ary_s *ary) { return ary->capa; }
238
+
239
+ /**
240
+ * Returns a temporary object owned by the Array.
241
+ *
242
+ * Wrap this function call within `fiobj_dup` to get a persistent handle. i.e.:
243
+ *
244
+ * fiobj_dup(fiobj_ary_index(array, 0));
245
+ *
246
+ * Negative values are retrived from the end of the array. i.e., `-1`
247
+ * is the last item.
248
+ */
249
+ FIO_FUNC inline void *fio_ary_index(fio_ary_s *ary, intptr_t pos) {
250
+ if (!ary || !ary->arry)
251
+ return NULL;
252
+ /* position is relative to `start`*/
253
+ if (pos >= 0) {
254
+ pos = pos + ary->start;
255
+ if ((size_t)pos >= ary->end)
256
+ return NULL;
257
+ return ary->arry[pos];
258
+ }
259
+ /* position is relative to `end`*/
260
+ pos = (intptr_t)ary->end + pos;
261
+ if (pos < 0 || (size_t)pos < ary->start)
262
+ return NULL;
263
+ return ary->arry[pos];
264
+ }
265
+
266
+ /** alias for `fiobj_ary_index` */
267
+ #define fio_ary_entry(a, p) fiobj_ary_index((a), (p))
268
+
269
+ /**
270
+ * Sets an object at the requested position.
271
+ *
272
+ * Returns the old value, if any.
273
+ *
274
+ * If an error occurs, the same data passed to the function is returned.
275
+ */
276
+ FIO_FUNC inline void *fio_ary_set(fio_ary_s *ary, void *data, intptr_t pos) {
277
+ if (!ary->arry) {
278
+ fio_ary_new(ary, 0);
279
+ }
280
+ void *old = NULL;
281
+ /* test for memory and request memory if missing, promises valid bounds. */
282
+ if (pos >= 0) {
283
+ if ((size_t)pos + ary->start >= ary->capa)
284
+ fio_ary_getmem(ary, (((size_t)pos + ary->start) - (ary->capa - 1)));
285
+ } else if (pos + (intptr_t)ary->end < 0)
286
+ fio_ary_getmem(ary, pos + ary->end);
287
+
288
+ if (pos >= 0) {
289
+ /* position relative to start */
290
+ pos = pos + ary->start;
291
+ /* initialize empty spaces, if any, setting new boundries */
292
+ while ((size_t)pos >= ary->end)
293
+ ary->arry[(ary->end)++] = NULL;
294
+ } else {
295
+ /* position relative to end */
296
+ pos = pos + (intptr_t)ary->end;
297
+ /* initialize empty spaces, if any, setting new boundries */
298
+ while (ary->start > (size_t)pos)
299
+ ary->arry[--(ary->start)] = NULL;
300
+ }
301
+
302
+ /* check for an existing object and set new objects */
303
+ if (ary->arry[pos])
304
+ old = ary->arry[pos];
305
+ ary->arry[pos] = data;
306
+ return old;
307
+ }
308
+
309
+ /* *****************************************************************************
310
+ Array push / shift API
311
+ ***************************************************************************** */
312
+
313
+ /**
314
+ * Pushes an object to the end of the Array. Returns -1 on error.
315
+ */
316
+ FIO_FUNC inline int fio_ary_push(fio_ary_s *ary, void *data) {
317
+ if (!ary->arry)
318
+ fio_ary_new(ary, 0);
319
+ else if (ary->capa <= ary->end)
320
+ fio_ary_getmem(ary, 1);
321
+ ary->arry[ary->end] = data;
322
+ ary->end += 1;
323
+ return 0;
324
+ }
325
+
326
+ /** Pops an object from the end of the Array. */
327
+ FIO_FUNC inline void *fio_ary_pop(fio_ary_s *ary) {
328
+ if (!ary || ary->start == ary->end)
329
+ return NULL;
330
+ ary->end -= 1;
331
+ return ary->arry[ary->end];
332
+ }
333
+
334
+ /**
335
+ * Unshifts an object to the begining of the Array. Returns -1 on error.
336
+ *
337
+ * This could be expensive, causing `memmove`.
338
+ */
339
+ FIO_FUNC inline int fio_ary_unshift(fio_ary_s *ary, void *data) {
340
+ if (!ary->arry)
341
+ fio_ary_new(ary, 0);
342
+ else if (!ary->start)
343
+ fio_ary_getmem(ary, -1);
344
+ ary->start -= 1;
345
+ ary->arry[ary->start] = data;
346
+ return 0;
347
+ }
348
+
349
+ /** Shifts an object from the beginning of the Array. */
350
+ FIO_FUNC inline void *fio_ary_shift(fio_ary_s *ary) {
351
+ if (!ary || ary->start == ary->end)
352
+ return NULL;
353
+ #ifdef __cplusplus
354
+ const size_t pos = ary->start;
355
+ #else
356
+ register const size_t pos = ary->start;
357
+ #endif
358
+ ary->start += 1;
359
+ return ary->arry[pos];
360
+ }
361
+
362
+ /**
363
+ * Single layer iteration using a callback for each entry in the array.
364
+ *
365
+ * The callback task function must accept an the entry data as well as an opaque
366
+ * user pointer.
367
+ *
368
+ * If the callback returns -1, the loop is broken. Any other value is ignored.
369
+ *
370
+ * Returns the relative "stop" position, i.e., the number of items processed +
371
+ * the starting point.
372
+ */
373
+ FIO_FUNC inline size_t fio_ary_each(fio_ary_s *ary, size_t start_at,
374
+ int (*task)(void *data, void *arg),
375
+ void *arg) {
376
+ if (!ary || ary->start == ary->end)
377
+ return 0;
378
+ const size_t start_pos = ary->start;
379
+ start_at += start_pos;
380
+ while (start_at < ary->end && task(ary->arry[start_at++], arg) != -1)
381
+ ;
382
+ return start_at - start_pos;
383
+ }
384
+
385
+ /* *****************************************************************************
386
+ Array compacting (untested)
387
+ ***************************************************************************** */
388
+
389
+ /**
390
+ * Removes any NULL *pointers* from an Array, keeping all other data in the
391
+ * array.
392
+ *
393
+ * This action is O(n) where n in the length of the array.
394
+ * It could get expensive.
395
+ */
396
+ FIO_FUNC inline void fio_ary_compact(fio_ary_s *ary) {
397
+ if (!ary || ary->start == ary->end)
398
+ return;
399
+ register void **pos = ary->arry + ary->start;
400
+ register void **reader = ary->arry + ary->start;
401
+ register void **stop = ary->arry + ary->end;
402
+ while (reader < stop) {
403
+ if (*reader) {
404
+ *pos = *reader;
405
+ pos += 1;
406
+ }
407
+ reader += 1;
408
+ }
409
+ ary->end = (size_t)(pos - ary->arry);
410
+ }
411
+
412
+ #ifdef __cplusplus
413
+ #undef register
414
+ #endif
415
+
416
+ #undef FIO_FUNC
417
+
418
+ #endif