iodine 0.7.32 → 0.7.33
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 +8 -0
- data/SPEC-WebSocket-Draft.md +5 -1
- data/examples/shootout.ru +1 -0
- data/exe/iodine +205 -7
- data/ext/iodine/fio.c +3 -4
- data/ext/iodine/iodine_caller.c +5 -1
- data/lib/iodine/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 430ea5b683f9561e44a9e1b4f6ab56f1b9c6e58637b3ecaae9f414eed58cc1ba
|
4
|
+
data.tar.gz: 17a876d312611ccb0892a9f41b6ae6ca9fa0257b8c78778a7a8edf545f3dc165
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3db4b691bae4fe0745c36b4d29a960bfd712e7cb3a29cc9f4e2f357a680f3c325b808bff8720a9f3835d8b0c86c725a0542c0d1800e2c38fd77198ee8f13399d
|
7
|
+
data.tar.gz: 0404f0211d42ece456f46bfa89b945f3c01d1b87770c612e276743aa3a82519b40e6705854f66a5bceb9d9e3fea27fe86ad9081d3a86271e5cccd07f6c6f0df8
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,14 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
6
6
|
|
7
7
|
## Changes:
|
8
8
|
|
9
|
+
#### Change log v.0.7.33
|
10
|
+
|
11
|
+
**Fix**: (`iodine`) exception protection would fail and crash if the exception throws wasn't of type `Exception`. I'm not sure how this would happen, but on some Ruby versions it appeared to have occur, maybe where a custom `raise` would be called with a non-exception type. The issue was fixed by testing for the availability of the `message` and `backtrace` functions. Credit to Jan Biedermann (@janbiedermann) for exposing this issue (#76).
|
12
|
+
|
13
|
+
**Fix**: (`cli`) the CLI interface no longer requires Rack to be installed. If Rack is installed, it will be used. Otherwise, a copy of the Rack::Builder code will execute, licensed under the MIT license from the Rack source code (Copyright (C) 2007-2019 [Leah Neukirchen](http://leahneukirchen.org/infopage.html)).
|
14
|
+
|
15
|
+
**Compatibility**: (`facil`) iodine would raise the signal `SIGINT` when shutting down in cluster mode, even if shutdown was initiated using `Iodine.stop`. Although this was designed to ensure worker processes would stop, this approach caused RSpec to stop testing and report an error. A temporary fix was applied and might be upstreamed to the facil.io repo. Credit to Jan Biedermann (@janbiedermann) for exposing this issue (#76).
|
16
|
+
|
9
17
|
#### Change log v.0.7.32
|
10
18
|
|
11
19
|
**Fix**: (`http1`) fixes a race-condition between the `on_ready` and `on_data` events, that could result in the `on_data` event being called twice instead of once (only possible with some clients). On multi-threaded workers, this could result in the CPU spinning while the task lock remains busy. Credit to Néstor Coppi (@Shelvak) for exposing the issue and providing an example application with detailed reports. Issue #75.
|
data/SPEC-WebSocket-Draft.md
CHANGED
@@ -25,6 +25,10 @@ Servers that publish WebSocket and/or EventSource (SSE) support using the `env['
|
|
25
25
|
|
26
26
|
This document reserves the Rack `env` Hash keys of `rack.upgrade?` and `rack.upgrade`.
|
27
27
|
|
28
|
+
A conforming server MUST set `env['rack.upgrade?']` to `:websocket` for incoming WebSocket connections and `:sse` for incoming EventSource (SSE) connections.
|
29
|
+
|
30
|
+
If a connection is not "upgradable", a conforming server SHOULD set `env['rack.upgrade?']` to either `nil` or `false`.
|
31
|
+
|
28
32
|
The historical use of `upgrade.websocket?` and `upgrade.websocket` (iodine 0.4.x) will be gradually deprecated.
|
29
33
|
|
30
34
|
### The WebSocket / EventSource Callback Object
|
@@ -82,7 +86,7 @@ The server **MUST** provide the Callback Object with a `client` object, that sup
|
|
82
86
|
|
83
87
|
\*Servers that divide large messages into a number of smaller messages (implement message fragmentation) MAY count each fragment separately, as if the fragmentation was performed by the user and `write` was called more than once per message.
|
84
88
|
|
85
|
-
* `env`
|
89
|
+
* `env` returns the Rack `env` hash related to the originating HTTP request. Some changes to the `env` hash (such as removal of the IO hijacking support) MAY be implemented by the server.
|
86
90
|
|
87
91
|
WebSocket `ping` / `pong`, timeouts and network considerations should be implemented by the server. It is **RECOMMENDED** (but not required) that the server send `ping`s to prevent connection timeouts and detect network failure.
|
88
92
|
|
data/examples/shootout.ru
CHANGED
data/exe/iodine
CHANGED
@@ -1,11 +1,209 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
require 'iodine'
|
4
|
+
|
5
|
+
# Load Rack if available (assume it will be used).
|
6
|
+
#
|
7
|
+
# Remember, code costs memory. Duplicating the Rack::Builder module and functions can be avoided.
|
2
8
|
begin
|
3
9
|
require 'rack'
|
10
|
+
module Iodine
|
11
|
+
module Base
|
12
|
+
module Rack
|
13
|
+
Builder = ::Rack::Builder
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
4
17
|
rescue LoadError
|
5
|
-
|
6
|
-
|
18
|
+
module Iodine
|
19
|
+
module Base
|
20
|
+
module Rack
|
21
|
+
# The Rack::Builder code is used when Rack isn't available.
|
22
|
+
#
|
23
|
+
# The code was copied (with minor adjustments) from the Rack source code and is licensed under the MIT license.
|
24
|
+
# Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
|
25
|
+
#
|
26
|
+
# ====
|
27
|
+
#
|
28
|
+
# Rack::Builder implements a small DSL to iteratively construct Rack
|
29
|
+
# applications.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
#
|
33
|
+
# require 'rack/lobster'
|
34
|
+
# app = Rack::Builder.new do
|
35
|
+
# use Rack::CommonLogger
|
36
|
+
# use Rack::ShowExceptions
|
37
|
+
# map "/lobster" do
|
38
|
+
# use Rack::Lint
|
39
|
+
# run Rack::Lobster.new
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# run app
|
44
|
+
#
|
45
|
+
# Or
|
46
|
+
#
|
47
|
+
# app = Rack::Builder.app do
|
48
|
+
# use Rack::CommonLogger
|
49
|
+
# run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# run app
|
53
|
+
#
|
54
|
+
# +use+ adds middleware to the stack, +run+ dispatches to an application.
|
55
|
+
# You can use +map+ to construct a Rack::URLMap in a convenient way.
|
56
|
+
class Builder
|
57
|
+
# https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
|
58
|
+
UTF_8_BOM = '\xef\xbb\xbf'
|
59
|
+
|
60
|
+
def self.parse_file(config, opts = Hash.new)
|
61
|
+
if config =~ /\.ru$/
|
62
|
+
return self.load_file(config, opts)
|
63
|
+
else
|
64
|
+
require config
|
65
|
+
app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
|
66
|
+
return app, {}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.load_file(path, options = Hash.new)
|
71
|
+
cfgfile = ::File.read(path)
|
72
|
+
cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
|
73
|
+
|
74
|
+
cfgfile.sub!(/^__END__\n.*\Z/m, '')
|
75
|
+
app = new_from_string cfgfile, path
|
76
|
+
|
77
|
+
return app, options
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.new_from_string(builder_script, file = "(rackup)")
|
81
|
+
eval "Iodine::Base::Rack::Builder.new {\n" + builder_script + "\n}.to_app",
|
82
|
+
TOPLEVEL_BINDING, file, 0
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(default_app = nil, &block)
|
86
|
+
@use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
|
87
|
+
instance_eval(&block) if block_given?
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.app(default_app = nil, &block)
|
91
|
+
self.new(default_app, &block).to_app
|
92
|
+
end
|
93
|
+
|
94
|
+
# Specifies middleware to use in a stack.
|
95
|
+
#
|
96
|
+
# class Middleware
|
97
|
+
# def initialize(app)
|
98
|
+
# @app = app
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# def call(env)
|
102
|
+
# env["rack.some_header"] = "setting an example"
|
103
|
+
# @app.call(env)
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# use Middleware
|
108
|
+
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
109
|
+
#
|
110
|
+
# All requests through to this application will first be processed by the middleware class.
|
111
|
+
# The +call+ method in this example sets an additional environment key which then can be
|
112
|
+
# referenced in the application if required.
|
113
|
+
def use(middleware, *args, &block)
|
114
|
+
if @map
|
115
|
+
mapping, @map = @map, nil
|
116
|
+
@use << proc { |app| generate_map(app, mapping) }
|
117
|
+
end
|
118
|
+
@use << proc { |app| middleware.new(app, *args, &block) }
|
119
|
+
end
|
120
|
+
|
121
|
+
# Takes an argument that is an object that responds to #call and returns a Rack response.
|
122
|
+
# The simplest form of this is a lambda object:
|
123
|
+
#
|
124
|
+
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
125
|
+
#
|
126
|
+
# However this could also be a class:
|
127
|
+
#
|
128
|
+
# class Heartbeat
|
129
|
+
# def self.call(env)
|
130
|
+
# [200, { "Content-Type" => "text/plain" }, ["OK"]]
|
131
|
+
# end
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# run Heartbeat
|
135
|
+
def run(app)
|
136
|
+
@run = app
|
137
|
+
end
|
138
|
+
|
139
|
+
# Takes a lambda or block that is used to warm-up the application.
|
140
|
+
#
|
141
|
+
# warmup do |app|
|
142
|
+
# client = Rack::MockRequest.new(app)
|
143
|
+
# client.get('/')
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# use SomeMiddleware
|
147
|
+
# run MyApp
|
148
|
+
def warmup(prc = nil, &block)
|
149
|
+
@warmup = prc || block
|
150
|
+
end
|
151
|
+
|
152
|
+
# Creates a route within the application.
|
153
|
+
#
|
154
|
+
# Rack::Builder.app do
|
155
|
+
# map '/' do
|
156
|
+
# run Heartbeat
|
157
|
+
# end
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# The +use+ method can also be used here to specify middleware to run under a specific path:
|
161
|
+
#
|
162
|
+
# Rack::Builder.app do
|
163
|
+
# map '/' do
|
164
|
+
# use Middleware
|
165
|
+
# run Heartbeat
|
166
|
+
# end
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
|
170
|
+
#
|
171
|
+
def map(path, &block)
|
172
|
+
@map ||= {}
|
173
|
+
@map[path] = block
|
174
|
+
end
|
175
|
+
|
176
|
+
# Freeze the app (set using run) and all middleware instances when building the application
|
177
|
+
# in to_app.
|
178
|
+
def freeze_app
|
179
|
+
@freeze_app = true
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_app
|
183
|
+
app = @map ? generate_map(@run, @map) : @run
|
184
|
+
fail "missing run or map statement" unless app
|
185
|
+
app.freeze if @freeze_app
|
186
|
+
app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
|
187
|
+
@warmup.call(app) if @warmup
|
188
|
+
app
|
189
|
+
end
|
190
|
+
|
191
|
+
def call(env)
|
192
|
+
to_app.call(env)
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def generate_map(default_app, mapping)
|
198
|
+
mapped = default_app ? { '/' => default_app } : {}
|
199
|
+
mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
|
200
|
+
URLMap.new(mapped)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
7
206
|
end
|
8
|
-
require 'iodine'
|
9
207
|
|
10
208
|
module Iodine
|
11
209
|
# The Iodine::Base namespace is reserved for internal use and is NOT part of the public API.
|
@@ -15,7 +213,7 @@ module Iodine
|
|
15
213
|
|
16
214
|
def self.try_file filename
|
17
215
|
return nil unless File.exist? filename
|
18
|
-
return ::Rack::Builder.parse_file filename
|
216
|
+
return Iodine::Base::Rack::Builder.parse_file filename
|
19
217
|
end
|
20
218
|
|
21
219
|
def self.get_app_opts
|
@@ -36,7 +234,7 @@ module Iodine
|
|
36
234
|
puts "WARNING: Ruby application not found#{ filename ? " - missing both #{filename} and config.ru" : " - missing config.ru"}."
|
37
235
|
if Iodine::DEFAULT_SETTINGS[:public]
|
38
236
|
puts " Running only static file service."
|
39
|
-
opt =
|
237
|
+
opt = Hash.new
|
40
238
|
app = Proc.new { [404, {}, "Not Found!"] }
|
41
239
|
else
|
42
240
|
puts "\nERROR: Couldn't run Ruby application, check command line arguments."
|
@@ -49,7 +247,7 @@ module Iodine
|
|
49
247
|
end
|
50
248
|
|
51
249
|
def self.perform_warmup(app)
|
52
|
-
# load anything marked with `autoload`, since autoload
|
250
|
+
# load anything marked with `autoload`, since autoload is niether thread safe nor fork friendly.
|
53
251
|
Iodine.on_state(:on_start) do
|
54
252
|
Module.constants.each do |n|
|
55
253
|
begin
|
@@ -57,7 +255,7 @@ module Iodine
|
|
57
255
|
rescue StandardError => _e
|
58
256
|
end
|
59
257
|
end
|
60
|
-
::Rack::Builder.new(app) do |r|
|
258
|
+
Iodine::Base::Rack::Builder.new(app) do |r|
|
61
259
|
r.warmup do |a|
|
62
260
|
client = ::Rack::MockRequest.new(a)
|
63
261
|
client.get('/')
|
data/ext/iodine/fio.c
CHANGED
@@ -3815,7 +3815,7 @@ static void fio_worker_cleanup(void) {
|
|
3815
3815
|
fio_state_callback_force(FIO_CALL_ON_FINISH);
|
3816
3816
|
fio_defer_perform();
|
3817
3817
|
if (!fio_data->is_worker) {
|
3818
|
-
|
3818
|
+
fio_cluster_signal_children();
|
3819
3819
|
while (wait(NULL) != -1)
|
3820
3820
|
;
|
3821
3821
|
}
|
@@ -6202,7 +6202,7 @@ static void fio_cluster_listen_on_close(intptr_t uuid,
|
|
6202
6202
|
(int)getpid());
|
6203
6203
|
#endif
|
6204
6204
|
if (fio_data->active)
|
6205
|
-
|
6205
|
+
fio_stop();
|
6206
6206
|
}
|
6207
6207
|
(void)uuid;
|
6208
6208
|
}
|
@@ -6244,7 +6244,6 @@ static void fio_cluster_client_handler(struct cluster_pr_s *pr) {
|
|
6244
6244
|
break;
|
6245
6245
|
case FIO_CLUSTER_MSG_SHUTDOWN:
|
6246
6246
|
fio_stop();
|
6247
|
-
kill(getpid(), SIGINT);
|
6248
6247
|
case FIO_CLUSTER_MSG_ERROR: /* fallthrough */
|
6249
6248
|
case FIO_CLUSTER_MSG_PING: /* fallthrough */
|
6250
6249
|
case FIO_CLUSTER_MSG_ROOT: /* fallthrough */
|
@@ -6499,7 +6498,7 @@ static void fio_pubsub_on_fork(void) {
|
|
6499
6498
|
/** Signals children (or self) to shutdown) - NOT signal safe. */
|
6500
6499
|
static void fio_cluster_signal_children(void) {
|
6501
6500
|
if (fio_parent_pid() != getpid()) {
|
6502
|
-
|
6501
|
+
fio_stop();
|
6503
6502
|
return;
|
6504
6503
|
}
|
6505
6504
|
fio_cluster_server_sender(fio_msg_internal_create(0, FIO_CLUSTER_MSG_SHUTDOWN,
|
data/ext/iodine/iodine_caller.c
CHANGED
@@ -27,7 +27,8 @@ typedef struct {
|
|
27
27
|
static void *iodine_handle_exception(void *ignr) {
|
28
28
|
(void)ignr;
|
29
29
|
VALUE exc = rb_errinfo();
|
30
|
-
if (exc != Qnil)
|
30
|
+
if (exc != Qnil && rb_respond_to(exc, rb_intern("message")) &&
|
31
|
+
rb_respond_to(exc, rb_intern("backtrace"))) {
|
31
32
|
VALUE msg = rb_funcall2(exc, rb_intern("message"), 0, NULL);
|
32
33
|
VALUE exc_class = rb_class_name(CLASS_OF(exc));
|
33
34
|
VALUE bt = rb_funcall2(exc, rb_intern("backtrace"), 0, NULL);
|
@@ -46,6 +47,9 @@ static void *iodine_handle_exception(void *ignr) {
|
|
46
47
|
rb_backtrace();
|
47
48
|
FIO_LOG_ERROR("\n");
|
48
49
|
rb_set_errinfo(Qnil);
|
50
|
+
} else if (exc != Qnil) {
|
51
|
+
FIO_LOG_ERROR(
|
52
|
+
"Iodine caught an unprotected exception - NO MESSAGE / DATA AVAILABLE");
|
49
53
|
}
|
50
54
|
return (void *)Qnil;
|
51
55
|
}
|
data/lib/iodine/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iodine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.33
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -202,7 +202,7 @@ licenses:
|
|
202
202
|
- MIT
|
203
203
|
metadata:
|
204
204
|
allowed_push_host: https://rubygems.org
|
205
|
-
post_install_message: 'Thank you for installing Iodine 0.7.
|
205
|
+
post_install_message: 'Thank you for installing Iodine 0.7.33.
|
206
206
|
|
207
207
|
'
|
208
208
|
rdoc_options: []
|