iodine 0.2.17 → 0.3.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +36 -3
  4. data/bin/config.ru +23 -2
  5. data/bin/http-hello +1 -1
  6. data/bin/ws-shootout +5 -0
  7. data/ext/iodine/defer.c +468 -0
  8. data/ext/iodine/defer.h +105 -0
  9. data/ext/iodine/evio.c +263 -0
  10. data/ext/iodine/evio.h +133 -0
  11. data/ext/iodine/extconf.rb +2 -1
  12. data/ext/iodine/facil.c +958 -0
  13. data/ext/iodine/facil.h +423 -0
  14. data/ext/iodine/http.c +90 -0
  15. data/ext/iodine/http.h +50 -12
  16. data/ext/iodine/http1.c +200 -267
  17. data/ext/iodine/http1.h +17 -26
  18. data/ext/iodine/http1_request.c +81 -0
  19. data/ext/iodine/http1_request.h +58 -0
  20. data/ext/iodine/http1_response.c +403 -0
  21. data/ext/iodine/http1_response.h +90 -0
  22. data/ext/iodine/http1_simple_parser.c +124 -108
  23. data/ext/iodine/http1_simple_parser.h +8 -3
  24. data/ext/iodine/http_request.c +104 -0
  25. data/ext/iodine/http_request.h +58 -102
  26. data/ext/iodine/http_response.c +212 -208
  27. data/ext/iodine/http_response.h +89 -252
  28. data/ext/iodine/iodine_core.c +57 -46
  29. data/ext/iodine/iodine_core.h +3 -1
  30. data/ext/iodine/iodine_http.c +105 -81
  31. data/ext/iodine/iodine_websocket.c +17 -13
  32. data/ext/iodine/iodine_websocket.h +1 -0
  33. data/ext/iodine/rb-call.c +9 -7
  34. data/ext/iodine/{rb-libasync.h → rb-defer.c} +57 -49
  35. data/ext/iodine/rb-rack-io.c +12 -6
  36. data/ext/iodine/rb-rack-io.h +1 -1
  37. data/ext/iodine/rb-registry.c +5 -2
  38. data/ext/iodine/sock.c +1159 -0
  39. data/ext/iodine/{libsock.h → sock.h} +138 -142
  40. data/ext/iodine/spnlock.inc +77 -0
  41. data/ext/iodine/websockets.c +101 -112
  42. data/ext/iodine/websockets.h +38 -19
  43. data/iodine.gemspec +3 -3
  44. data/lib/iodine/version.rb +1 -1
  45. data/lib/rack/handler/iodine.rb +6 -6
  46. metadata +23 -19
  47. data/ext/iodine/http_response_http1.h +0 -382
  48. data/ext/iodine/libasync.c +0 -570
  49. data/ext/iodine/libasync.h +0 -122
  50. data/ext/iodine/libreact.c +0 -350
  51. data/ext/iodine/libreact.h +0 -244
  52. data/ext/iodine/libserver.c +0 -957
  53. data/ext/iodine/libserver.h +0 -481
  54. data/ext/iodine/libsock.c +0 -1025
  55. data/ext/iodine/spnlock.h +0 -243
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de923f79329ea2c150786d10000b43b6378612e3
4
- data.tar.gz: 97749ca95384e2e5b0487c4bfda183690440257c
3
+ metadata.gz: ca71a751e0fec40a40f656630455dabd2c8b7fce
4
+ data.tar.gz: d80446503accae4cca189bdd7526571f8fb5c5a9
5
5
  SHA512:
6
- metadata.gz: 40b2d878c483d077421eab48b4be2b02f6c3bb1ebf32cd43c9fc6007fdae110828df6b6d02867d54bf6ae524e3762dd8d9f95277712038a64e1727f00e3a1153
7
- data.tar.gz: 289d7f82bd5f2a67621ada01dfc56143ba62c5dbeafcd0b4c5a544a9ba7c992b2c4dff5322b7b0fe21e656f3b9913716d8b523d0c708325028c54fc2a2cfa0bf
6
+ metadata.gz: c4fa45b4c91496c28cf85136b5ca15479d4ef6f615a29a688fe279d7258fae33ab5c7c4c48cf13676ef1b337c4d4e8efb5a2c9bf43d86bf8fe7ea8c628f5ee00
7
+ data.tar.gz: f3a404f09c1eb2a1cb0d18130ed06001e3831f0483c4b1d0c0a90d98dae863f896cc6ef7e4418c71b6cbf32455b341a245b5294688a86b036f7a7f9e6e3eba11
@@ -8,6 +8,12 @@ Please notice that this change log contains changes for upcoming releases as wel
8
8
 
9
9
  ***
10
10
 
11
+ #### Change log v.0.3.0
12
+
13
+ **`facil.io` C Core Update**: The C library core that drives Iodine [`facil.io`](http://facil.io) was updated to version 0.4.0 and Iodine follows closely on the heels of this update. The transition was easy enough and the API remains unchanged... but because the performance gain was so big and because it's a new code base, we opted to bump the minor release version.
14
+
15
+ ***
16
+
11
17
  #### Change log v.0.2.17
12
18
 
13
19
  **Performance**: Enhanced Performance for single threaded / blocking applications by adding a dedicated IO thread. This is related to issue #14.
data/README.md CHANGED
@@ -31,7 +31,7 @@ Iodine includes a light and fast HTTP and Websocket server written in C that was
31
31
  Using the Iodine server is easy, simply add Iodine as a gem to your Rack application:
32
32
 
33
33
  ```ruby
34
- gem 'iodine', '>=0.2'
34
+ gem 'iodine', '>=0.3'
35
35
  ```
36
36
 
37
37
  Iodine will calculate, when possible, a good enough default concurrency model for fast applications... this might not fit your application if you use database access or other blocking calls.
@@ -225,15 +225,48 @@ Iodine::Rack.run My_Broadcast
225
225
 
226
226
  Of course, if you still want to use Rack's `hijack` API, Iodine will support you - but be aware that you will need to implement your own reactor and thread pool for any sockets you hijack, as well as a socket buffer for non-blocking `write` operations (why do that when you can write a protocol object and have the main reactor manage the socket?).
227
227
 
228
+ ### Performance oriented design - but safety first
229
+
230
+ Iodine is an evened server, similar in it's architecture to `nginx` and `puma`. It's different than the simple "thread-per-client" design that is often taught when we begin to learn about network programming.
231
+
232
+ By leveraging `epoll` (on Linux) and `kqueue` (on BSD), iodine can listen to multiple network events on multiple sockets using a single thread.
233
+
234
+ All these events go into a task queue, together with the application events and any user generated tasks, such as ones scheduled by [`Iodine.run`](http://www.rubydoc.info/github/boazsegev/iodine/Iodine#run-class_method).
235
+
236
+ In pseudo-code, this might look like this
237
+
238
+ ```ruby
239
+ QUEUE = Queue.new
240
+
241
+ def server_cycle
242
+ QUEUE << get_next_32_socket_events # these events schedule the proper user code to run
243
+ QUEUE << [server]
244
+ end
245
+
246
+ def run_server
247
+ while ((event = QUEUE.pop))
248
+ event.shift.call(*event)
249
+ end
250
+ end
251
+ ```
252
+
253
+ In pure Ruby (without using C extensions or Java), it's possible to do the same by using `select`... and although `select` has some issues, it works well for smaller concurrency levels.
254
+
255
+ The server events are fairly fast and fragmented (longer code is fragmented across multiple events), so one thread is enough to run the server including it's static file service and everything... but single threaded mode should probably be avoided.
256
+
257
+ The thread pool is there to help slow user code. It's very common that the application's code will run slower and require external resources (i.e., databases, a pub/sub service, etc'). This slow code could "starve" the server (that is patiently waiting to run it's tasks on the same thread) - which is why a thread pool is often necessary.
258
+
259
+ The slower your application code, the more threads you will need to keep the server running smoothly.
260
+
228
261
  ### How does it compare to other servers?
229
262
 
230
- Personally, after looking around, the only comparable servers are Puma and Passenger (the open source version), which Iodine significantly outperformed on my tests.
263
+ Personally, after looking around, the only comparable servers are Puma and Passenger, which Iodine significantly outperformed on my tests (I didn't test Passenger's enterprise version).
231
264
 
232
265
  Since the HTTP and Websocket parsers are written in C (with no RegExp), they're fairly fast.
233
266
 
234
267
  Also, Iodine's core and parsers are running outside of Ruby's global lock, meaning that they enjoy true concurrency before entering the Ruby layer (your application) - this offers Iodine a big advantage over other Ruby servers.
235
268
 
236
- Another assumption Iodine makes is that it is behind a load balancer / proxy (which is the normal way Ruby applications are deployed) - this allows Iodine to disregard header validity checks (we're not checking for invalid characters) which speeds up the parsing process even further.
269
+ Another assumption Iodine makes is that it is behind a load balancer / proxy (which is the normal way Ruby applications are deployed) - this allows Iodine to disregard header validity checks (we're not checking for invalid characters) and focus it's resources on other security and performance concerns.
237
270
 
238
271
  I recommend benchmarking the performance for yourself using `wrk` or `ab`:
239
272
 
@@ -8,7 +8,8 @@ require 'rack/lint'
8
8
  # This value (app) sets which of the different applications will run.
9
9
  #
10
10
  # Valid values are "hello", "slow" (debugs env values), "simple"
11
- app = 'big'
11
+ app = 'hello'
12
+
12
13
  # This is a simple Hello World Rack application, for benchmarking.
13
14
  HELLO_RESPONSE = [200, { 'Content-Type'.freeze => 'text/html'.freeze,
14
15
  'Content-Length'.freeze => '16'.freeze }.freeze,
@@ -19,7 +20,6 @@ hello = proc do |_env|
19
20
  end
20
21
 
21
22
  slow = proc do |env|
22
- out = "ENV:\n<br/>\n#{env.to_a.map { |h| "#{h[0]}: #{h[1]}" } .join "\n<br/>\n"}\n<br/>\n"
23
23
  request = Rack::Request.new(env)
24
24
  # Benchmark.bm do |bm|
25
25
  # bm.report('Reading from env Hash to a string X 1000') { 1000.times { out = "ENV:\r\n#{env.to_a.map { |h| "#{h[0]}: #{h[1]}" } .join "\n"}\n" } }
@@ -27,7 +27,10 @@ slow = proc do |env|
27
27
  # end
28
28
  if request.path_info == '/source'.freeze
29
29
  [200, { 'X-Sendfile' => File.expand_path(__FILE__) }, []]
30
+ elsif request.path_info == '/file'.freeze
31
+ [200, { 'X-Header' => 'This was a Rack::Sendfile response' }, File.open(__FILE__)]
30
32
  else
33
+ out = "ENV:\n<br/>\n#{env.to_a.map { |h| "#{h[0]}: #{h[1]}" } .join "\n<br/>\n"}\n<br/>\n"
31
34
  out += "\n<br/>\nRequest Path: #{request.path_info}\n<br/>\nParams:\n<br/>\n#{request.params.to_a.map { |h| "#{h[0]}: #{h[1]}" } .join "\n<br/>\n"}\n<br/>\n" unless request.params.empty?
32
35
  [200, { 'Content-Type'.freeze => 'text/html'.freeze,
33
36
  'Content-Length'.freeze => out.length.to_s },
@@ -49,11 +52,17 @@ simple = proc do |env|
49
52
  end
50
53
 
51
54
  logo_png = nil
55
+
52
56
  big = proc do |_env|
53
57
  logo_png ||= IO.binread '../logo.png'
54
58
  [200, { 'Content-Length'.freeze => logo_png.length.to_s , 'Content-Type'.freeze => 'image/png'.freeze}, [logo_png]]
55
59
  end
56
60
 
61
+ bigX = proc do |_env|
62
+ logo_png ||= IO.binread '../logo.png'
63
+ [200, { 'Content-Length'.freeze => logo_png.length.to_s , 'Content-Type'.freeze => 'image/png'.freeze, 'X-Sendfile'.freeze => '../logo.png'.freeze}, [logo_png]]
64
+ end
65
+
57
66
  case app
58
67
  when 'simple'
59
68
  use Rack::Sendfile
@@ -62,6 +71,8 @@ when 'hello'
62
71
  run hello
63
72
  when 'big'
64
73
  run big
74
+ when 'bigX'
75
+ run bigX
65
76
  when 'slow'
66
77
  use Rack::Lint
67
78
  run slow
@@ -71,3 +82,13 @@ end
71
82
 
72
83
  # ab -n 1000000 -c 2000 -k http://127.0.0.1:3000/
73
84
  # wrk -c400 -d5 -t12 http://localhost:3000/
85
+ #
86
+ # def cycle
87
+ # puts `wrk -c4000 -d5 -t12 http://localhost:3000/`
88
+ # sleep(2)
89
+ # puts `wrk -c4000 -d5 -t12 http://localhost:3000/source`
90
+ # sleep(3)
91
+ # puts `wrk -c200 -d5 -t12 http://localhost:3000/file`
92
+ # true
93
+ # end
94
+ # sleep(10) while cycle
@@ -13,7 +13,7 @@ require 'rack'
13
13
 
14
14
  # create the server object and setup any settings we might need.
15
15
  Iodine::Rack
16
- Iodine.threads ||= 16
16
+ Iodine.threads ||= 1
17
17
  Iodine.processes ||= 1 # 4
18
18
  Iodine::Rack.public = '~/Documents/Scratch'
19
19
  count = 2
@@ -33,6 +33,8 @@ class ShootoutApp
33
33
  msg = {type: 'broadcast', payload: payload}.to_json
34
34
  # Iodine::Websocket.each {|ws| ws.write msg}
35
35
  Iodine::Websocket.each_write(msg) # {|ws| true }
36
+ # each_write(msg) # {|ws| true }
37
+ # write msg
36
38
  write({type: "broadcastResult", payload: payload}.to_json)
37
39
  end
38
40
  end
@@ -46,6 +48,9 @@ Iodine::Rack.public = nil
46
48
  Iodine::Rack.app = ShootoutApp
47
49
  Iodine.start
48
50
 
51
+ # websocket-bench broadcast ws://127.0.0.1:3000/ --concurrent 10 --sample-size 100 --step-size 1000 --limit-percentile 95 --limit-rtt 250ms --initial-clients 1000
52
+
53
+ #
49
54
  # server.on_http= Proc.new do |env|
50
55
  # # [200, {"Content-Length".freeze => "12".freeze}, ["Hello World!".freeze]];
51
56
  # if env["HTTP_UPGRADE".freeze] =~ /websocket/i.freeze
@@ -0,0 +1,468 @@
1
+ /*
2
+ Copyright: Boaz Segev, 2016-2017
3
+ License: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+ #include "spnlock.inc"
8
+
9
+ #include "defer.h"
10
+
11
+ #include <errno.h>
12
+ #include <signal.h>
13
+ #include <stdio.h>
14
+ #include <sys/types.h>
15
+ #include <sys/wait.h>
16
+ #include <unistd.h>
17
+
18
+ /* *****************************************************************************
19
+ Compile time settings
20
+ ***************************************************************************** */
21
+
22
+ #ifndef DEFER_QUEUE_BUFFER
23
+ #define DEFER_QUEUE_BUFFER 4096
24
+ #endif
25
+ #ifndef DEFER_THROTTLE
26
+ #define DEFER_THROTTLE 8388608UL
27
+ #endif
28
+
29
+ /* *****************************************************************************
30
+ Data Structures
31
+ ***************************************************************************** */
32
+
33
+ typedef struct {
34
+ void (*func)(void *, void *);
35
+ void *arg1;
36
+ void *arg2;
37
+ } task_s;
38
+
39
+ typedef struct task_node_s {
40
+ task_s task;
41
+ struct task_node_s *next;
42
+ } task_node_s;
43
+
44
+ static task_node_s tasks_buffer[DEFER_QUEUE_BUFFER];
45
+
46
+ static struct {
47
+ task_node_s *first;
48
+ task_node_s **last;
49
+ task_node_s *pool;
50
+ spn_lock_i lock;
51
+ unsigned char initialized;
52
+ } deferred = {.first = NULL,
53
+ .last = &deferred.first,
54
+ .pool = NULL,
55
+ .lock = 0,
56
+ .initialized = 0};
57
+
58
+ /* *****************************************************************************
59
+ API
60
+ ***************************************************************************** */
61
+
62
+ /** Defer an execution of a function for later. */
63
+ int defer(void (*func)(void *, void *), void *arg1, void *arg2) {
64
+ if (!func)
65
+ goto call_error;
66
+ task_node_s *task;
67
+ spn_lock(&deferred.lock);
68
+ if (deferred.pool) {
69
+ task = deferred.pool;
70
+ deferred.pool = deferred.pool->next;
71
+ } else if (deferred.initialized) {
72
+ task = malloc(sizeof(task_node_s));
73
+ if (!task)
74
+ goto error;
75
+ } else
76
+ goto initialize;
77
+ schedule:
78
+ *deferred.last = task;
79
+ deferred.last = &task->next;
80
+ task->task.func = func;
81
+ task->task.arg1 = arg1;
82
+ task->task.arg2 = arg2;
83
+ task->next = NULL;
84
+ spn_unlock(&deferred.lock);
85
+ return 0;
86
+ error:
87
+ spn_unlock(&deferred.lock);
88
+ perror("ERROR CRITICAL: defer can't allocate task");
89
+ exit(9);
90
+ call_error:
91
+ return -1;
92
+ initialize:
93
+ deferred.initialized = 1;
94
+ task = tasks_buffer;
95
+ deferred.pool = tasks_buffer + 1;
96
+ for (size_t i = 1; i < (DEFER_QUEUE_BUFFER - 1); i++) {
97
+ tasks_buffer[i].next = &tasks_buffer[i + 1];
98
+ }
99
+ tasks_buffer[DEFER_QUEUE_BUFFER - 1].next = NULL;
100
+ goto schedule;
101
+ }
102
+
103
+ /** Performs all deferred functions until the queue had been depleted. */
104
+ void defer_perform(void) {
105
+ task_node_s *tmp;
106
+ task_s task;
107
+ restart:
108
+ spn_lock(&deferred.lock);
109
+ tmp = deferred.first;
110
+ if (tmp) {
111
+ deferred.first = tmp->next;
112
+ if (!deferred.first)
113
+ deferred.last = &deferred.first;
114
+ task = tmp->task;
115
+ if (tmp >= tasks_buffer && tmp < tasks_buffer + DEFER_QUEUE_BUFFER) {
116
+ tmp->next = deferred.pool;
117
+ deferred.pool = tmp;
118
+ } else {
119
+ free(tmp);
120
+ }
121
+ spn_unlock(&deferred.lock);
122
+ task.func(task.arg1, task.arg2);
123
+ goto restart;
124
+ } else
125
+ spn_unlock(&deferred.lock);
126
+ }
127
+
128
+ /** returns true if there are deferred functions waiting for execution. */
129
+ int defer_has_queue(void) { return deferred.first != NULL; }
130
+
131
+ /* *****************************************************************************
132
+ Thread Pool Support
133
+ ***************************************************************************** */
134
+
135
+ #if defined(__unix__) || defined(__APPLE__) || defined(__linux__) || \
136
+ defined(DEBUG)
137
+ #include <pthread.h>
138
+
139
+ #pragma weak defer_new_thread
140
+ void *defer_new_thread(void *(*thread_func)(void *), pool_pt arg) {
141
+ pthread_t *thread = malloc(sizeof(*thread));
142
+ if (thread == NULL || pthread_create(thread, NULL, thread_func, (void *)arg))
143
+ goto error;
144
+ return thread;
145
+ error:
146
+ free(thread);
147
+ return NULL;
148
+ }
149
+
150
+ #pragma weak defer_join_thread
151
+ int defer_join_thread(void *p_thr) {
152
+ if (!p_thr)
153
+ return -1;
154
+ pthread_join(*(pthread_t *)p_thr, NULL);
155
+ free(p_thr);
156
+ return 0;
157
+ }
158
+
159
+ #else /* No pthreads... BYO thread implementation. */
160
+
161
+ #pragma weak defer_new_thread
162
+ void *defer_new_thread(void *(*thread_func)(void *), void *arg) {
163
+ (void)thread_func;
164
+ (void)arg;
165
+ return NULL;
166
+ }
167
+ #pragma weak defer_join_thread
168
+ int defer_join_thread(void *p_thr) {
169
+ (void)p_thr;
170
+ return -1;
171
+ }
172
+
173
+ #endif /* DEBUG || pthread default */
174
+
175
+ struct defer_pool {
176
+ unsigned int flag;
177
+ unsigned int count;
178
+ void *threads[];
179
+ };
180
+
181
+ static void *defer_worker_thread(void *pool) {
182
+ signal(SIGPIPE, SIG_IGN);
183
+ size_t throttle = (((pool_pt)pool)->count & 127) * DEFER_THROTTLE;
184
+ do {
185
+ throttle_thread(throttle);
186
+ defer_perform();
187
+ } while (((pool_pt)pool)->flag);
188
+ return NULL;
189
+ }
190
+
191
+ void defer_pool_stop(pool_pt pool) { pool->flag = 0; }
192
+
193
+ int defer_pool_is_active(pool_pt pool) { return pool->flag; }
194
+
195
+ void defer_pool_wait(pool_pt pool) {
196
+ while (pool->count) {
197
+ pool->count--;
198
+ defer_join_thread(pool->threads[pool->count]);
199
+ }
200
+ }
201
+
202
+ static inline pool_pt defer_pool_initialize(unsigned int thread_count,
203
+ pool_pt pool) {
204
+ pool->flag = 1;
205
+ pool->count = 0;
206
+ while (pool->count < thread_count &&
207
+ (pool->threads[pool->count] =
208
+ defer_new_thread(defer_worker_thread, pool)))
209
+ pool->count++;
210
+ if (pool->count == thread_count) {
211
+ return pool;
212
+ }
213
+ defer_pool_stop(pool);
214
+ return NULL;
215
+ }
216
+
217
+ pool_pt defer_pool_start(unsigned int thread_count) {
218
+ if (thread_count == 0)
219
+ return NULL;
220
+ pool_pt pool = malloc(sizeof(*pool) + (thread_count * sizeof(void *)));
221
+ if (!pool)
222
+ return NULL;
223
+ return defer_pool_initialize(thread_count, pool);
224
+ }
225
+
226
+ /* *****************************************************************************
227
+ Child Process support (`fork`)
228
+ ***************************************************************************** */
229
+
230
+ static pool_pt forked_pool;
231
+
232
+ static void sig_int_handler(int sig) {
233
+ if (sig != SIGINT)
234
+ return;
235
+ if (!forked_pool)
236
+ return;
237
+ defer_pool_stop(forked_pool);
238
+ }
239
+
240
+ /* *
241
+ Zombie Reaping
242
+ With thanks to Dr Graham D Shaw.
243
+ http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
244
+ */
245
+
246
+ void reap_child_handler(int sig) {
247
+ (void)(sig);
248
+ int old_errno = errno;
249
+ while (waitpid(-1, NULL, WNOHANG) > 0)
250
+ ;
251
+ errno = old_errno;
252
+ }
253
+
254
+ inline static void reap_children(void) {
255
+ struct sigaction sa;
256
+ sa.sa_handler = reap_child_handler;
257
+ sigemptyset(&sa.sa_mask);
258
+ sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
259
+ if (sigaction(SIGCHLD, &sa, 0) == -1) {
260
+ perror("Child reaping initialization failed");
261
+ exit(1);
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Forks the process, starts up a thread pool and waits for all tasks to run.
267
+ * All existing tasks will run in all processes (multiple times).
268
+ *
269
+ * Returns 0 on success, -1 on error and a positive number if this is a child
270
+ * process that was forked.
271
+ */
272
+ int defer_perform_in_fork(unsigned int process_count,
273
+ unsigned int thread_count) {
274
+ struct sigaction act, old, old_term, old_pipe;
275
+ pid_t *pids = NULL;
276
+ int ret = 0;
277
+ unsigned int pids_count;
278
+
279
+ act.sa_handler = sig_int_handler;
280
+ sigemptyset(&act.sa_mask);
281
+ act.sa_flags = SA_RESTART | SA_NOCLDSTOP;
282
+
283
+ if (sigaction(SIGINT, &act, &old)) {
284
+ perror("couldn't set signal handler");
285
+ goto finish;
286
+ };
287
+ if (sigaction(SIGTERM, &act, &old_term)) {
288
+ perror("couldn't set signal handler");
289
+ goto finish;
290
+ };
291
+ act.sa_handler = SIG_IGN;
292
+ if (sigaction(SIGPIPE, &act, &old_pipe)) {
293
+ perror("couldn't set signal handler");
294
+ goto finish;
295
+ };
296
+ reap_children();
297
+
298
+ if (!process_count)
299
+ process_count = 1;
300
+ --process_count;
301
+ pids = calloc(process_count, sizeof(*pids));
302
+ if (process_count && !pids)
303
+ goto finish;
304
+ for (pids_count = 0; pids_count < process_count; pids_count++) {
305
+ if (!(pids[pids_count] = fork())) {
306
+ forked_pool = defer_pool_start(thread_count);
307
+ defer_pool_wait(forked_pool);
308
+ defer_perform();
309
+ defer_perform();
310
+ return 1;
311
+ }
312
+ if (pids[pids_count] == -1) {
313
+ ret = -1;
314
+ goto finish;
315
+ }
316
+ }
317
+ pids_count++;
318
+ forked_pool = defer_pool_start(thread_count);
319
+ defer_pool_wait(forked_pool);
320
+ forked_pool = NULL;
321
+ defer_perform();
322
+ finish:
323
+ if (pids) {
324
+ for (size_t j = 0; j < pids_count; j++) {
325
+ kill(pids[j], SIGINT);
326
+ }
327
+ for (size_t j = 0; j < pids_count; j++) {
328
+ waitpid(pids[j], NULL, 0);
329
+ }
330
+ free(pids);
331
+ }
332
+ sigaction(SIGINT, &old, &act);
333
+ sigaction(SIGTERM, &old_term, &act);
334
+ sigaction(SIGTERM, &old_pipe, &act);
335
+ return ret;
336
+ }
337
+
338
+ /** Returns TRUE (1) if the forked thread pool hadn't been signaled to finish
339
+ * up. */
340
+ int defer_fork_is_active(void) { return forked_pool && forked_pool->flag; }
341
+
342
+ /* *****************************************************************************
343
+ Test
344
+ ***************************************************************************** */
345
+ #ifdef DEBUG
346
+
347
+ #include <stdio.h>
348
+
349
+ #include <pthread.h>
350
+ #define DEFER_TEST_THREAD_COUNT 128
351
+
352
+ static spn_lock_i i_lock = 0;
353
+ static size_t i_count = 0;
354
+
355
+ static void sample_task(void *unused, void *unused2) {
356
+ (void)(unused);
357
+ (void)(unused2);
358
+ spn_lock(&i_lock);
359
+ i_count++;
360
+ spn_unlock(&i_lock);
361
+ }
362
+
363
+ static void sched_sample_task(void *unused, void *unused2) {
364
+ (void)(unused);
365
+ (void)(unused2);
366
+ for (size_t i = 0; i < 1024; i++) {
367
+ defer(sample_task, NULL, NULL);
368
+ }
369
+ }
370
+
371
+ static void thrd_sched(void *unused, void *unused2) {
372
+ for (size_t i = 0; i < (1024 / DEFER_TEST_THREAD_COUNT); i++) {
373
+ sched_sample_task(unused, unused2);
374
+ }
375
+ }
376
+
377
+ static void text_task_text(void *unused, void *unused2) {
378
+ (void)(unused);
379
+ (void)(unused2);
380
+ spn_lock(&i_lock);
381
+ fprintf(stderr, "this text should print before defer_perform returns\n");
382
+ spn_unlock(&i_lock);
383
+ }
384
+
385
+ static void text_task(void *a1, void *a2) {
386
+ static const struct timespec tm = {.tv_sec = 2};
387
+ nanosleep(&tm, NULL);
388
+ defer(text_task_text, a1, a2);
389
+ }
390
+
391
+ static void pid_task(void *arg, void *unused2) {
392
+ (void)(unused2);
393
+ fprintf(stderr, "* %d pid is going to sleep... (%s)\n", getpid(),
394
+ arg ? (char *)arg : "unknown");
395
+ }
396
+
397
+ void defer_test(void) {
398
+ time_t start, end;
399
+ fprintf(stderr, "Starting defer testing\n");
400
+
401
+ spn_lock(&i_lock);
402
+ i_count = 0;
403
+ spn_unlock(&i_lock);
404
+ start = clock();
405
+ for (size_t i = 0; i < 1024; i++) {
406
+ defer(sched_sample_task, NULL, NULL);
407
+ }
408
+ defer_perform();
409
+ end = clock();
410
+ fprintf(stderr, "Defer single thread: %lu cycles with i_count = %lu\n",
411
+ end - start, i_count);
412
+
413
+ spn_lock(&i_lock);
414
+ i_count = 0;
415
+ spn_unlock(&i_lock);
416
+ start = clock();
417
+ pool_pt pool = defer_pool_start(DEFER_TEST_THREAD_COUNT);
418
+ if (pool) {
419
+ for (size_t i = 0; i < DEFER_TEST_THREAD_COUNT; i++) {
420
+ defer(thrd_sched, NULL, NULL);
421
+ }
422
+ // defer((void (*)(void *))defer_pool_stop, pool);
423
+ defer_pool_stop(pool);
424
+ defer_pool_wait(pool);
425
+ end = clock();
426
+ fprintf(stderr,
427
+ "Defer multi-thread (%d threads): %lu cycles with i_count = %lu\n",
428
+ DEFER_TEST_THREAD_COUNT, end - start, i_count);
429
+ } else
430
+ fprintf(stderr, "Defer multi-thread: FAILED!\n");
431
+
432
+ spn_lock(&i_lock);
433
+ i_count = 0;
434
+ spn_unlock(&i_lock);
435
+ start = clock();
436
+ for (size_t i = 0; i < 1024; i++) {
437
+ defer(sched_sample_task, NULL, NULL);
438
+ }
439
+ defer_perform();
440
+ end = clock();
441
+ fprintf(stderr, "Defer single thread (2): %lu cycles with i_count = %lu\n",
442
+ end - start, i_count);
443
+
444
+ fprintf(stderr, "calling defer_perform.\n");
445
+ defer(text_task, NULL, NULL);
446
+ defer_perform();
447
+ fprintf(stderr, "defer_perform returned. i_count = %lu\n", i_count);
448
+ size_t pool_count = 0;
449
+ task_node_s *pos = deferred.pool;
450
+ while (pos) {
451
+ pool_count++;
452
+ pos = pos->next;
453
+ }
454
+ fprintf(stderr, "defer pool count %lu/%d (%s)\n", pool_count,
455
+ DEFER_QUEUE_BUFFER,
456
+ pool_count == DEFER_QUEUE_BUFFER ? "pass" : "FAILED");
457
+ fprintf(stderr, "press ^C to finish PID test\n");
458
+ defer(pid_task, "pid test", NULL);
459
+ if (defer_perform_in_fork(4, 64) > 0) {
460
+ fprintf(stderr, "* %d finished\n", getpid());
461
+ exit(0);
462
+ };
463
+ fprintf(stderr,
464
+ " === Defer pool memory footprint %lu X %d = %lu bytes ===\n",
465
+ sizeof(task_node_s), DEFER_QUEUE_BUFFER, sizeof(tasks_buffer));
466
+ }
467
+
468
+ #endif