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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +63 -100
- data/bin/raw-rbhttp +12 -7
- data/examples/config.ru +8 -7
- data/examples/echo.ru +8 -7
- data/examples/info.md +41 -35
- data/examples/pubsub_engine.ru +12 -12
- data/examples/redis.ru +10 -12
- data/examples/shootout.ru +19 -42
- data/exe/iodine +116 -1
- data/ext/iodine/defer.c +1 -1
- data/ext/iodine/facil.c +12 -8
- data/ext/iodine/facil.h +2 -2
- data/ext/iodine/iodine.c +177 -343
- data/ext/iodine/iodine.h +18 -72
- data/ext/iodine/iodine_caller.c +132 -0
- data/ext/iodine/iodine_caller.h +21 -0
- data/ext/iodine/iodine_connection.c +841 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +391 -0
- data/ext/iodine/iodine_defer.h +7 -0
- data/ext/iodine/{rb-fiobj2rb.h → iodine_fiobj2rb.h} +6 -6
- data/ext/iodine/iodine_helpers.c +51 -5
- data/ext/iodine/iodine_helpers.h +2 -3
- data/ext/iodine/iodine_http.c +284 -141
- data/ext/iodine/iodine_http.h +2 -2
- data/ext/iodine/iodine_json.c +13 -13
- data/ext/iodine/iodine_json.h +1 -1
- data/ext/iodine/iodine_pubsub.c +573 -823
- data/ext/iodine/iodine_pubsub.h +15 -27
- data/ext/iodine/{rb-rack-io.c → iodine_rack_io.c} +30 -8
- data/ext/iodine/{rb-rack-io.h → iodine_rack_io.h} +1 -0
- data/ext/iodine/iodine_store.c +136 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +385 -0
- data/ext/iodine/iodine_tcp.h +9 -0
- data/lib/iodine.rb +73 -171
- data/lib/iodine/connection.rb +34 -0
- data/lib/iodine/pubsub.rb +5 -18
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +1 -182
- metadata +17 -18
- data/ext/iodine/iodine_protocol.c +0 -689
- data/ext/iodine/iodine_protocol.h +0 -13
- data/ext/iodine/iodine_websockets.c +0 -550
- data/ext/iodine/iodine_websockets.h +0 -17
- data/ext/iodine/rb-call.c +0 -156
- data/ext/iodine/rb-call.h +0 -70
- data/ext/iodine/rb-defer.c +0 -124
- data/ext/iodine/rb-registry.c +0 -150
- data/ext/iodine/rb-registry.h +0 -34
- data/lib/iodine/cli.rb +0 -89
- data/lib/iodine/monkeypatch.rb +0 -46
- data/lib/iodine/protocol.rb +0 -42
- data/lib/iodine/websocket.rb +0 -16
data/examples/pubsub_engine.ru
CHANGED
@@ -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.
|
13
|
-
Iodine::PubSub.
|
14
|
-
Iodine::PubSub.
|
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
|
data/examples/redis.ru
CHANGED
@@ -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
|
-
|
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
|
data/examples/shootout.ru
CHANGED
@@ -1,22 +1,12 @@
|
|
1
1
|
require 'iodine'
|
2
2
|
require 'json'
|
3
3
|
|
4
|
-
|
5
|
-
# ON_IDLE.call
|
6
|
-
|
7
|
-
class ShootoutApp
|
4
|
+
module ShootoutApp
|
8
5
|
# the default HTTP response
|
9
6
|
def self.call(env)
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
41
|
-
subscribe :shootout
|
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
|
-
|
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
|
-
|
62
|
-
|
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
|
-
|
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
|
data/ext/iodine/defer.c
CHANGED
@@ -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
|
}
|
data/ext/iodine/facil.c
CHANGED
@@ -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
|
-
|
432
|
-
|
433
|
-
|
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 (
|
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;
|
data/ext/iodine/facil.h
CHANGED
@@ -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
|
568
|
-
|
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.
|
data/ext/iodine/iodine.c
CHANGED
@@ -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
|
-
|
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
|
-
|
7
|
+
OS specific patches
|
50
8
|
***************************************************************************** */
|
51
9
|
|
52
|
-
|
53
|
-
|
54
|
-
|
10
|
+
#ifdef __APPLE__
|
11
|
+
#include <dlfcn.h>
|
12
|
+
#endif
|
55
13
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
24
|
+
Constants and State
|
64
25
|
***************************************************************************** */
|
65
26
|
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
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
|
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
|
-
|
207
|
-
|
208
|
-
}
|
209
|
-
|
210
|
-
static void *
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
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
|
-
|
311
|
-
|
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
|
-
|
330
|
-
|
331
|
-
|
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
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
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
|
-
|
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(¶ms.threads, ¶ms.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
|
-
/**
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
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, ¶ms);
|
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
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
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
|
}
|