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/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 still run on the whole process cluster."
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.websocket?'.freeze])
32
- env['upgrade.websocket'.freeze] = WS_RedisPubSub.new(env['PATH_INFO'] ? env['PATH_INFO'][1..-1] : "guest")
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 channel: "chat"
47
+ subscribe "chat"
48
48
  # let everyone know we arrived
49
- # publish channel: "chat", message: "#{@name} entered the chat."
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 channel: "chat", message: "#{@name}: #{data}"
57
+ publish "chat", "#{@name}: #{data}"
58
58
  end
59
59
  def on_close
60
60
  # let everyone know we left
61
- publish channel: "chat", message: "#{@name} left the chat."
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 env['upgrade.websocket?'.freeze] # && env['HTTP_UPGRADE'.freeze] =~ /websocket/i
10
- env['upgrade.websocket'.freeze] = ShootoutApp.new
11
- return [0, {}, []]
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 = "ENV:\r\n#{env.to_a.map { |h| "#{h[0]}: #{h[1]}" } .join "\n"}\n"
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 += "\nRequest Path: #{request.path_info}\nParams:\r\n#{request.params.to_a.map { |h| "#{h[0]}: #{h[1]}" } .join "\n"}\n" unless request.params.empty?
16
- [200, { 'Content-Length' => out.length, 'Content-Type' => 'text/plain; charset=UTF-8;' }, [out]]
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
- subscribe channel: "shootout"
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
- publish(channel: "shootout", message: data)
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
- publish(channel: "shootout", message: ({type: 'broadcast', payload: payload}.to_json))
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(2)
50
- # puts `wrk -c4000 -d15 -t12 http://localhost:3000/`
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 524287UL
24
+ #define DEFER_THROTTLE 1048574UL
24
25
  #endif
25
26
  #ifndef DEFER_THROTTLE_LIMIT
26
- #define DEFER_THROTTLE_LIMIT 1572864UL
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)-4)/3 */
32
- #define DEFER_QUEUE_BLOCK_COUNT 340
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)-4)/3 */
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, to grab/create a task and place it at the tail
132
- */ spn_lock(&deferred.lock);
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 *), pool_pt arg) {
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, (void *)arg))
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
- free(p_thr);
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
- /* thread pool data container */
275
- struct defer_pool {
276
- unsigned int flag;
277
- unsigned int count;
278
- void *threads[];
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 pool_pt pool = pool_;
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
- throttle_thread(throttle);
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) { pool->flag = 0; }
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
- /** Waits for a running thread pool, joining threads and finishing all tasks. */
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
- defer_new_thread(defer_worker_thread, pool)))
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 = malloc(sizeof(*pool) + (thread_count * sizeof(void *)));
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
- /* setup zomie reaping */
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
- #define DEFER_TEST_THREAD_COUNT 128
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
- static void sample_task(void *unused, void *unused2) {
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 single_counter_task(void *unused, void *unused2) {
441
+ static void sample_task(void *unused, void *unused2) {
519
442
  (void)(unused);
520
443
  (void)(unused2);
521
- spn_lock(&i_lock);
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 *unused, void *unused2) {
529
- (void)(unused);
447
+ static void sched_sample_task(void *count, void *unused2) {
530
448
  (void)(unused2);
531
- for (size_t i = 0; i < 1024; 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
- time_t start, end;
564
- fprintf(stderr, "Starting defer testing\n");
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
- spn_lock(&i_lock);
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 < (1024 * 1024); 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, count_dealloc, count_alloc);
578
-
579
- spn_lock(&i_lock);
580
- COUNT_RESET;
581
- i_count = 0;
582
- spn_unlock(&i_lock);
583
- start = clock();
584
- defer(single_counter_task, NULL, NULL);
585
- defer(single_counter_task, NULL, NULL);
586
- defer_perform();
587
- end = clock();
588
- fprintf(stderr,
589
- "Defer single thread, two tasks: "
590
- "%lu cycles with i_count = %lu, %lu/%lu "
591
- "free/malloc\n",
592
- end - start, i_count, count_dealloc, count_alloc);
593
-
594
- spn_lock(&i_lock);
595
- COUNT_RESET;
596
- i_count = 0;
597
- spn_unlock(&i_lock);
598
- start = clock();
599
- for (size_t i = 0; i < 1024; i++) {
600
- defer(sched_sample_task, NULL, NULL);
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
- spn_lock(&i_lock);
610
- COUNT_RESET;
611
- i_count = 0;
612
- spn_unlock(&i_lock);
613
- start = clock();
614
- pool_pt pool = defer_pool_start(DEFER_TEST_THREAD_COUNT);
615
- if (pool) {
616
- for (size_t i = 0; i < DEFER_TEST_THREAD_COUNT; i++) {
617
- defer(thrd_sched, NULL, NULL);
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 multi-thread (%d threads): %lu cycles with i_count = %lu, "
625
- "%lu/%lu free/malloc\n",
626
- DEFER_TEST_THREAD_COUNT, end - start, i_count, count_dealloc,
627
- count_alloc);
628
- } else
629
- fprintf(stderr, "Defer multi-thread: FAILED!\n");
630
-
631
- spn_lock(&i_lock);
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, count_alloc);
652
-
653
- fprintf(stderr, "press ^C to finish PID test\n");
654
- defer(pid_task, "pid test", NULL);
655
- if (defer_perform_in_fork(4, 64) > 0) {
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", count_dealloc,
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