iodine 0.5.2 → 0.6.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +63 -100
  4. data/bin/raw-rbhttp +12 -7
  5. data/examples/config.ru +8 -7
  6. data/examples/echo.ru +8 -7
  7. data/examples/info.md +41 -35
  8. data/examples/pubsub_engine.ru +12 -12
  9. data/examples/redis.ru +10 -12
  10. data/examples/shootout.ru +19 -42
  11. data/exe/iodine +116 -1
  12. data/ext/iodine/defer.c +1 -1
  13. data/ext/iodine/facil.c +12 -8
  14. data/ext/iodine/facil.h +2 -2
  15. data/ext/iodine/iodine.c +177 -343
  16. data/ext/iodine/iodine.h +18 -72
  17. data/ext/iodine/iodine_caller.c +132 -0
  18. data/ext/iodine/iodine_caller.h +21 -0
  19. data/ext/iodine/iodine_connection.c +841 -0
  20. data/ext/iodine/iodine_connection.h +55 -0
  21. data/ext/iodine/iodine_defer.c +391 -0
  22. data/ext/iodine/iodine_defer.h +7 -0
  23. data/ext/iodine/{rb-fiobj2rb.h → iodine_fiobj2rb.h} +6 -6
  24. data/ext/iodine/iodine_helpers.c +51 -5
  25. data/ext/iodine/iodine_helpers.h +2 -3
  26. data/ext/iodine/iodine_http.c +284 -141
  27. data/ext/iodine/iodine_http.h +2 -2
  28. data/ext/iodine/iodine_json.c +13 -13
  29. data/ext/iodine/iodine_json.h +1 -1
  30. data/ext/iodine/iodine_pubsub.c +573 -823
  31. data/ext/iodine/iodine_pubsub.h +15 -27
  32. data/ext/iodine/{rb-rack-io.c → iodine_rack_io.c} +30 -8
  33. data/ext/iodine/{rb-rack-io.h → iodine_rack_io.h} +1 -0
  34. data/ext/iodine/iodine_store.c +136 -0
  35. data/ext/iodine/iodine_store.h +20 -0
  36. data/ext/iodine/iodine_tcp.c +385 -0
  37. data/ext/iodine/iodine_tcp.h +9 -0
  38. data/lib/iodine.rb +73 -171
  39. data/lib/iodine/connection.rb +34 -0
  40. data/lib/iodine/pubsub.rb +5 -18
  41. data/lib/iodine/rack_utils.rb +43 -0
  42. data/lib/iodine/version.rb +1 -1
  43. data/lib/rack/handler/iodine.rb +1 -182
  44. metadata +17 -18
  45. data/ext/iodine/iodine_protocol.c +0 -689
  46. data/ext/iodine/iodine_protocol.h +0 -13
  47. data/ext/iodine/iodine_websockets.c +0 -550
  48. data/ext/iodine/iodine_websockets.h +0 -17
  49. data/ext/iodine/rb-call.c +0 -156
  50. data/ext/iodine/rb-call.h +0 -70
  51. data/ext/iodine/rb-defer.c +0 -124
  52. data/ext/iodine/rb-registry.c +0 -150
  53. data/ext/iodine/rb-registry.h +0 -34
  54. data/lib/iodine/cli.rb +0 -89
  55. data/lib/iodine/monkeypatch.rb +0 -46
  56. data/lib/iodine/protocol.rb +0 -42
  57. data/lib/iodine/websocket.rb +0 -16
@@ -9,9 +9,9 @@ class PubSubReporter < Iodine::PubSub::Engine
9
9
  # make sure engine setup is complete
10
10
  super
11
11
  # register engine and make it into the new default
12
- @target = Iodine::PubSub.default_engine
13
- Iodine::PubSub.default_engine = self
14
- Iodine::PubSub.register self
12
+ @target = Iodine::PubSub.default
13
+ Iodine::PubSub.default = self
14
+ Iodine::PubSub.attach self
15
15
  end
16
16
  def subscribe to, as = nil
17
17
  puts "* Subscribing to \"#{to}\" (#{as || "exact match"})"
@@ -57,22 +57,22 @@ class PubSubClient
57
57
  @name = name
58
58
  end
59
59
  # seng a message to new clients.
60
- def on_open
61
- subscribe "chat"
60
+ def on_open(client)
61
+ client.subscribe "chat"
62
62
  # let everyone know we arrived
63
- publish "chat", "#{@name} entered the chat."
63
+ client.publish "chat", "#{@name} entered the chat."
64
64
  end
65
65
  # send a message, letting the client know the server is suggunt down.
66
- def on_shutdown
67
- write "Server shutting down. Goodbye."
66
+ def on_shutdown(client)
67
+ client.write "Server shutting down. Goodbye."
68
68
  end
69
69
  # perform the echo
70
- def on_message data
71
- publish "chat", "#{@name}: #{data}"
70
+ def on_message(client, data)
71
+ client.publish "chat", "#{@name}: #{data}"
72
72
  end
73
- def on_close
73
+ def on_close(client)
74
74
  # let everyone know we left
75
- publish "chat", "#{@name} left the chat."
75
+ client.publish "chat", "#{@name} left the chat."
76
76
  # we don't need to unsubscribe, subscriptions are cleared automatically once the connection is closed.
77
77
  end
78
78
  end
@@ -5,12 +5,10 @@
5
5
  # REDIS_URL=redis://localhost:6379/0 iodine -t 1 -p 3000 redis.ru
6
6
  # REDIS_URL=redis://localhost:6379/0 iodine -t 1 -p 3030 redis.ru
7
7
  #
8
- require 'uri'
9
8
  require 'iodine'
10
9
  # initialize the Redis engine for each Iodine process.
11
10
  if ENV["REDIS_URL"]
12
- uri = URI(ENV["REDIS_URL"])
13
- Iodine.default_pubsub = Iodine::PubSub::RedisEngine.new(uri.host, uri.port, 0, uri.password)
11
+ Iodine::PubSub.default = Iodine::PubSub::Redis.new(ENV["REDIS_URL"], ping: 10)
14
12
  else
15
13
  puts "* No Redis, it's okay, pub/sub will support the process cluster."
16
14
  end
@@ -43,22 +41,22 @@ class WS_RedisPubSub
43
41
  @name = name
44
42
  end
45
43
  # seng a message to new clients.
46
- def on_open
47
- subscribe "chat"
44
+ def on_open client
45
+ client.subscribe "chat"
48
46
  # let everyone know we arrived
49
- publish "chat", "#{@name} entered the chat."
47
+ client.publish "chat", "#{@name} entered the chat."
50
48
  end
51
49
  # send a message, letting the client know the server is suggunt down.
52
- def on_shutdown
53
- write "Server shutting down. Goodbye."
50
+ def on_shutdown client
51
+ client.write "Server shutting down. Goodbye."
54
52
  end
55
53
  # perform the echo
56
- def on_message data
57
- publish "chat", "#{@name}: #{data}"
54
+ def on_message client, data
55
+ client.publish "chat", "#{@name}: #{data}"
58
56
  end
59
- def on_close
57
+ def on_close client
60
58
  # let everyone know we left
61
- publish "chat", "#{@name} left the chat."
59
+ client.publish "chat", "#{@name} left the chat."
62
60
  # we don't need to unsubscribe, subscriptions are cleared automatically once the connection is closed.
63
61
  end
64
62
  end
@@ -1,22 +1,12 @@
1
1
  require 'iodine'
2
2
  require 'json'
3
3
 
4
- # ON_IDLE = proc { Iodine::Base.db_print_registry ; Iodine.on_idle(&ON_IDLE) }
5
- # ON_IDLE.call
6
-
7
- class ShootoutApp
4
+ module ShootoutApp
8
5
  # the default HTTP response
9
6
  def self.call(env)
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
7
+ if(env['rack.upgrade?'.freeze] == :websocket)
8
+ env['rack.upgrade'.freeze] = ShootoutApp
9
+ return [0, {}, []]
20
10
  end
21
11
  out = []
22
12
  len = 0
@@ -36,46 +26,27 @@ class ShootoutApp
36
26
  # We'll base the shootout on the internal Pub/Sub service.
37
27
  # It's slower than writing to every socket a pre-parsed message, but it's closer
38
28
  # to real-life implementations.
39
- def on_open
40
- if(Iodine::VERSION >= "0.5.0")
41
- subscribe :shootout, as: :binary
42
- else
43
- subscribe channel: :shootout
44
- end
29
+ def self.on_open client
30
+ client.subscribe :shootout_b, as: :binary
31
+ client.subscribe :shootout
45
32
  end
46
- def on_message data
33
+ def self.on_message client, data
47
34
  if data[0] == 'b' # binary
48
- if(Iodine::VERSION >= "0.5.0")
49
- publish :shootout, data
50
- else
51
- publish channel: :shootout, message: data
52
- end
35
+ client.publish :shootout_b, data
53
36
  data[0] = 'r'
54
- write data
37
+ client.write data
55
38
  return
56
39
  end
57
40
  cmd, payload = JSON(data).values_at('type', 'payload')
58
41
  if cmd == 'echo'
59
- write({type: 'echo', payload: payload}.to_json)
42
+ client.write({type: 'echo', payload: payload}.to_json)
60
43
  else
61
- # data = {type: 'broadcast', payload: payload}.to_json
62
- # broadcast :push2client, data
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
68
- write({type: "broadcastResult", payload: payload}.to_json)
44
+ client.publish :shootout, {type: 'broadcast', payload: payload}.to_json
45
+ client.write({type: "broadcastResult", payload: payload}.to_json)
69
46
  end
70
- rescue
71
- puts "Incoming message format error - not JSON?"
72
47
  end
73
48
  end
74
49
 
75
- # if defined?(Iodine)
76
- # Iodine.run_every(5000) { Iodine::Base.db_print_registry }
77
- # end
78
-
79
50
  run ShootoutApp
80
51
  #
81
52
  # def cycle
@@ -85,3 +56,9 @@ run ShootoutApp
85
56
  # true
86
57
  # end
87
58
  # sleep(10) while cycle
59
+
60
+ # # Used when debugging:
61
+ # ON_IDLE = proc { Iodine::Base.db_print_protected_objects ; Iodine.on_idle(&ON_IDLE) }
62
+ # ON_IDLE.call
63
+ # Iodine.on_shutdown { Iodine::Base.db_print_protected_objects }
64
+
data/exe/iodine CHANGED
@@ -1,5 +1,120 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'rack'
3
+ require 'iodine'
2
4
 
3
- require 'iodine/cli'
5
+ module Iodine
6
+ # The Iodine::Base namespace is reserved for internal use and is NOT part of the public API.
7
+ module Base
8
+ # Command line interface. The Ruby CLI might be changed in future versions.
9
+ module CLI
10
+
11
+ def print_help
12
+ puts <<-EOS
13
+
14
+ Iodine's HTTP/Websocket server version #{Iodine::VERSION}
15
+
16
+ Use:
17
+
18
+ iodine <options> <filename>
19
+
20
+ Both <options> and <filename> are optional.
21
+
22
+ Available options:
23
+ -b Binding address. Default: nil (same as 0.0.0.0).
24
+ -p Port number. Default: 3000.
25
+ -t Number of threads. Default: CPU core count.
26
+ -w Number of worker processes. Default: CPU core count.
27
+ -www Public folder for static file serving. Default: nil (none).
28
+ -v Log responses. Default: never log responses.
29
+ -warmup Warmup invokes autoloading (lazy loading) during server startup.
30
+ -tout HTTP inactivity connection timeout. Default: 40 seconds.
31
+ -maxhead Maximum total headers length per HTTP request. Default: 32Kb.
32
+ -maxbd Maximum Mb per HTTP message (max body size). Default: 50Mb.
33
+ -maxms Maximum Bytes per Websocket message. Default: 250Kb.
34
+ -ping WebSocket / SSE ping interval in seconds. Default: 40 seconds.
35
+ <filename> Defaults to: config.ru
36
+
37
+ Example:
38
+
39
+ iodine -p 80
40
+
41
+ iodine -p 8080 path/to/app/conf.ru
42
+
43
+ iodine -p 8080 -w 4 -t 16
44
+
45
+ EOS
46
+ end
47
+
48
+
49
+ def try_file filename
50
+ return nil unless File.exist? filename
51
+ return ::Rack::Builder.parse_file filename
52
+ end
53
+
54
+ def filename_argument
55
+ return ((ARGV[-2].to_s[0] != '-' || ARGV[-2].to_s == '-warmup' || ARGV[-2].to_s == '-v' || ARGV[-2].to_s == '-q' || (ARGV[-2].to_s[0] == '-' && ARGV[-2].to_i.to_s == ARGV[-2].to_s)) && ARGV[-1].to_s[0] != '-' && ARGV[-1])
56
+ end
57
+
58
+ def get_app_opts
59
+ app, opt = nil, nil
60
+ filename = filename_argument
61
+ if filename
62
+ app, opt = try_file filename;
63
+ unless opt
64
+ puts "* Couldn't find #{filename}\n testing for config.ru\n"
65
+ app, opt = try_file "config.ru"
66
+ end
67
+ else
68
+ app, opt = try_file "config.ru";
69
+ end
70
+
71
+ unless opt
72
+ puts "WARNING: Ruby application not found#{ filename ? " - missing both #{filename} and config.ru" : " - missing config.ru"}."
73
+ if Iodine::DEFAULT_HTTP_ARGS[:public]
74
+ puts " Running only static file service."
75
+ opt = ::Rack::Server::Options.new.parse!([])
76
+ else
77
+ puts "For help run:"
78
+ puts " iodine -?"
79
+ exit(0);
80
+ end
81
+ end
82
+ return app, opt
83
+ end
84
+
85
+ def perform_warmup
86
+ # load anything marked with `autoload`, since autoload isn't thread safe nor fork friendly.
87
+ Iodine.run do
88
+ Module.constants.each do |n|
89
+ begin
90
+ Object.const_get(n)
91
+ rescue Exception => _e
92
+ end
93
+ end
94
+ ::Rack::Builder.new(app) do |r|
95
+ r.warmup do |a|
96
+ client = ::Rack::MockRequest.new(a)
97
+ client.get('/')
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ def call
104
+ if ARGV[0] =~ /(\-\?)|(help)|(\?)|(h)|(\-h)$/
105
+ return print_help
106
+ end
107
+
108
+ app, opt = get_app_opts
109
+
110
+ perform_warmup if ARGV.index('-warmup')
111
+
112
+ Iodine::Rack.run(app, opt)
113
+ end
114
+
115
+ extend self
116
+ end
117
+ end
118
+ end
4
119
 
5
120
  Iodine::Base::CLI.call
@@ -289,7 +289,7 @@ int defer_join_thread(void *p_thr) {
289
289
  if (!p_thr || !(*((pthread_t *)p_thr)))
290
290
  return -1;
291
291
  pthread_join(*((pthread_t *)p_thr), NULL);
292
- *((pthread_t *)p_thr) = NULL;
292
+ *((pthread_t *)p_thr) = (pthread_t)NULL;
293
293
  defer_free_thread(p_thr);
294
294
  return 0;
295
295
  }
@@ -135,7 +135,7 @@ static const char *CLUSTER_CONNECTION_PROTOCOL_NAME =
135
135
  "cluster connection __facil_internal__";
136
136
 
137
137
  static inline int is_counted_protocol(protocol_s *p) {
138
- return p->service != TIMER_PROTOCOL_NAME &&
138
+ return p && p->service != TIMER_PROTOCOL_NAME &&
139
139
  p->service != CLUSTER_LISTEN_PROTOCOL_NAME &&
140
140
  p->service != CLUSTER_CONNECTION_PROTOCOL_NAME;
141
141
  }
@@ -419,6 +419,10 @@ Initialization and Cleanup
419
419
  ***************************************************************************** */
420
420
  static spn_lock_i facil_libinit_lock = SPN_LOCK_INIT;
421
421
 
422
+ /** Rounds up any size to the nearest page alignment (assumes 4096 bytes per
423
+ * page) */
424
+ #define round_size(size) (((size) & (~4095)) + (4096 * (!!((size)&4095))))
425
+
422
426
  static void facil_cluster_cleanup(void); /* cluster data cleanup */
423
427
 
424
428
  static void facil_libcleanup(void) {
@@ -427,10 +431,10 @@ static void facil_libcleanup(void) {
427
431
  if (facil_data) {
428
432
  facil_external_root_cleanup();
429
433
  facil_cluster_cleanup();
430
- defer_perform(); /* perform any lingering cleanup tasks */
431
- munmap(facil_data,
432
- sizeof(*facil_data) + ((size_t)facil_data->capacity *
433
- sizeof(struct connection_data_s)));
434
+ // defer_perform(); /* perform any lingering cleanup tasks? */
435
+ size_t mem_size = sizeof(*facil_data) + ((size_t)facil_data->capacity *
436
+ sizeof(struct connection_data_s));
437
+ munmap(facil_data, round_size(mem_size));
434
438
  facil_data = NULL;
435
439
  }
436
440
  spn_unlock(&facil_libinit_lock);
@@ -447,7 +451,7 @@ static void facil_lib_init(void) {
447
451
  spn_lock(&facil_libinit_lock);
448
452
  if (facil_data)
449
453
  goto finish;
450
- facil_data = mmap(NULL, mem_size, PROT_READ | PROT_WRITE,
454
+ facil_data = mmap(NULL, round_size(mem_size), PROT_READ | PROT_WRITE,
451
455
  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
452
456
  if (!facil_data || facil_data == MAP_FAILED) {
453
457
  perror("ERROR: Couldn't initialize the facil.io library");
@@ -1677,7 +1681,7 @@ static void facil_worker_cleanup(void) {
1677
1681
  facil_cluster_signal_children();
1678
1682
  for (int i = 0; i <= facil_data->capacity; ++i) {
1679
1683
  intptr_t uuid;
1680
- if (fd_data(i).protocol && is_counted_protocol(fd_data(i).protocol) &&
1684
+ if (is_counted_protocol(fd_data(i).protocol) &&
1681
1685
  (uuid = sock_fd2uuid(i)) >= 0) {
1682
1686
  defer(deferred_on_shutdown, (void *)uuid, NULL);
1683
1687
  }
@@ -2059,7 +2063,7 @@ static int facil_attach_state(intptr_t uuid, protocol_s *protocol,
2059
2063
  spn_sub(&facil_data->connection_count, 1);
2060
2064
  }
2061
2065
  defer(deferred_on_close, (void *)uuid, old_protocol);
2062
- } else if (evio_isactive()) {
2066
+ } else if (evio_isactive() && protocol) {
2063
2067
  return evio_add(sock_uuid2fd(uuid), (void *)uuid);
2064
2068
  }
2065
2069
  return 0;
@@ -564,8 +564,8 @@ void facil_cluster_set_handler(int32_t filter,
564
564
 
565
565
  Unknown `msg_type` values are silently ignored.
566
566
 
567
- The `msg_type` value can be any number less than 1,073,741,824. All values
568
- starting at 1,073,741,824 are reserved for internal use.
567
+ The `msg_type` value can be any positive number less than 1,073,741,824. All
568
+ negative values and values above 1,073,741,824 are reserved for internal use.
569
569
 
570
570
  Callbacks are invoked using an O(n) matching, where `n` is the number of
571
571
  registered callbacks.
@@ -1,158 +1,32 @@
1
1
  #include "iodine.h"
2
- #include "iodine_helpers.h"
3
- #include "iodine_http.h"
4
- #include "iodine_protocol.h"
5
- #include "iodine_pubsub.h"
6
- #include "iodine_websockets.h"
7
- #include "rb-rack-io.h"
8
- #include <dlfcn.h>
9
- /*
10
- Copyright: Boaz segev, 2016-2017
11
- License: MIT
12
-
13
- Feel free to copy, use and enjoy according to the license provided.
14
- */
15
2
 
16
- VALUE Iodine;
17
- VALUE IodineBase;
18
-
19
- VALUE iodine_force_var_id;
20
- VALUE iodine_channel_var_id;
21
- VALUE iodine_pattern_var_id;
22
- VALUE iodine_text_var_id;
23
- VALUE iodine_binary_var_id;
24
- VALUE iodine_engine_var_id;
25
- VALUE iodine_message_var_id;
26
-
27
- ID iodine_fd_var_id;
28
- ID iodine_cdata_var_id;
29
- ID iodine_timeout_var_id;
30
- ID iodine_call_proc_id;
31
- ID iodine_new_func_id;
32
- ID iodine_on_open_func_id;
33
- ID iodine_on_message_func_id;
34
- ID iodine_on_data_func_id;
35
- ID iodine_on_drained_func_id;
36
- ID iodine_on_shutdown_func_id;
37
- ID iodine_on_close_func_id;
38
- ID iodine_ping_func_id;
39
- ID iodine_buff_var_id;
40
- ID iodine_to_s_method_id;
41
- ID iodine_to_i_func_id;
42
-
43
- rb_encoding *IodineBinaryEncoding;
44
- rb_encoding *IodineUTF8Encoding;
45
- int IodineBinaryEncodingIndex;
46
- int IodineUTF8EncodingIndex;
3
+ #include <ruby/version.h>
47
4
 
5
+ #include "facil.h"
48
6
  /* *****************************************************************************
49
- Internal helpers
7
+ OS specific patches
50
8
  ***************************************************************************** */
51
9
 
52
- static void iodine_run_task(void *block_) {
53
- RubyCaller.call((VALUE)block_, iodine_call_proc_id);
54
- }
10
+ #ifdef __APPLE__
11
+ #include <dlfcn.h>
12
+ #endif
55
13
 
56
- static void iodine_perform_deferred(void *block_, void *ignr) {
57
- RubyCaller.call((VALUE)block_, iodine_call_proc_id);
58
- Registry.remove((VALUE)block_);
59
- (void)ignr;
14
+ /** Any patches required by the running environment for consistent behavior */
15
+ static void patch_env(void) {
16
+ #ifdef __APPLE__
17
+ /* patch for dealing with the High Sierra `fork` limitations */
18
+ void *obj_c_runtime = dlopen("Foundation.framework/Foundation", RTLD_LAZY);
19
+ (void)obj_c_runtime;
20
+ #endif
60
21
  }
61
22
 
62
23
  /* *****************************************************************************
63
- Published functions
24
+ Constants and State
64
25
  ***************************************************************************** */
65
26
 
66
- /** Returns the number of total connections managed by Iodine. */
67
- static VALUE iodine_count(VALUE self) {
68
- size_t count = facil_count(NULL);
69
- return ULL2NUM(count);
70
- (void)self;
71
- }
72
-
73
- /**
74
- Runs the required block after the specified number of milliseconds have passed.
75
- Time is counted only once Iodine started running (using {Iodine.start}).
76
-
77
- Tasks scheduled before calling {Iodine.start} will run once for every process.
78
-
79
- Always returns a copy of the block object.
80
- */
81
- static VALUE iodine_run_after(VALUE self, VALUE milliseconds) {
82
- (void)(self);
83
- if (TYPE(milliseconds) != T_FIXNUM) {
84
- rb_raise(rb_eTypeError, "milliseconds must be a number");
85
- return Qnil;
86
- }
87
- size_t milli = FIX2UINT(milliseconds);
88
- // requires a block to be passed
89
- rb_need_block();
90
- VALUE block = rb_block_proc();
91
- if (block == Qnil)
92
- return Qfalse;
93
- Registry.add(block);
94
- if (facil_run_every(milli, 1, iodine_run_task, (void *)block,
95
- (void (*)(void *))Registry.remove) == -1) {
96
- perror("ERROR: Iodine couldn't initialize timer");
97
- return Qnil;
98
- }
99
- return block;
100
- }
101
- /**
102
- Runs the required block after the specified number of milliseconds have passed.
103
- Time is counted only once Iodine started running (using {Iodine.start}).
104
-
105
- Accepts:
106
-
107
- milliseconds:: the number of milliseconds between event repetitions.
108
-
109
- repetitions:: the number of event repetitions. Defaults to 0 (never ending).
110
-
111
- block:: (required) a block is required, as otherwise there is nothing to
112
- perform.
113
-
114
- The event will repeat itself until the number of repetitions had been delpeted.
115
-
116
- Always returns a copy of the block object.
117
- */
118
- static VALUE iodine_run_every(int argc, VALUE *argv, VALUE self) {
119
- (void)(self);
120
- VALUE milliseconds, repetitions, block;
121
-
122
- rb_scan_args(argc, argv, "11&", &milliseconds, &repetitions, &block);
123
-
124
- if (TYPE(milliseconds) != T_FIXNUM) {
125
- rb_raise(rb_eTypeError, "milliseconds must be a number.");
126
- return Qnil;
127
- }
128
- if (repetitions != Qnil && TYPE(repetitions) != T_FIXNUM) {
129
- rb_raise(rb_eTypeError, "repetitions must be a number or `nil`.");
130
- return Qnil;
131
- }
132
-
133
- size_t milli = FIX2UINT(milliseconds);
134
- size_t repeat = (repetitions == Qnil) ? 0 : FIX2UINT(repetitions);
135
- // requires a block to be passed
136
- rb_need_block();
137
- Registry.add(block);
138
- if (facil_run_every(milli, repeat, iodine_run_task, (void *)block,
139
- (void (*)(void *))Registry.remove) == -1) {
140
- perror("ERROR: Iodine couldn't initialize timer");
141
- return Qnil;
142
- }
143
- return block;
144
- }
145
-
146
- static VALUE iodine_run(VALUE self) {
147
- rb_need_block();
148
- VALUE block = rb_block_proc();
149
- if (block == Qnil)
150
- return Qfalse;
151
- Registry.add(block);
152
- defer(iodine_perform_deferred, (void *)block, NULL);
153
- return block;
154
- (void)self;
155
- }
27
+ VALUE IodineModule;
28
+ VALUE IodineBaseModule;
29
+ static ID call_id;
156
30
 
157
31
  /* *****************************************************************************
158
32
  Idling
@@ -163,6 +37,11 @@ Idling
163
37
  static spn_lock_i iodine_on_idle_lock = SPN_LOCK_INIT;
164
38
  static fio_ls_s iodine_on_idle_list = FIO_LS_INIT(iodine_on_idle_list);
165
39
 
40
+ static void iodine_perform_deferred(void *block, void *ignr) {
41
+ IodineCaller.call((VALUE)block, call_id);
42
+ (void)ignr;
43
+ }
44
+
166
45
  /**
167
46
  Schedules a single occuring event for the next idle cycle.
168
47
 
@@ -177,7 +56,7 @@ i.e.
177
56
  VALUE iodine_sched_on_idle(VALUE self) {
178
57
  rb_need_block();
179
58
  VALUE block = rb_block_proc();
180
- Registry.add(block);
59
+ IodineStore.add(block);
181
60
  spn_lock(&iodine_on_idle_lock);
182
61
  fio_ls_push(&iodine_on_idle_list, (void *)block);
183
62
  spn_unlock(&iodine_on_idle_lock);
@@ -190,169 +69,141 @@ static void iodine_on_idle(void) {
190
69
  while (fio_ls_any(&iodine_on_idle_list)) {
191
70
  VALUE block = (VALUE)fio_ls_shift(&iodine_on_idle_list);
192
71
  defer(iodine_perform_deferred, (void *)block, NULL);
72
+ IodineStore.remove(block);
193
73
  }
194
74
  spn_unlock(&iodine_on_idle_lock);
195
75
  }
76
+
196
77
  /* *****************************************************************************
197
- Running the server
78
+ Running Iodine
198
79
  ***************************************************************************** */
199
80
 
200
- #include "spnlock.inc"
201
- #include <pthread.h>
202
-
203
- static volatile int sock_io_thread = 0;
204
- static pthread_t sock_io_pthread;
205
81
  typedef struct {
206
- size_t threads;
207
- size_t processes;
208
- } iodine_start_settings_s;
209
-
210
- static void *iodine_io_thread(void *arg) {
211
- (void)arg;
212
- struct timespec tm;
213
- while (sock_io_thread) {
214
- sock_flush_all();
215
- tm = (struct timespec){.tv_nsec = 0, .tv_sec = 1};
216
- nanosleep(&tm, NULL);
217
- }
82
+ int16_t threads;
83
+ int16_t workers;
84
+ } iodine_start_params_s;
85
+
86
+ static void *iodine_run_outside_GVL(void *params_) {
87
+ iodine_start_params_s *params = params_;
88
+ facil_run(.threads = params->threads, .processes = params->workers,
89
+ .on_idle = iodine_on_idle, .on_finish = iodine_defer_on_finish);
218
90
  return NULL;
219
91
  }
220
- void iodine_start_io_thread(void *a1, void *a2) {
221
- (void)a1;
222
- (void)a2;
223
- pthread_create(&sock_io_pthread, NULL, iodine_io_thread, NULL);
224
- }
225
- static void iodine_join_io_thread(void) {
226
- sock_io_thread = 0;
227
- if (sock_io_pthread) {
228
- pthread_join(sock_io_pthread, NULL);
229
- }
230
- sock_io_pthread = NULL;
231
- }
232
92
 
233
- static void *srv_start_no_gvl(void *s_) {
234
- iodine_start_settings_s *s = s_;
235
- sock_io_thread = 1;
236
- iodine_start_io_thread(NULL, NULL);
237
- fprintf(stderr, "\n");
238
- if (s->processes == 1 || (s->processes == 0 && s->threads > 0)) {
239
- /* single worker */
240
- RubyCaller.call(Iodine, rb_intern("before_fork"));
241
- RubyCaller.call(Iodine, rb_intern("after_fork"));
242
- }
243
- facil_run(.threads = s->threads, .processes = s->processes,
244
- .on_idle = iodine_on_idle, .on_finish = iodine_join_io_thread);
245
- return NULL;
246
- }
93
+ /* *****************************************************************************
94
+ Core API
95
+ ***************************************************************************** */
247
96
 
248
- static int iodine_review_rack_app(void) {
249
- /* Check for Iodine::Rack.app and perform the C equivalent:
250
- * Iodine::HTTP.listen app: @app, port: @port, address: @address, log: @log,
251
- * max_msg: max_msg, max_body: max_body, public: @public, ping:
252
- * @ws_timeout, timeout: @timeout
253
- */
254
-
255
- VALUE rack = rb_const_get(Iodine, rb_intern("Rack"));
256
- VALUE app = rb_ivar_get(rack, rb_intern("@app"));
257
- VALUE www = rb_ivar_get(rack, rb_intern("@public"));
258
- if ((app == Qnil || app == Qfalse) && (www == Qnil || www == Qfalse))
259
- return 0;
260
- VALUE opt = rb_hash_new();
261
- Registry.add(opt);
262
-
263
- rb_hash_aset(opt, ID2SYM(rb_intern("app")),
264
- rb_ivar_get(rack, rb_intern("@app")));
265
- rb_hash_aset(opt, ID2SYM(rb_intern("port")),
266
- rb_ivar_get(rack, rb_intern("@port")));
267
- rb_hash_aset(opt, ID2SYM(rb_intern("app")),
268
- rb_ivar_get(rack, rb_intern("@app")));
269
- rb_hash_aset(opt, ID2SYM(rb_intern("address")),
270
- rb_ivar_get(rack, rb_intern("@address")));
271
- rb_hash_aset(opt, ID2SYM(rb_intern("log")),
272
- rb_ivar_get(rack, rb_intern("@log")));
273
- rb_hash_aset(opt, ID2SYM(rb_intern("max_msg")),
274
- rb_ivar_get(rack, rb_intern("@max_msg")));
275
- rb_hash_aset(opt, ID2SYM(rb_intern("max_body")),
276
- rb_ivar_get(rack, rb_intern("@max_body")));
277
- rb_hash_aset(opt, ID2SYM(rb_intern("public")),
278
- rb_ivar_get(rack, rb_intern("@public")));
279
- rb_hash_aset(opt, ID2SYM(rb_intern("ping")),
280
- rb_ivar_get(rack, rb_intern("@ws_timeout")));
281
- rb_hash_aset(opt, ID2SYM(rb_intern("timeout")),
282
- rb_ivar_get(rack, rb_intern("@ws_timeout")));
283
- rb_hash_aset(opt, ID2SYM(rb_intern("max_headers")),
284
- rb_ivar_get(rack, rb_intern("@max_headers")));
285
- if (rb_funcall2(Iodine, rb_intern("listen2http"), 1, &opt) == Qfalse) {
286
- Registry.remove(opt);
287
- return -1;
288
- }
289
- Registry.remove(opt);
290
- return 0;
97
+ /**
98
+ * Returns the number of worker threads that will be used when {Iodine.start}
99
+ * is called.
100
+ *
101
+ * Negative numbers are translated as fractions of the number of CPU cores.
102
+ * i.e., -2 == half the number of detected CPU cores.
103
+ *
104
+ * Zero values promise nothing (iodine will decide what to do with them).
105
+ */
106
+ static VALUE iodine_threads_get(VALUE self) {
107
+ VALUE i = rb_ivar_get(self, rb_intern2("@threads", 8));
108
+ if (i == Qnil)
109
+ i = INT2NUM(0);
110
+ return i;
291
111
  }
292
112
 
293
113
  /**
294
- Starts the Iodine event loop. This will hang the thread until an interrupt
295
- (`^C`) signal is received.
296
-
297
- Returns the Iodine module.
298
- */
299
- static VALUE iodine_start(VALUE self) {
300
- /* re-register the Rack::Push namespace to point at Iodine */
301
- if (rb_const_defined(rb_cObject, rb_intern("Rack"))) {
302
- VALUE rack = rb_const_get(rb_cObject, rb_intern("Rack"));
303
- if (rack != Qnil) {
304
- if (rb_const_defined(rack, rb_intern("PubSub"))) {
305
- rb_const_remove(rack, rb_intern("PubSub"));
306
- }
307
- rb_const_set(rack, rb_intern("PubSub"), Iodine);
308
- }
114
+ * Sets the number of worker threads that will be used when {Iodine.start}
115
+ * is called.
116
+ *
117
+ * Negative numbers are translated as fractions of the number of CPU cores.
118
+ * i.e., -2 == half the number of detected CPU cores.
119
+ *
120
+ * Zero values promise nothing (iodine will decide what to do with them).
121
+ */
122
+ static VALUE iodine_threads_set(VALUE self, VALUE val) {
123
+ Check_Type(val, T_FIXNUM);
124
+ if (NUM2SSIZET(val) >= (1 << 12)) {
125
+ rb_raise(rb_eRangeError, "requsted thread count is out of range.");
309
126
  }
310
- /* for the special Iodine::Rack object and backwards compatibility */
311
- if (iodine_review_rack_app()) {
312
- fprintf(stderr, "ERROR: (iodine) cann't start Iodine::Rack.\n");
313
- return Qnil;
314
- }
315
-
316
- VALUE rb_th_i = rb_iv_get(Iodine, "@threads");
317
- VALUE rb_pr_i = rb_iv_get(Iodine, "@processes");
318
-
319
- iodine_start_settings_s s = {
320
- .threads = ((TYPE(rb_th_i) == T_FIXNUM) ? FIX2INT(rb_th_i) : 0),
321
- .processes = ((TYPE(rb_pr_i) == T_FIXNUM) ? FIX2INT(rb_pr_i) : 0)};
322
-
323
- RubyCaller.set_gvl_state(1);
324
- RubyCaller.leave_gvl(srv_start_no_gvl, (void *)&s);
325
-
326
- return self;
127
+ rb_ivar_set(self, rb_intern2("@threads", 8), val);
128
+ return val;
327
129
  }
328
130
 
329
- static VALUE iodine_is_running(VALUE self) {
330
- return (facil_is_running() ? Qtrue : Qfalse);
331
- (void)self;
131
+ /**
132
+ *Returns the number of worker processes that will be used when {Iodine.start}
133
+ * is called.
134
+ *
135
+ * Negative numbers are translated as fractions of the number of CPU cores.
136
+ * i.e., -2 == half the number of detected CPU cores.
137
+ *
138
+ * Zero values promise nothing (iodine will decide what to do with them).
139
+ */
140
+ static VALUE iodine_workers_get(VALUE self) {
141
+ VALUE i = rb_ivar_get(self, rb_intern2("@workers", 8));
142
+ if (i == Qnil)
143
+ i = INT2NUM(0);
144
+ return i;
332
145
  }
333
146
 
334
- /* *****************************************************************************
335
- Debug
336
- ***************************************************************************** */
337
-
338
- /** Used for debugging purpuses. Lists GC protected objects */
339
- VALUE iodine_print_registry(VALUE self) {
340
- Registry.print();
341
- return Qnil;
342
- (void)self;
147
+ /**
148
+ * Sets the number of worker processes that will be used when {Iodine.start}
149
+ * is called.
150
+ *
151
+ * Negative numbers are translated as fractions of the number of CPU cores.
152
+ * i.e., -2 == half the number of detected CPU cores.
153
+ *
154
+ * Zero values promise nothing (iodine will decide what to do with them).
155
+ */
156
+ static VALUE iodine_workers_set(VALUE self, VALUE val) {
157
+ Check_Type(val, T_FIXNUM);
158
+ if (NUM2SSIZET(val) >= (1 << 9)) {
159
+ rb_raise(rb_eRangeError, "requsted worker process count is out of range.");
160
+ }
161
+ rb_ivar_set(self, rb_intern2("@workers", 8), val);
162
+ return val;
343
163
  }
344
164
 
345
- /* *****************************************************************************
346
- Library Initialization
347
- ***************************************************************************** */
165
+ /** Prints the Iodine startup message */
166
+ static void iodine_print_startup_message(iodine_start_params_s params) {
167
+ VALUE iodine_version = rb_const_get(IodineModule, rb_intern("VERSION"));
168
+ VALUE ruby_version = rb_const_get(IodineModule, rb_intern("RUBY_VERSION"));
169
+ facil_expected_concurrency(&params.threads, &params.workers);
170
+ fprintf(stderr,
171
+ "\nStarting up Iodine:\n"
172
+ " * Ruby v.%s\n * Iodine v.%s\n"
173
+ " * %d Workers X %d Threads per worker.\n"
174
+ "\n",
175
+ StringValueCStr(ruby_version), StringValueCStr(iodine_version),
176
+ params.workers, params.threads);
177
+ (void)params;
178
+ }
348
179
 
349
- /** Any patches required by the running environment for consistent behavior */
350
- static void patch_env(void) {
351
- #ifdef __APPLE__
352
- /* patch for dealing with the High Sierra `fork` limitations */
353
- void *obj_c_runtime = dlopen("Foundation.framework/Foundation", RTLD_LAZY);
354
- (void)obj_c_runtime;
355
- #endif
180
+ /**
181
+ * This will block the calling (main) thread and start the Iodine reactor.
182
+ *
183
+ * When using cluster mode (2 or more worker processes), it is important that no
184
+ * other threads are active.
185
+ *
186
+ * For many reasons, `fork` should NOT be called while multi-threading, so
187
+ * cluster mode must always be initiated from the main thread in a single thread
188
+ * environment.
189
+ *
190
+ * For information about why forking in multi-threaded environments should be
191
+ * avoided, see (for example):
192
+ * http://www.linuxprogrammingblog.com/threads-and-fork-think-twice-before-using-them
193
+ *
194
+ */
195
+ static VALUE iodine_start(VALUE self) {
196
+ if (facil_is_running()) {
197
+ rb_raise(rb_eRuntimeError, "Iodine already running!");
198
+ }
199
+ VALUE threads_rb = iodine_threads_get(self);
200
+ VALUE workers_rb = iodine_workers_get(self);
201
+ iodine_start_params_s params = {
202
+ .threads = NUM2SHORT(threads_rb), .workers = NUM2SHORT(workers_rb),
203
+ };
204
+ iodine_print_startup_message(params);
205
+ IodineCaller.leaveGVL(iodine_run_outside_GVL, &params);
206
+ return self;
356
207
  }
357
208
 
358
209
  /* *****************************************************************************
@@ -362,64 +213,47 @@ Here we connect all the C code to the Ruby interface, completing the bridge
362
213
  between Lib-Server and Ruby.
363
214
  ***************************************************************************** */
364
215
  void Init_iodine(void) {
365
- // Set GVL for main thread
366
- RubyCaller.set_gvl_state(1);
367
216
  // load any environment specific patches
368
217
  patch_env();
369
- // initialize globally used IDs, for faster access to the Ruby layer.
370
- iodine_buff_var_id = rb_intern("scrtbuffer");
371
- iodine_call_proc_id = rb_intern("call");
372
- iodine_cdata_var_id = rb_intern("iodine_cdata");
373
- iodine_fd_var_id = rb_intern("iodine_fd");
374
- iodine_new_func_id = rb_intern("new");
375
- iodine_on_close_func_id = rb_intern("on_close");
376
- iodine_on_data_func_id = rb_intern("on_data");
377
- iodine_on_message_func_id = rb_intern("on_message");
378
- iodine_on_open_func_id = rb_intern("on_open");
379
- iodine_on_drained_func_id = rb_intern("on_drained");
380
- iodine_on_shutdown_func_id = rb_intern("on_shutdown");
381
- iodine_ping_func_id = rb_intern("ping");
382
- iodine_timeout_var_id = rb_intern("@timeout");
383
- iodine_to_i_func_id = rb_intern("to_i");
384
- iodine_to_s_method_id = rb_intern("to_s");
385
-
386
- iodine_binary_var_id = ID2SYM(rb_intern("binary"));
387
- iodine_channel_var_id = ID2SYM(rb_intern("channel"));
388
- iodine_engine_var_id = ID2SYM(rb_intern("engine"));
389
- iodine_force_var_id = ID2SYM(rb_intern("encoding"));
390
- iodine_message_var_id = ID2SYM(rb_intern("message"));
391
- iodine_pattern_var_id = ID2SYM(rb_intern("pattern"));
392
- iodine_text_var_id = ID2SYM(rb_intern("text"));
393
-
394
- IodineBinaryEncodingIndex = rb_enc_find_index("binary");
395
- IodineUTF8EncodingIndex = rb_enc_find_index("UTF-8");
396
- IodineBinaryEncoding = rb_enc_find("binary");
397
- IodineUTF8Encoding = rb_enc_find("UTF-8");
398
-
399
- // The core Iodine module wraps facil.io functionality and little more.
400
- Iodine = rb_define_module("Iodine");
401
-
402
- // the Iodine singleton functions
403
- rb_define_module_function(Iodine, "start", iodine_start, 0);
404
- rb_define_module_function(Iodine, "running?", iodine_is_running, 0);
405
- rb_define_singleton_method(Iodine, "count", iodine_count, 0);
406
- rb_define_module_function(Iodine, "run", iodine_run, 0);
407
- rb_define_module_function(Iodine, "run_after", iodine_run_after, 1);
408
- rb_define_module_function(Iodine, "run_every", iodine_run_every, -1);
409
- rb_define_module_function(Iodine, "on_idle", iodine_sched_on_idle, 0);
410
-
411
- /// Iodine::Base is for internal use.
412
- IodineBase = rb_define_module_under(Iodine, "Base");
413
- rb_define_module_function(IodineBase, "db_print_registry",
414
- iodine_print_registry, 0);
415
-
416
- // Initialize the registry under the Iodine core
417
- Registry.init(Iodine);
418
-
419
- /* Initialize the rest of the library. */
420
- Iodine_init_protocol();
421
- Iodine_init_pubsub();
422
- Iodine_init_http();
423
- Iodine_init_websocket();
424
- Iodine_init_helpers();
218
+
219
+ // force the GVL state for the main thread
220
+ IodineCaller.set_GVL(1);
221
+
222
+ // Create the Iodine module (namespace)
223
+ IodineModule = rb_define_module("Iodine");
224
+ IodineBaseModule = rb_define_module_under(IodineModule, "Base");
225
+ call_id = rb_intern2("call", 4);
226
+
227
+ // register core methods
228
+ rb_define_module_function(IodineModule, "threads", iodine_threads_get, 0);
229
+ rb_define_module_function(IodineModule, "threads=", iodine_threads_set, 1);
230
+ rb_define_module_function(IodineModule, "workers", iodine_workers_get, 0);
231
+ rb_define_module_function(IodineModule, "workers=", iodine_workers_set, 1);
232
+ rb_define_module_function(IodineModule, "start", iodine_start, 0);
233
+ rb_define_module_function(IodineModule, "on_idle", iodine_sched_on_idle, 0);
234
+
235
+ // initialize Object storage for GC protection
236
+ iodine_storage_init();
237
+
238
+ // initialize concurrency related methods
239
+ iodine_defer_initialize();
240
+
241
+ // initialize the connection class
242
+ iodine_connection_init();
243
+
244
+ // intialize the TCP/IP related module
245
+ iodine_init_tcp_connections();
246
+
247
+ // initialize the HTTP module
248
+ iodine_init_http();
249
+
250
+ // initialize JSON helpers
251
+ iodine_init_json();
252
+
253
+ // initialize Rack helpers and IO
254
+ iodine_init_helpers();
255
+ IodineRackIO.init();
256
+
257
+ // initialize Pub/Sub extension (for Engines)
258
+ iodine_pubsub_init();
425
259
  }