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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +36 -3
- data/bin/config.ru +23 -2
- data/bin/http-hello +1 -1
- data/bin/ws-shootout +5 -0
- data/ext/iodine/defer.c +468 -0
- data/ext/iodine/defer.h +105 -0
- data/ext/iodine/evio.c +263 -0
- data/ext/iodine/evio.h +133 -0
- data/ext/iodine/extconf.rb +2 -1
- data/ext/iodine/facil.c +958 -0
- data/ext/iodine/facil.h +423 -0
- data/ext/iodine/http.c +90 -0
- data/ext/iodine/http.h +50 -12
- data/ext/iodine/http1.c +200 -267
- data/ext/iodine/http1.h +17 -26
- data/ext/iodine/http1_request.c +81 -0
- data/ext/iodine/http1_request.h +58 -0
- data/ext/iodine/http1_response.c +403 -0
- data/ext/iodine/http1_response.h +90 -0
- data/ext/iodine/http1_simple_parser.c +124 -108
- data/ext/iodine/http1_simple_parser.h +8 -3
- data/ext/iodine/http_request.c +104 -0
- data/ext/iodine/http_request.h +58 -102
- data/ext/iodine/http_response.c +212 -208
- data/ext/iodine/http_response.h +89 -252
- data/ext/iodine/iodine_core.c +57 -46
- data/ext/iodine/iodine_core.h +3 -1
- data/ext/iodine/iodine_http.c +105 -81
- data/ext/iodine/iodine_websocket.c +17 -13
- data/ext/iodine/iodine_websocket.h +1 -0
- data/ext/iodine/rb-call.c +9 -7
- data/ext/iodine/{rb-libasync.h → rb-defer.c} +57 -49
- data/ext/iodine/rb-rack-io.c +12 -6
- data/ext/iodine/rb-rack-io.h +1 -1
- data/ext/iodine/rb-registry.c +5 -2
- data/ext/iodine/sock.c +1159 -0
- data/ext/iodine/{libsock.h → sock.h} +138 -142
- data/ext/iodine/spnlock.inc +77 -0
- data/ext/iodine/websockets.c +101 -112
- data/ext/iodine/websockets.h +38 -19
- data/iodine.gemspec +3 -3
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +6 -6
- metadata +23 -19
- data/ext/iodine/http_response_http1.h +0 -382
- data/ext/iodine/libasync.c +0 -570
- data/ext/iodine/libasync.h +0 -122
- data/ext/iodine/libreact.c +0 -350
- data/ext/iodine/libreact.h +0 -244
- data/ext/iodine/libserver.c +0 -957
- data/ext/iodine/libserver.h +0 -481
- data/ext/iodine/libsock.c +0 -1025
- data/ext/iodine/spnlock.h +0 -243
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca71a751e0fec40a40f656630455dabd2c8b7fce
|
4
|
+
data.tar.gz: d80446503accae4cca189bdd7526571f8fb5c5a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4fa45b4c91496c28cf85136b5ca15479d4ef6f615a29a688fe279d7258fae33ab5c7c4c48cf13676ef1b337c4d4e8efb5a2c9bf43d86bf8fe7ea8c628f5ee00
|
7
|
+
data.tar.gz: f3a404f09c1eb2a1cb0d18130ed06001e3831f0483c4b1d0c0a90d98dae863f896cc6ef7e4418c71b6cbf32455b341a245b5294688a86b036f7a7f9e6e3eba11
|
data/CHANGELOG.md
CHANGED
@@ -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.
|
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
|
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)
|
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
|
|
data/bin/config.ru
CHANGED
@@ -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 = '
|
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
|
data/bin/http-hello
CHANGED
data/bin/ws-shootout
CHANGED
@@ -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
|
data/ext/iodine/defer.c
ADDED
@@ -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
|