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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +22 -0
- data/LIMITS.md +19 -9
- data/README.md +92 -77
- data/SPEC-PubSub-Draft.md +113 -0
- data/SPEC-Websocket-Draft.md +127 -143
- data/bin/http-hello +0 -1
- data/bin/raw-rbhttp +1 -1
- data/bin/raw_broadcast +8 -10
- data/bin/updated api +2 -2
- data/bin/ws-broadcast +2 -4
- data/bin/ws-echo +2 -2
- data/examples/config.ru +13 -13
- data/examples/echo.ru +5 -6
- data/examples/hello.ru +2 -3
- data/examples/info.md +316 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/redis.ru +9 -9
- data/examples/shootout.ru +45 -11
- data/ext/iodine/defer.c +194 -297
- data/ext/iodine/defer.h +61 -53
- data/ext/iodine/evio.c +0 -260
- data/ext/iodine/evio.h +50 -22
- data/ext/iodine/evio_callbacks.c +26 -0
- data/ext/iodine/evio_epoll.c +251 -0
- data/ext/iodine/evio_kqueue.c +193 -0
- data/ext/iodine/extconf.rb +1 -1
- data/ext/iodine/facil.c +1420 -542
- data/ext/iodine/facil.h +151 -64
- data/ext/iodine/fio_ary.h +418 -0
- data/ext/iodine/{base64.c → fio_base64.c} +33 -24
- data/ext/iodine/{base64.h → fio_base64.h} +6 -7
- data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
- data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
- data/ext/iodine/fio_hashmap.h +759 -0
- data/ext/iodine/fio_json_parser.h +651 -0
- data/ext/iodine/fio_llist.h +257 -0
- data/ext/iodine/fio_mem.c +672 -0
- data/ext/iodine/fio_mem.h +140 -0
- data/ext/iodine/fio_random.c +248 -0
- data/ext/iodine/{random.h → fio_random.h} +11 -14
- data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
- data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
- data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
- data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
- data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
- data/ext/iodine/fio_siphash.h +18 -0
- data/ext/iodine/fio_tmpfile.h +38 -0
- data/ext/iodine/fiobj.h +24 -7
- data/ext/iodine/fiobj4sock.h +23 -0
- data/ext/iodine/fiobj_ary.c +143 -226
- data/ext/iodine/fiobj_ary.h +17 -16
- data/ext/iodine/fiobj_data.c +1160 -0
- data/ext/iodine/fiobj_data.h +164 -0
- data/ext/iodine/fiobj_hash.c +298 -406
- data/ext/iodine/fiobj_hash.h +101 -54
- data/ext/iodine/fiobj_json.c +478 -601
- data/ext/iodine/fiobj_json.h +34 -9
- data/ext/iodine/fiobj_numbers.c +383 -51
- data/ext/iodine/fiobj_numbers.h +87 -11
- data/ext/iodine/fiobj_str.c +423 -184
- data/ext/iodine/fiobj_str.h +81 -32
- data/ext/iodine/fiobject.c +273 -522
- data/ext/iodine/fiobject.h +477 -112
- data/ext/iodine/http.c +2243 -83
- data/ext/iodine/http.h +842 -121
- data/ext/iodine/http1.c +810 -385
- data/ext/iodine/http1.h +16 -39
- data/ext/iodine/http1_parser.c +146 -74
- data/ext/iodine/http1_parser.h +15 -4
- data/ext/iodine/http_internal.c +1258 -0
- data/ext/iodine/http_internal.h +226 -0
- data/ext/iodine/http_mime_parser.h +341 -0
- data/ext/iodine/iodine.c +86 -68
- data/ext/iodine/iodine.h +26 -11
- data/ext/iodine/iodine_helpers.c +8 -7
- data/ext/iodine/iodine_http.c +487 -324
- data/ext/iodine/iodine_json.c +304 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_protocol.c +107 -45
- data/ext/iodine/iodine_pubsub.c +526 -225
- data/ext/iodine/iodine_pubsub.h +10 -0
- data/ext/iodine/iodine_websockets.c +268 -510
- data/ext/iodine/iodine_websockets.h +2 -4
- data/ext/iodine/pubsub.c +726 -432
- data/ext/iodine/pubsub.h +85 -103
- data/ext/iodine/rb-call.c +4 -4
- data/ext/iodine/rb-defer.c +46 -22
- data/ext/iodine/rb-fiobj2rb.h +117 -0
- data/ext/iodine/rb-rack-io.c +73 -238
- data/ext/iodine/rb-rack-io.h +2 -2
- data/ext/iodine/rb-registry.c +35 -93
- data/ext/iodine/rb-registry.h +1 -0
- data/ext/iodine/redis_engine.c +742 -304
- data/ext/iodine/redis_engine.h +42 -39
- data/ext/iodine/resp_parser.h +311 -0
- data/ext/iodine/sock.c +627 -490
- data/ext/iodine/sock.h +345 -297
- data/ext/iodine/spnlock.inc +15 -4
- data/ext/iodine/websocket_parser.h +16 -20
- data/ext/iodine/websockets.c +188 -257
- data/ext/iodine/websockets.h +24 -133
- data/lib/iodine.rb +52 -7
- data/lib/iodine/cli.rb +6 -24
- data/lib/iodine/json.rb +40 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/iodine/websocket.rb +5 -3
- data/lib/rack/handler/iodine.rb +58 -13
- metadata +38 -48
- data/bin/ws-shootout +0 -107
- data/examples/broadcast.ru +0 -56
- data/ext/iodine/bscrypt-common.h +0 -116
- data/ext/iodine/bscrypt.h +0 -49
- data/ext/iodine/fio2resp.c +0 -60
- data/ext/iodine/fio2resp.h +0 -51
- data/ext/iodine/fio_dict.c +0 -446
- data/ext/iodine/fio_dict.h +0 -99
- data/ext/iodine/fio_hash_table.h +0 -370
- data/ext/iodine/fio_list.h +0 -111
- data/ext/iodine/fiobj_internal.h +0 -280
- data/ext/iodine/fiobj_primitives.c +0 -131
- data/ext/iodine/fiobj_primitives.h +0 -55
- data/ext/iodine/fiobj_sym.c +0 -135
- data/ext/iodine/fiobj_sym.h +0 -60
- data/ext/iodine/hex.c +0 -124
- data/ext/iodine/hex.h +0 -70
- data/ext/iodine/http1_request.c +0 -81
- data/ext/iodine/http1_request.h +0 -58
- data/ext/iodine/http1_response.c +0 -417
- data/ext/iodine/http1_response.h +0 -95
- data/ext/iodine/http_request.c +0 -111
- data/ext/iodine/http_request.h +0 -102
- data/ext/iodine/http_response.c +0 -1703
- data/ext/iodine/http_response.h +0 -250
- data/ext/iodine/misc.c +0 -182
- data/ext/iodine/misc.h +0 -74
- data/ext/iodine/random.c +0 -208
- data/ext/iodine/redis_connection.c +0 -278
- data/ext/iodine/redis_connection.h +0 -86
- data/ext/iodine/resp.c +0 -842
- data/ext/iodine/resp.h +0 -261
- data/ext/iodine/siphash.c +0 -154
- data/ext/iodine/siphash.h +0 -22
- data/ext/iodine/xor-crypt.c +0 -193
- data/ext/iodine/xor-crypt.h +0 -107
data/examples/redis.ru
CHANGED
@@ -1,19 +1,18 @@
|
|
1
1
|
# This example implements a Redis pub/sub engine according to the Iodine::PubSub::Engine specifications.
|
2
2
|
#
|
3
|
-
# The engine code is locates at examples/redis_pubsub.rb and it requires the hiredis gem.
|
4
|
-
#
|
5
3
|
# Run this applications on two ports, in two terminals to see the synchronization is action
|
6
4
|
#
|
7
5
|
# REDIS_URL=redis://localhost:6379/0 iodine -t 1 -p 3000 redis.ru
|
8
6
|
# REDIS_URL=redis://localhost:6379/0 iodine -t 1 -p 3030 redis.ru
|
9
7
|
#
|
10
8
|
require 'uri'
|
9
|
+
require 'iodine'
|
11
10
|
# initialize the Redis engine for each Iodine process.
|
12
11
|
if ENV["REDIS_URL"]
|
13
12
|
uri = URI(ENV["REDIS_URL"])
|
14
13
|
Iodine.default_pubsub = Iodine::PubSub::RedisEngine.new(uri.host, uri.port, 0, uri.password)
|
15
14
|
else
|
16
|
-
puts "* No Redis, it's okay, pub/sub will
|
15
|
+
puts "* No Redis, it's okay, pub/sub will support the process cluster."
|
17
16
|
end
|
18
17
|
|
19
18
|
# A simple router - Checks for Websocket Upgrade and answers HTTP.
|
@@ -28,8 +27,9 @@ module MyHTTPRouter
|
|
28
27
|
# this is function will be called by the Rack server (iodine) for every request.
|
29
28
|
def self.call env
|
30
29
|
# check if this is an upgrade request.
|
31
|
-
if(env['upgrade
|
32
|
-
|
30
|
+
if(env['rack.upgrade?'.freeze])
|
31
|
+
puts "SSE connections will not be able te send data, just listen." if(env['rack.upgrade?'.freeze] == :sse)
|
32
|
+
env['rack.upgrade'.freeze] = WS_RedisPubSub.new(env['PATH_INFO'] && env['PATH_INFO'].length > 1 ? env['PATH_INFO'][1..-1] : "guest")
|
33
33
|
return WS_RESPONSE
|
34
34
|
end
|
35
35
|
# simply return the RESPONSE object, no matter what request was received.
|
@@ -44,9 +44,9 @@ class WS_RedisPubSub
|
|
44
44
|
end
|
45
45
|
# seng a message to new clients.
|
46
46
|
def on_open
|
47
|
-
subscribe
|
47
|
+
subscribe "chat"
|
48
48
|
# let everyone know we arrived
|
49
|
-
|
49
|
+
publish "chat", "#{@name} entered the chat."
|
50
50
|
end
|
51
51
|
# send a message, letting the client know the server is suggunt down.
|
52
52
|
def on_shutdown
|
@@ -54,11 +54,11 @@ class WS_RedisPubSub
|
|
54
54
|
end
|
55
55
|
# perform the echo
|
56
56
|
def on_message data
|
57
|
-
publish
|
57
|
+
publish "chat", "#{@name}: #{data}"
|
58
58
|
end
|
59
59
|
def on_close
|
60
60
|
# let everyone know we left
|
61
|
-
publish
|
61
|
+
publish "chat", "#{@name} left the chat."
|
62
62
|
# we don't need to unsubscribe, subscriptions are cleared automatically once the connection is closed.
|
63
63
|
end
|
64
64
|
end
|
data/examples/shootout.ru
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'iodine'
|
2
|
+
require 'json'
|
2
3
|
|
3
4
|
# ON_IDLE = proc { Iodine::Base.db_print_registry ; Iodine.on_idle(&ON_IDLE) }
|
4
5
|
# ON_IDLE.call
|
@@ -6,24 +7,49 @@ require 'iodine'
|
|
6
7
|
class ShootoutApp
|
7
8
|
# the default HTTP response
|
8
9
|
def self.call(env)
|
9
|
-
if
|
10
|
-
|
11
|
-
|
10
|
+
if(Iodine::VERSION >= "0.5.0")
|
11
|
+
if(env['rack.upgrade?'.freeze] == :websocket)
|
12
|
+
env['rack.upgrade'.freeze] = ShootoutApp.new
|
13
|
+
return [0, {}, []]
|
14
|
+
end
|
15
|
+
else
|
16
|
+
if(env['upgrade.websocket?'.freeze])
|
17
|
+
env['upgrade.websocket'.freeze] = ShootoutApp.new
|
18
|
+
return [0, {}, []]
|
19
|
+
end
|
12
20
|
end
|
13
|
-
out =
|
21
|
+
out = []
|
22
|
+
len = 0
|
23
|
+
out << "ENV:\n"
|
24
|
+
len += 5
|
25
|
+
env.each { |k, v| out << "#{k}: #{v}\n" ; len += out[-1].length }
|
14
26
|
request = Rack::Request.new(env)
|
15
|
-
out
|
16
|
-
|
27
|
+
out << "\nRequest Path: #{request.path_info}\n"
|
28
|
+
len += out[-1].length
|
29
|
+
unless request.params.empty?
|
30
|
+
out << "Params:\n"
|
31
|
+
len += out[-1].length
|
32
|
+
request.params.each { |k,v| out << "#{k}: #{v}\n" ; len += out[-1].length }
|
33
|
+
end
|
34
|
+
[200, { 'Content-Length' => len.to_s, 'Content-Type' => 'text/plain; charset=UTF-8;' }, out]
|
17
35
|
end
|
18
36
|
# We'll base the shootout on the internal Pub/Sub service.
|
19
37
|
# It's slower than writing to every socket a pre-parsed message, but it's closer
|
20
38
|
# to real-life implementations.
|
21
39
|
def on_open
|
22
|
-
|
40
|
+
if(Iodine::VERSION >= "0.5.0")
|
41
|
+
subscribe :shootout, as: :binary
|
42
|
+
else
|
43
|
+
subscribe channel: :shootout
|
44
|
+
end
|
23
45
|
end
|
24
46
|
def on_message data
|
25
47
|
if data[0] == 'b' # binary
|
26
|
-
|
48
|
+
if(Iodine::VERSION >= "0.5.0")
|
49
|
+
publish :shootout, data
|
50
|
+
else
|
51
|
+
publish channel: :shootout, message: data
|
52
|
+
end
|
27
53
|
data[0] = 'r'
|
28
54
|
write data
|
29
55
|
return
|
@@ -34,7 +60,11 @@ class ShootoutApp
|
|
34
60
|
else
|
35
61
|
# data = {type: 'broadcast', payload: payload}.to_json
|
36
62
|
# broadcast :push2client, data
|
37
|
-
|
63
|
+
if(Iodine::VERSION >= "0.5.0")
|
64
|
+
publish :shootout, {type: 'broadcast', payload: payload}.to_json
|
65
|
+
else
|
66
|
+
publish channel: :shootout, message: {type: 'broadcast', payload: payload}.to_json
|
67
|
+
end
|
38
68
|
write({type: "broadcastResult", payload: payload}.to_json)
|
39
69
|
end
|
40
70
|
rescue
|
@@ -42,12 +72,16 @@ class ShootoutApp
|
|
42
72
|
end
|
43
73
|
end
|
44
74
|
|
75
|
+
# if defined?(Iodine)
|
76
|
+
# Iodine.run_every(5000) { Iodine::Base.db_print_registry }
|
77
|
+
# end
|
78
|
+
|
45
79
|
run ShootoutApp
|
46
80
|
#
|
47
81
|
# def cycle
|
48
82
|
# puts `websocket-bench broadcast ws://127.0.0.1:3000/ --concurrent 10 --sample-size 100 --server-type binary --step-size 1000 --limit-percentile 95 --limit-rtt 250ms --initial-clients 1000`
|
49
|
-
# sleep(
|
50
|
-
# puts `wrk -c4000 -d15 -
|
83
|
+
# sleep(4)
|
84
|
+
# puts `wrk -c4000 -d15 -t2 http://localhost:3000/`
|
51
85
|
# true
|
52
86
|
# end
|
53
87
|
# sleep(10) while cycle
|
data/ext/iodine/defer.c
CHANGED
@@ -10,6 +10,7 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
10
10
|
|
11
11
|
#include <errno.h>
|
12
12
|
#include <signal.h>
|
13
|
+
#include <stdint.h>
|
13
14
|
#include <stdio.h>
|
14
15
|
#include <sys/types.h>
|
15
16
|
#include <sys/wait.h>
|
@@ -20,18 +21,30 @@ Compile time settings
|
|
20
21
|
***************************************************************************** */
|
21
22
|
|
22
23
|
#ifndef DEFER_THROTTLE
|
23
|
-
#define DEFER_THROTTLE
|
24
|
+
#define DEFER_THROTTLE 1048574UL
|
24
25
|
#endif
|
25
26
|
#ifndef DEFER_THROTTLE_LIMIT
|
26
|
-
#define DEFER_THROTTLE_LIMIT
|
27
|
+
#define DEFER_THROTTLE_LIMIT 2097148UL
|
28
|
+
#endif
|
29
|
+
|
30
|
+
/**
|
31
|
+
* The progressive throttling model makes concurrency and parallelism more
|
32
|
+
* likely.
|
33
|
+
*
|
34
|
+
* Otherwise threads are assumed to be intended for "fallback" in case of slow
|
35
|
+
* user code, where a single thread should be active most of the time and other
|
36
|
+
* threads are activated only when that single thread is slow to perform.
|
37
|
+
*/
|
38
|
+
#ifndef DEFER_THROTTLE_PROGRESSIVE
|
39
|
+
#define DEFER_THROTTLE_PROGRESSIVE 1
|
27
40
|
#endif
|
28
41
|
|
29
42
|
#ifndef DEFER_QUEUE_BLOCK_COUNT
|
30
43
|
#if UINTPTR_MAX <= 0xFFFFFFFF
|
31
|
-
/* Almost a page of memory on most 32 bit machines: ((4096/4)-
|
32
|
-
#define DEFER_QUEUE_BLOCK_COUNT
|
44
|
+
/* Almost a page of memory on most 32 bit machines: ((4096/4)-5)/3 */
|
45
|
+
#define DEFER_QUEUE_BLOCK_COUNT 339
|
33
46
|
#else
|
34
|
-
/* Almost a page of memory on most 64 bit machines: ((4096/8)-
|
47
|
+
/* Almost a page of memory on most 64 bit machines: ((4096/8)-5)/3 */
|
35
48
|
#define DEFER_QUEUE_BLOCK_COUNT 168
|
36
49
|
#endif
|
37
50
|
#endif
|
@@ -126,10 +139,10 @@ critical_error:
|
|
126
139
|
}
|
127
140
|
|
128
141
|
static inline task_s pop_task(void) {
|
129
|
-
task_s ret = (task_s){NULL};
|
142
|
+
task_s ret = (task_s){.func = NULL};
|
130
143
|
queue_block_s *to_free = NULL;
|
131
|
-
/* lock the state machine,
|
132
|
-
|
144
|
+
/* lock the state machine, grab/create a task and place it at the tail */
|
145
|
+
spn_lock(&deferred.lock);
|
133
146
|
|
134
147
|
/* empty? */
|
135
148
|
if (deferred.reader->write == deferred.reader->read &&
|
@@ -148,6 +161,11 @@ static inline task_s pop_task(void) {
|
|
148
161
|
to_free = deferred.reader;
|
149
162
|
deferred.reader = deferred.reader->next;
|
150
163
|
} else {
|
164
|
+
if (deferred.reader != &static_queue && static_queue.state == 2) {
|
165
|
+
to_free = deferred.reader;
|
166
|
+
deferred.writer = &static_queue;
|
167
|
+
deferred.reader = &static_queue;
|
168
|
+
}
|
151
169
|
deferred.reader->write = deferred.reader->read = deferred.reader->state =
|
152
170
|
0;
|
153
171
|
}
|
@@ -157,6 +175,7 @@ static inline task_s pop_task(void) {
|
|
157
175
|
finish:
|
158
176
|
if (to_free == &static_queue) {
|
159
177
|
static_queue.state = 2;
|
178
|
+
static_queue.next = NULL;
|
160
179
|
}
|
161
180
|
spn_unlock(&deferred.lock);
|
162
181
|
|
@@ -182,6 +201,8 @@ static inline void clear_tasks(void) {
|
|
182
201
|
spn_unlock(&deferred.lock);
|
183
202
|
}
|
184
203
|
|
204
|
+
void defer_on_fork(void) { deferred.lock = SPN_LOCK_INIT; }
|
205
|
+
|
185
206
|
#define push_task(...) push_task((task_s){__VA_ARGS__})
|
186
207
|
|
187
208
|
/* *****************************************************************************
|
@@ -194,6 +215,7 @@ int defer(void (*func)(void *, void *), void *arg1, void *arg2) {
|
|
194
215
|
if (!func)
|
195
216
|
goto call_error;
|
196
217
|
push_task(.func = func, .arg1 = arg1, .arg2 = arg2);
|
218
|
+
defer_thread_signal();
|
197
219
|
return 0;
|
198
220
|
|
199
221
|
call_error:
|
@@ -221,6 +243,16 @@ void defer_clear_queue(void) { clear_tasks(); }
|
|
221
243
|
Thread Pool Support
|
222
244
|
***************************************************************************** */
|
223
245
|
|
246
|
+
/* thread pool data container */
|
247
|
+
struct defer_pool {
|
248
|
+
volatile unsigned int flag;
|
249
|
+
unsigned int count;
|
250
|
+
struct thread_msg_s {
|
251
|
+
pool_pt pool;
|
252
|
+
void *thrd;
|
253
|
+
} threads[];
|
254
|
+
};
|
255
|
+
|
224
256
|
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__) || \
|
225
257
|
defined(DEBUG)
|
226
258
|
#include <pthread.h>
|
@@ -228,9 +260,9 @@ Thread Pool Support
|
|
228
260
|
/* `weak` functions can be overloaded to change the thread implementation. */
|
229
261
|
|
230
262
|
#pragma weak defer_new_thread
|
231
|
-
void *defer_new_thread(void *(*thread_func)(void *),
|
263
|
+
void *defer_new_thread(void *(*thread_func)(void *), void *arg) {
|
232
264
|
pthread_t *thread = malloc(sizeof(*thread));
|
233
|
-
if (thread == NULL || pthread_create(thread, NULL, thread_func,
|
265
|
+
if (thread == NULL || pthread_create(thread, NULL, thread_func, arg))
|
234
266
|
goto error;
|
235
267
|
return thread;
|
236
268
|
error:
|
@@ -238,12 +270,24 @@ error:
|
|
238
270
|
return NULL;
|
239
271
|
}
|
240
272
|
|
273
|
+
/**
|
274
|
+
* OVERRIDE THIS to replace the default pthread implementation.
|
275
|
+
*
|
276
|
+
* Frees the memory asociated with a thread indentifier (allows the thread to
|
277
|
+
* run it's course, just the identifier is freed).
|
278
|
+
*/
|
279
|
+
#pragma weak defer_free_thread
|
280
|
+
void defer_free_thread(void *p_thr) {
|
281
|
+
pthread_detach(*((pthread_t *)p_thr));
|
282
|
+
free(p_thr);
|
283
|
+
}
|
284
|
+
|
241
285
|
#pragma weak defer_join_thread
|
242
286
|
int defer_join_thread(void *p_thr) {
|
243
287
|
if (!p_thr)
|
244
288
|
return -1;
|
245
289
|
pthread_join(*((pthread_t *)p_thr), NULL);
|
246
|
-
|
290
|
+
defer_free_thread(p_thr);
|
247
291
|
return 0;
|
248
292
|
}
|
249
293
|
|
@@ -260,6 +304,10 @@ void *defer_new_thread(void *(*thread_func)(void *), void *arg) {
|
|
260
304
|
(void)arg;
|
261
305
|
return NULL;
|
262
306
|
}
|
307
|
+
|
308
|
+
#pragma weak defer_free_thread
|
309
|
+
void defer_free_thread(void *p_thr) { void(p_thr); }
|
310
|
+
|
263
311
|
#pragma weak defer_join_thread
|
264
312
|
int defer_join_thread(void *p_thr) {
|
265
313
|
(void)p_thr;
|
@@ -271,43 +319,80 @@ void defer_thread_throttle(unsigned long microsec) { return; }
|
|
271
319
|
|
272
320
|
#endif /* DEBUG || pthread default */
|
273
321
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
322
|
+
/**
|
323
|
+
* A thread entering this function should wait for new evennts.
|
324
|
+
*/
|
325
|
+
#pragma weak defer_thread_wait
|
326
|
+
void defer_thread_wait(pool_pt pool, void *p_thr) {
|
327
|
+
if (DEFER_THROTTLE_PROGRESSIVE) {
|
328
|
+
/* keeps threads active (concurrent), but reduces performance */
|
329
|
+
static __thread size_t static_throttle = 1;
|
330
|
+
if (static_throttle < DEFER_THROTTLE_LIMIT)
|
331
|
+
static_throttle = (static_throttle << 1);
|
332
|
+
throttle_thread(static_throttle);
|
333
|
+
if (defer_has_queue())
|
334
|
+
static_throttle = 1;
|
335
|
+
(void)p_thr;
|
336
|
+
(void)pool;
|
337
|
+
} else {
|
338
|
+
/* Protects against slow user code, but mostly a single active thread */
|
339
|
+
size_t throttle =
|
340
|
+
pool ? ((pool->count) * DEFER_THROTTLE) : DEFER_THROTTLE_LIMIT;
|
341
|
+
if (!throttle || throttle > DEFER_THROTTLE_LIMIT)
|
342
|
+
throttle = DEFER_THROTTLE_LIMIT;
|
343
|
+
if (throttle == DEFER_THROTTLE)
|
344
|
+
throttle <<= 1;
|
345
|
+
throttle_thread(throttle);
|
346
|
+
(void)p_thr;
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
/**
|
351
|
+
* This should signal a single waiting thread to wake up (a new task entered the
|
352
|
+
* queue).
|
353
|
+
*/
|
354
|
+
#pragma weak defer_thread_signal
|
355
|
+
void defer_thread_signal(void) { (void)0; }
|
280
356
|
|
281
357
|
/* a thread's cycle. This is what a worker thread does... repeatedly. */
|
282
358
|
static void *defer_worker_thread(void *pool_) {
|
283
|
-
volatile
|
359
|
+
struct thread_msg_s volatile *data = pool_;
|
284
360
|
signal(SIGPIPE, SIG_IGN);
|
285
|
-
/* the throttle replaces conditional variables for better performance */
|
286
|
-
size_t throttle = (pool->count) * DEFER_THROTTLE;
|
287
|
-
if (!throttle || throttle > DEFER_THROTTLE_LIMIT)
|
288
|
-
throttle = DEFER_THROTTLE_LIMIT;
|
289
361
|
/* perform any available tasks */
|
290
362
|
defer_perform();
|
291
363
|
/* as long as the flag is true, wait for and perform tasks. */
|
292
364
|
do {
|
293
|
-
|
365
|
+
defer_thread_wait(data->pool, data->thrd);
|
294
366
|
defer_perform();
|
295
|
-
} while (pool->flag);
|
367
|
+
} while (data->pool->flag);
|
296
368
|
return NULL;
|
297
369
|
}
|
298
370
|
|
299
371
|
/** Signals a running thread pool to stop. Returns immediately. */
|
300
|
-
void defer_pool_stop(pool_pt pool) {
|
372
|
+
void defer_pool_stop(pool_pt pool) {
|
373
|
+
if (!pool)
|
374
|
+
return;
|
375
|
+
pool->flag = 0;
|
376
|
+
for (size_t i = 0; i < pool->count; ++i) {
|
377
|
+
defer_thread_signal();
|
378
|
+
}
|
379
|
+
}
|
301
380
|
|
302
381
|
/** Returns TRUE (1) if the pool is hadn't been signaled to finish up. */
|
303
|
-
int defer_pool_is_active(pool_pt pool) { return pool->flag; }
|
382
|
+
int defer_pool_is_active(pool_pt pool) { return (int)pool->flag; }
|
304
383
|
|
305
|
-
/**
|
384
|
+
/**
|
385
|
+
* Waits for a running thread pool, joining threads and finishing all tasks.
|
386
|
+
*
|
387
|
+
* This function MUST be called in order to free the pool's data (the
|
388
|
+
* `pool_pt`).
|
389
|
+
*/
|
306
390
|
void defer_pool_wait(pool_pt pool) {
|
307
391
|
while (pool->count) {
|
308
392
|
pool->count--;
|
309
|
-
defer_join_thread(pool->threads[pool->count]);
|
393
|
+
defer_join_thread(pool->threads[pool->count].thrd);
|
310
394
|
}
|
395
|
+
free(pool);
|
311
396
|
}
|
312
397
|
|
313
398
|
/** The logic behind `defer_pool_start`. */
|
@@ -316,8 +401,10 @@ static inline pool_pt defer_pool_initialize(unsigned int thread_count,
|
|
316
401
|
pool->flag = 1;
|
317
402
|
pool->count = 0;
|
318
403
|
while (pool->count < thread_count &&
|
319
|
-
(pool->threads[pool->count] =
|
320
|
-
|
404
|
+
(pool->threads[pool->count].pool = pool) &&
|
405
|
+
(pool->threads[pool->count].thrd = defer_new_thread(
|
406
|
+
defer_worker_thread, (void *)(pool->threads + pool->count))))
|
407
|
+
|
321
408
|
pool->count++;
|
322
409
|
if (pool->count == thread_count) {
|
323
410
|
return pool;
|
@@ -330,221 +417,44 @@ static inline pool_pt defer_pool_initialize(unsigned int thread_count,
|
|
330
417
|
pool_pt defer_pool_start(unsigned int thread_count) {
|
331
418
|
if (thread_count == 0)
|
332
419
|
return NULL;
|
333
|
-
pool_pt pool =
|
420
|
+
pool_pt pool =
|
421
|
+
malloc(sizeof(*pool) + (thread_count * sizeof(*pool->threads)));
|
334
422
|
if (!pool)
|
335
423
|
return NULL;
|
336
|
-
return defer_pool_initialize(thread_count, pool);
|
337
|
-
}
|
338
|
-
|
339
|
-
/* *****************************************************************************
|
340
|
-
Child Process support (`fork`)
|
341
|
-
***************************************************************************** */
|
342
|
-
|
343
|
-
/**
|
344
|
-
OVERRIDE THIS to replace the default `fork` implementation or to inject hooks
|
345
|
-
into the forking function.
|
346
|
-
|
347
|
-
Behaves like the system's `fork`.
|
348
|
-
*/
|
349
|
-
#pragma weak defer_new_child
|
350
|
-
int defer_new_child(void) { return (int)fork(); }
|
351
|
-
|
352
|
-
/* forked `defer` workers use a global thread pool object. */
|
353
|
-
static pool_pt forked_pool;
|
354
|
-
|
355
|
-
/* handles the SIGINT and SIGTERM signals by shutting down workers */
|
356
|
-
static void sig_int_handler(int sig) {
|
357
|
-
if (sig != SIGINT && sig != SIGTERM)
|
358
|
-
return;
|
359
|
-
if (!forked_pool)
|
360
|
-
return;
|
361
|
-
defer_pool_stop(forked_pool);
|
362
|
-
}
|
363
|
-
|
364
|
-
/*
|
365
|
-
Zombie Reaping
|
366
|
-
With thanks to Dr Graham D Shaw.
|
367
|
-
http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
|
368
|
-
*/
|
369
|
-
void reap_child_handler(int sig) {
|
370
|
-
(void)(sig);
|
371
|
-
int old_errno = errno;
|
372
|
-
while (waitpid(-1, NULL, WNOHANG) > 0)
|
373
|
-
;
|
374
|
-
errno = old_errno;
|
375
|
-
}
|
376
|
-
|
377
|
-
#if !defined(NO_CHILD_REAPER) || NO_CHILD_REAPER == 0
|
378
|
-
/* initializes zombie reaping for the process */
|
379
|
-
inline static void reap_children(void) {
|
380
|
-
struct sigaction sa;
|
381
|
-
sa.sa_handler = reap_child_handler;
|
382
|
-
sigemptyset(&sa.sa_mask);
|
383
|
-
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
384
|
-
if (sigaction(SIGCHLD, &sa, 0) == -1) {
|
385
|
-
perror("Child reaping initialization failed");
|
386
|
-
kill(0, SIGINT), exit(errno);
|
387
|
-
}
|
388
|
-
}
|
389
|
-
#endif
|
390
|
-
|
391
|
-
/* a global process identifier (0 == root) */
|
392
|
-
static int defer_fork_pid_id = 0;
|
393
|
-
|
394
|
-
/**
|
395
|
-
* Forks the process, starts up a thread pool and waits for all tasks to run.
|
396
|
-
* All existing tasks will run in all processes (multiple times).
|
397
|
-
*
|
398
|
-
* Returns 0 on success, -1 on error and a positive number if this is a child
|
399
|
-
* process that was forked.
|
400
|
-
*/
|
401
|
-
int defer_perform_in_fork(unsigned int process_count,
|
402
|
-
unsigned int thread_count) {
|
403
|
-
if (forked_pool)
|
404
|
-
return -1; /* we're already running inside an active `fork` */
|
405
|
-
|
406
|
-
/* we use a placeholder while initializing the forked thread pool, so calls to
|
407
|
-
* `defer_fork_is_active` don't fail.
|
408
|
-
*/
|
409
|
-
static struct defer_pool pool_placeholder = {.count = 1, .flag = 1};
|
410
|
-
|
411
|
-
/* setup signal handling */
|
412
|
-
struct sigaction act, old, old_term, old_pipe;
|
413
|
-
pid_t *pids = NULL;
|
414
|
-
int ret = 0;
|
415
|
-
unsigned int pids_count;
|
416
|
-
|
417
|
-
act.sa_handler = sig_int_handler;
|
418
|
-
sigemptyset(&act.sa_mask);
|
419
|
-
act.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
420
|
-
|
421
|
-
if (sigaction(SIGINT, &act, &old)) {
|
422
|
-
perror("couldn't set signal handler");
|
423
|
-
goto finish;
|
424
|
-
};
|
425
|
-
|
426
|
-
if (sigaction(SIGTERM, &act, &old_term)) {
|
427
|
-
perror("couldn't set signal handler");
|
428
|
-
goto finish;
|
429
|
-
};
|
430
|
-
|
431
|
-
act.sa_handler = SIG_IGN;
|
432
|
-
if (sigaction(SIGPIPE, &act, &old_pipe)) {
|
433
|
-
perror("couldn't set signal handler");
|
434
|
-
goto finish;
|
435
|
-
};
|
436
424
|
|
437
|
-
|
438
|
-
#if !defined(NO_CHILD_REAPER) || NO_CHILD_REAPER == 0
|
439
|
-
reap_children();
|
440
|
-
#endif
|
441
|
-
|
442
|
-
if (!process_count)
|
443
|
-
process_count = 1;
|
444
|
-
--process_count;
|
445
|
-
|
446
|
-
/* for `process_count == 0` nothing happens */
|
447
|
-
pids = calloc(process_count, sizeof(*pids));
|
448
|
-
if (process_count && !pids)
|
449
|
-
goto finish;
|
450
|
-
for (pids_count = 0; pids_count < process_count; pids_count++) {
|
451
|
-
if (!(pids[pids_count] = (pid_t)defer_new_child())) {
|
452
|
-
defer_fork_pid_id = pids_count + 1;
|
453
|
-
forked_pool = &pool_placeholder;
|
454
|
-
forked_pool = defer_pool_start(thread_count);
|
455
|
-
defer_pool_wait(forked_pool);
|
456
|
-
defer_perform();
|
457
|
-
defer_perform();
|
458
|
-
return 1;
|
459
|
-
}
|
460
|
-
if (pids[pids_count] == -1) {
|
461
|
-
ret = -1;
|
462
|
-
goto finish;
|
463
|
-
}
|
464
|
-
}
|
465
|
-
|
466
|
-
forked_pool = &pool_placeholder;
|
467
|
-
forked_pool = defer_pool_start(thread_count);
|
468
|
-
|
469
|
-
defer_pool_wait(forked_pool);
|
470
|
-
forked_pool = NULL;
|
471
|
-
|
472
|
-
defer_perform();
|
473
|
-
|
474
|
-
finish:
|
475
|
-
if (pids) {
|
476
|
-
for (size_t j = 0; j < pids_count; j++) {
|
477
|
-
kill(pids[j], SIGINT);
|
478
|
-
}
|
479
|
-
for (size_t j = 0; j < pids_count; j++) {
|
480
|
-
waitpid(pids[j], NULL, 0);
|
481
|
-
}
|
482
|
-
free(pids);
|
483
|
-
}
|
484
|
-
sigaction(SIGINT, &old, &act);
|
485
|
-
sigaction(SIGTERM, &old_term, &act);
|
486
|
-
sigaction(SIGTERM, &old_pipe, &act);
|
487
|
-
return ret;
|
425
|
+
return defer_pool_initialize(thread_count, pool);
|
488
426
|
}
|
489
427
|
|
490
|
-
/** Returns TRUE (1) if the forked thread pool hadn't been signaled to finish
|
491
|
-
* up. */
|
492
|
-
int defer_fork_is_active(void) { return forked_pool && forked_pool->flag; }
|
493
|
-
|
494
|
-
/** Returns the process number for the current working proceess. 0 == parent. */
|
495
|
-
int defer_fork_pid(void) { return defer_fork_pid_id; }
|
496
|
-
|
497
428
|
/* *****************************************************************************
|
498
429
|
Test
|
499
430
|
***************************************************************************** */
|
500
431
|
#ifdef DEBUG
|
501
432
|
|
502
|
-
#include <stdio.h>
|
503
|
-
|
504
433
|
#include <pthread.h>
|
505
|
-
#
|
434
|
+
#include <stdio.h>
|
435
|
+
#include <sys/stat.h>
|
506
436
|
|
507
|
-
static spn_lock_i i_lock = 0;
|
508
437
|
static size_t i_count = 0;
|
509
438
|
|
510
|
-
|
511
|
-
(void)(unused);
|
512
|
-
(void)(unused2);
|
513
|
-
spn_lock(&i_lock);
|
514
|
-
i_count++;
|
515
|
-
spn_unlock(&i_lock);
|
516
|
-
}
|
439
|
+
#define TOTAL_COUNT (512 * 1024)
|
517
440
|
|
518
|
-
static void
|
441
|
+
static void sample_task(void *unused, void *unused2) {
|
519
442
|
(void)(unused);
|
520
443
|
(void)(unused2);
|
521
|
-
|
522
|
-
i_count++;
|
523
|
-
spn_unlock(&i_lock);
|
524
|
-
if (i_count < (1024 * 1024))
|
525
|
-
defer(single_counter_task, NULL, NULL);
|
444
|
+
spn_add(&i_count, 1);
|
526
445
|
}
|
527
446
|
|
528
|
-
static void sched_sample_task(void *
|
529
|
-
(void)(unused);
|
447
|
+
static void sched_sample_task(void *count, void *unused2) {
|
530
448
|
(void)(unused2);
|
531
|
-
for (size_t i = 0; i <
|
449
|
+
for (size_t i = 0; i < (uintptr_t)count; i++) {
|
532
450
|
defer(sample_task, NULL, NULL);
|
533
451
|
}
|
534
452
|
}
|
535
453
|
|
536
|
-
static void thrd_sched(void *unused, void *unused2) {
|
537
|
-
for (size_t i = 0; i < (1024 / DEFER_TEST_THREAD_COUNT); i++) {
|
538
|
-
sched_sample_task(unused, unused2);
|
539
|
-
}
|
540
|
-
}
|
541
|
-
|
542
454
|
static void text_task_text(void *unused, void *unused2) {
|
543
455
|
(void)(unused);
|
544
456
|
(void)(unused2);
|
545
|
-
spn_lock(&i_lock);
|
546
457
|
fprintf(stderr, "this text should print before defer_perform returns\n");
|
547
|
-
spn_unlock(&i_lock);
|
548
458
|
}
|
549
459
|
|
550
460
|
static void text_task(void *a1, void *a2) {
|
@@ -553,114 +463,101 @@ static void text_task(void *a1, void *a2) {
|
|
553
463
|
defer(text_task_text, a1, a2);
|
554
464
|
}
|
555
465
|
|
556
|
-
static void pid_task(void *arg, void *unused2) {
|
557
|
-
(void)(unused2);
|
558
|
-
fprintf(stderr, "* %d pid is going to sleep... (%s)\n", getpid(),
|
559
|
-
arg ? (char *)arg : "unknown");
|
560
|
-
}
|
561
|
-
|
562
466
|
void defer_test(void) {
|
563
|
-
|
564
|
-
|
467
|
+
#define TEST_ASSERT(cond, ...) \
|
468
|
+
if (!(cond)) { \
|
469
|
+
fprintf(stderr, "* " __VA_ARGS__); \
|
470
|
+
fprintf(stderr, "Testing failed.\n"); \
|
471
|
+
exit(-1); \
|
472
|
+
}
|
565
473
|
|
566
|
-
|
474
|
+
clock_t start, end;
|
475
|
+
fprintf(stderr, "Starting defer testing\n");
|
567
476
|
i_count = 0;
|
568
|
-
spn_unlock(&i_lock);
|
569
477
|
start = clock();
|
570
|
-
for (size_t i = 0; i <
|
478
|
+
for (size_t i = 0; i < TOTAL_COUNT; i++) {
|
571
479
|
sample_task(NULL, NULL);
|
572
480
|
}
|
573
481
|
end = clock();
|
574
482
|
fprintf(stderr,
|
575
483
|
"Deferless (direct call) counter: %lu cycles with i_count = %lu, "
|
576
484
|
"%lu/%lu free/malloc\n",
|
577
|
-
end - start, i_count,
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
485
|
+
(unsigned long)(end - start), (unsigned long)i_count,
|
486
|
+
(unsigned long)count_dealloc, (unsigned long)count_alloc);
|
487
|
+
size_t i_count_should_be = i_count;
|
488
|
+
|
489
|
+
fprintf(stderr, "\n");
|
490
|
+
|
491
|
+
for (int i = 1; TOTAL_COUNT >> i; ++i) {
|
492
|
+
COUNT_RESET;
|
493
|
+
i_count = 0;
|
494
|
+
const size_t per_task = TOTAL_COUNT >> i;
|
495
|
+
const size_t tasks = 1 << i;
|
496
|
+
start = clock();
|
497
|
+
for (size_t j = 0; j < tasks; ++j) {
|
498
|
+
defer(sched_sample_task, (void *)per_task, NULL);
|
499
|
+
}
|
500
|
+
defer_perform();
|
501
|
+
end = clock();
|
502
|
+
fprintf(stderr,
|
503
|
+
"- Defer single thread, %zu scheduling loops (%zu each):\n"
|
504
|
+
" %lu cycles with i_count = %lu, %lu/%lu "
|
505
|
+
"free/malloc\n",
|
506
|
+
tasks, per_task, (unsigned long)(end - start),
|
507
|
+
(unsigned long)i_count, (unsigned long)count_dealloc,
|
508
|
+
(unsigned long)count_alloc);
|
509
|
+
TEST_ASSERT(i_count == i_count_should_be, "ERROR: defer count invalid\n");
|
601
510
|
}
|
602
|
-
defer_perform();
|
603
|
-
end = clock();
|
604
|
-
fprintf(stderr,
|
605
|
-
"Defer single thread: %lu cycles with i_count = %lu, %lu/%lu "
|
606
|
-
"free/malloc\n",
|
607
|
-
end - start, i_count, count_dealloc, count_alloc);
|
608
511
|
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
512
|
+
ssize_t cpu_count = 8;
|
513
|
+
#ifdef _SC_NPROCESSORS_ONLN
|
514
|
+
cpu_count = (sysconf(_SC_NPROCESSORS_ONLN) >> 1) | 1;
|
515
|
+
#endif
|
516
|
+
|
517
|
+
fprintf(stderr, "\n");
|
518
|
+
|
519
|
+
for (int i = 1; TOTAL_COUNT >> i; ++i) {
|
520
|
+
COUNT_RESET;
|
521
|
+
i_count = 0;
|
522
|
+
const size_t per_task = TOTAL_COUNT >> i;
|
523
|
+
const size_t tasks = 1 << i;
|
524
|
+
pool_pt pool = defer_pool_start(cpu_count);
|
525
|
+
start = clock();
|
526
|
+
for (size_t j = 0; j < tasks; ++j) {
|
527
|
+
defer(sched_sample_task, (void *)per_task, NULL);
|
618
528
|
}
|
619
|
-
// defer((void (*)(void *))defer_pool_stop, pool);
|
620
529
|
defer_pool_stop(pool);
|
621
530
|
defer_pool_wait(pool);
|
622
531
|
end = clock();
|
623
532
|
fprintf(stderr,
|
624
|
-
"Defer
|
625
|
-
"%lu/%lu
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
533
|
+
"- Defer %zu threads, %zu scheduling loops (%zu each):\n"
|
534
|
+
" %lu cycles with i_count = %lu, %lu/%lu "
|
535
|
+
"free/malloc\n",
|
536
|
+
(size_t)cpu_count, tasks, per_task, (unsigned long)(end - start),
|
537
|
+
(unsigned long)i_count, (unsigned long)count_dealloc,
|
538
|
+
(unsigned long)count_alloc);
|
539
|
+
TEST_ASSERT(i_count == i_count_should_be, "ERROR: defer count invalid\n");
|
540
|
+
}
|
541
|
+
|
632
542
|
COUNT_RESET;
|
633
543
|
i_count = 0;
|
634
|
-
spn_unlock(&i_lock);
|
635
|
-
start = clock();
|
636
544
|
for (size_t i = 0; i < 1024; i++) {
|
637
545
|
defer(sched_sample_task, NULL, NULL);
|
638
546
|
}
|
639
547
|
defer_perform();
|
640
|
-
end = clock();
|
641
|
-
fprintf(stderr,
|
642
|
-
"Defer single thread (2): %lu cycles with i_count = %lu, %lu/%lu "
|
643
|
-
"free/malloc\n",
|
644
|
-
end - start, i_count, count_dealloc, count_alloc);
|
645
|
-
|
646
|
-
fprintf(stderr, "calling defer_perform.\n");
|
647
548
|
defer(text_task, NULL, NULL);
|
549
|
+
fprintf(stderr, "calling defer_perform.\n");
|
648
550
|
defer_perform();
|
649
551
|
fprintf(stderr,
|
650
552
|
"defer_perform returned. i_count = %lu, %lu/%lu free/malloc\n",
|
651
|
-
i_count, count_dealloc,
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
fprintf(stderr, "* %d finished\n", getpid());
|
657
|
-
exit(0);
|
658
|
-
};
|
659
|
-
fprintf(stderr, "* Defer queue %lu/%lu free/malloc\n", count_dealloc,
|
660
|
-
count_alloc);
|
553
|
+
(unsigned long)i_count, (unsigned long)count_dealloc,
|
554
|
+
(unsigned long)count_alloc);
|
555
|
+
|
556
|
+
COUNT_RESET;
|
557
|
+
i_count = 0;
|
661
558
|
defer_clear_queue();
|
662
|
-
fprintf(stderr, "* Defer queue %lu/%lu free/malloc\n",
|
663
|
-
count_alloc);
|
559
|
+
fprintf(stderr, "* Defer cleared queue: %lu/%lu free/malloc\n\n",
|
560
|
+
(unsigned long)count_dealloc, (unsigned long)count_alloc);
|
664
561
|
}
|
665
562
|
|
666
563
|
#endif
|