iodine 0.7.38 → 0.7.43
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +5 -12
- data/.travis.yml +6 -5
- data/CHANGELOG.md +28 -0
- data/README.md +55 -48
- data/Rakefile +3 -1
- data/SPEC-PubSub-Draft.md +89 -47
- data/SPEC-WebSocket-Draft.md +92 -55
- data/examples/async_task.ru +92 -0
- data/exe/iodine +1 -0
- data/ext/iodine/extconf.rb +0 -34
- data/ext/iodine/fio.c +65 -65
- data/ext/iodine/fio_tls_missing.c +6 -0
- data/ext/iodine/fio_tls_openssl.c +64 -28
- data/ext/iodine/http.c +87 -100
- data/ext/iodine/http1.c +6 -15
- data/ext/iodine/http1_parser.h +806 -63
- data/ext/iodine/iodine.c +2 -0
- data/ext/iodine/iodine.h +1 -0
- data/ext/iodine/iodine_caller.c +1 -1
- data/ext/iodine/iodine_connection.c +20 -8
- data/ext/iodine/iodine_defer.c +1 -0
- data/ext/iodine/iodine_http.c +4 -6
- data/ext/iodine/iodine_mustache.c +2 -4
- data/ext/iodine/iodine_tls.c +2 -16
- data/lib/iodine.rb +1 -1
- data/lib/iodine/connection.rb +12 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +6 -0
- metadata +5 -5
- data/ext/iodine/http1_parser.c +0 -616
data/SPEC-WebSocket-Draft.md
CHANGED
@@ -5,62 +5,72 @@ This draft is also implemented by [the Agoo server](https://github.com/ohler55/a
|
|
5
5
|
---
|
6
6
|
## Purpose
|
7
7
|
|
8
|
-
This document details
|
8
|
+
This document details a Rack specification extension for WebSocket / EventSource servers.
|
9
9
|
|
10
|
-
The purpose of
|
10
|
+
The purpose of this specification is:
|
11
11
|
|
12
|
-
1. To
|
12
|
+
1. To improve application safety by phasing out the use of `hijack` and replacing it with the use of application object callbacks.
|
13
13
|
|
14
|
-
|
14
|
+
This should make it easer for applications to accept WebSocket and EventSource (SSE) connections without exposing themselves to risks and errors related to IO / network logic (such as slow client attacks, buffer flooding, etc').
|
15
15
|
|
16
|
-
2. To
|
16
|
+
2. To improve separation of concerns between servers and applications, moving the IO / network logic related to WebSocket and EventSource (SSE) back to the server.
|
17
17
|
|
18
|
-
Simply put,
|
19
|
-
|
20
|
-
3. Allow applications to use WebSocket and EventSource (SSE) on HTTP/2 servers. Note: current `hijack` practices will break network connections when attempting to implement EventSource (SSE).
|
18
|
+
Simply put, when using a server that support this extension, the application / framework doesn’t need to have any knowledge about networking, transport protocols, IO streams, polling, etc'.
|
21
19
|
|
22
20
|
## Rack WebSockets / EventSource
|
23
21
|
|
24
|
-
Servers that publish WebSocket and/or EventSource (SSE) support using the `env['rack.upgrade?']` value MUST follow the requirements set in this document.
|
22
|
+
Servers that publish WebSocket and/or EventSource (SSE) support using the `env['rack.upgrade?']` value **MUST** follow the requirements set in this document.
|
25
23
|
|
26
24
|
This document reserves the Rack `env` Hash keys of `rack.upgrade?` and `rack.upgrade`.
|
27
25
|
|
28
|
-
A conforming server MUST set `env['rack.upgrade?']` to `:websocket` for incoming WebSocket connections and `:sse` for incoming EventSource (SSE) connections.
|
26
|
+
A conforming server **MUST** set `env['rack.upgrade?']` to `:websocket` for incoming WebSocket connections and `:sse` for incoming EventSource (SSE) connections.
|
27
|
+
|
28
|
+
If a connection is not "upgradeable", a conforming server **SHOULD** set `env['rack.upgrade?']` to either `nil` or `false`. Setting the `env['rack.upgrade?']` to either `false` or `nil` should make it easier for applications to test for server support during a normal HTTP request.
|
29
|
+
|
30
|
+
If the connection is upgradeable and a client application set a value for `env['rack.upgrade']`:
|
31
|
+
|
32
|
+
* the server **MUST** use that value as a WebSocket / EventSource Callback Object unless the response status code `>= 300` (redirection / error status code).
|
33
|
+
|
34
|
+
* The response `body` **MUST NOT** be sent when switching to a Callback Object.
|
35
|
+
|
36
|
+
If a connection is **NOT** upgradeable and a client application set a value for `env['rack.upgrade']`:
|
29
37
|
|
30
|
-
|
38
|
+
* The server **SHOULD** ignore the Callback Object and process the response as if it did not exist.
|
31
39
|
|
32
|
-
|
40
|
+
* A server **MAY** use the Callback Object to allow a client to "hijack" the data stream as raw data stream. Such behavior **MUST** be documented.
|
33
41
|
|
34
42
|
### The WebSocket / EventSource Callback Object
|
35
43
|
|
36
44
|
WebSocket and EventSource connection upgrade and handling is performed using a Callback Object.
|
37
45
|
|
38
|
-
The Callback Object could be a any object which implements any of the following callbacks:
|
46
|
+
The Callback Object could be a any object which implements any (of none) of the following callbacks:
|
39
47
|
|
40
|
-
* `on_open(client)`
|
48
|
+
* `on_open(client)` **MUST** be called once the connection had been established and/or the Callback Object had been linked to the `client` object.
|
41
49
|
|
42
|
-
* `on_message(client, data)`
|
50
|
+
* `on_message(client, data)` **MUST** be called when incoming WebSocket data is received.
|
43
51
|
|
44
52
|
This callback is ignored for EventSource connections.
|
45
53
|
|
46
|
-
`data`
|
54
|
+
`data` **MUST** be a String with an encoding of UTF-8 for text messages and `binary` encoding for non-text messages (as specified by the WebSocket Protocol).
|
47
55
|
|
48
56
|
The *callback object* **MUST** assume that the `data` String will be a **recyclable buffer** and that it's content will be corrupted the moment the `on_message` callback returns.
|
49
57
|
|
50
|
-
Servers **MAY**, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional
|
58
|
+
Servers **MAY**, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional, is *not* required and might result in issues in cases where the client code is less than pristine.
|
51
59
|
|
52
|
-
* `on_drained(client)` **MAY** be called when the
|
60
|
+
* `on_drained(client)` **MAY** be called when the `client.write` buffer becomes empty. **If** `client.pending` ever returns a non-zero value (see later on), the `on_drained` callback **MUST** be called once the write buffer becomes empty.
|
53
61
|
|
54
62
|
* `on_shutdown(client)` **MAY** be called during the server's graceful shutdown process, _before_ the connection is closed and in addition to the `on_close` function (which is called _after_ the connection is closed.
|
55
63
|
|
56
|
-
* `on_close(client)` **MUST** be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `client.close` being called, etc').
|
64
|
+
* `on_close(client)` **MUST** be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `client.close` being called, etc') or the Callback Object was replaced by another Callback Object.
|
57
65
|
|
58
66
|
|
59
67
|
The server **MUST** provide the Callback Object with a `client` object, that supports the following methods (this approach promises applications could be server agnostic):
|
60
68
|
|
61
|
-
* `
|
69
|
+
* `env` **MUST** return 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.
|
62
70
|
|
63
|
-
|
71
|
+
* `write(data)` **MUST** schedule **all** the data to be sent. `data` **MUST** be a String. Servers **MAY** silently convert non-String objects to JSON if an application attempts to `write` a non-String value, otherwise servers **SHOULD** throw an exception.
|
72
|
+
|
73
|
+
A call to `write` only promises that the data is scheduled to be sent. Implementation details may differ across servers.
|
64
74
|
|
65
75
|
`write` shall return `true` on success and `false` if the connection is closed.
|
66
76
|
|
@@ -70,27 +80,52 @@ The server **MUST** provide the Callback Object with a `client` object, that sup
|
|
70
80
|
|
71
81
|
* If `data` is binary encoded it will be sent as non-text (as specified by the WebSocket Protocol).
|
72
82
|
|
73
|
-
A server **SHOULD** document
|
83
|
+
A server **SHOULD** document its concurrency model, allowing developers to know whether `write` will block or not, whether buffered IO is implemented, etc'.
|
84
|
+
|
85
|
+
For example, evented servers are encouraged to avoid blocking and return immediately, deferring the actual `write` operation for later. However, (process/thread/fiber) per-connection based servers **MAY** choose to return only after all the data was sent. Documenting these differences will allows applications to choose the model that best fits their needs and environments.
|
74
86
|
|
75
|
-
* `close` closes the connection once all the data
|
87
|
+
* `close` closes the connection once all the data scheduled using `write` was sent. If `close` is called while there is still data to be sent, `close` **SHOULD** return immediately and only take effect once the data was sent.
|
76
88
|
|
77
89
|
`close` shall always return `nil`.
|
78
90
|
|
79
|
-
* `open?`
|
91
|
+
* `open?` **MUST** return `false` **if** the connection was never open, is known to be closed or marked to be closed. Otherwise `true` **MUST** be returned.
|
80
92
|
|
81
93
|
* `pending` **MUST** return -1 if the connection is closed. Otherwise, `pending` **SHOULD** return the number of pending writes (messages in the `write` queue\*) that need to be processed before the next time the `on_drained` callback is called.
|
82
94
|
|
83
|
-
Servers **MAY** choose to always return the value `0`
|
95
|
+
Servers **MAY** choose to always return the value `0` **ONLY IF** they never call the `on_drained` callback and the connection is open.
|
96
|
+
|
97
|
+
Servers that return a positive number **MUST** call the `on_drained` callback when a call to `pending` would return the value `0`.
|
98
|
+
|
99
|
+
\*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.
|
100
|
+
|
101
|
+
* `pubsub?` **MUST** return `false` **unless** the pub/sub extension is supported.
|
102
|
+
|
103
|
+
Pub/Sub patterns are idiomatic for WebSockets and EventSource connections but their API is out of scope for this extension.
|
84
104
|
|
85
|
-
|
105
|
+
* `class` **MUST** return the client's Class, allowing it be extended with additional features (such as Pub/Sub, etc').
|
86
106
|
|
87
|
-
|
107
|
+
**Note**: Ruby adds this method automatically to every class, no need to do a thing.
|
88
108
|
|
89
|
-
|
109
|
+
The server **MAY** support the following (optional) methods for the `client` object:
|
90
110
|
|
91
|
-
|
111
|
+
* `handler` if implemented, **MUST** return the callback object linked to the `client` object.
|
92
112
|
|
93
|
-
|
113
|
+
* `handler=` if implemented, **MUST** set a new Callback Object for `client`.
|
114
|
+
|
115
|
+
This allows applications to switch from one callback object to another (i.e., in case of credential upgrades).
|
116
|
+
|
117
|
+
Once a new Callback Object was set, the server **MUST** call the old handler's `on_close` callback and **afterwards** call the new handler's `on_open` callback.
|
118
|
+
|
119
|
+
It is **RECOMMENDED** (but not required) that this also updates the value for `env['rack.upgrade']`.
|
120
|
+
|
121
|
+
* `timeout` / `timeout=` allows applications to get / set connection timeouts dynamically and separately for each connection. Servers **SHOULD** provide a global setting for the default connection timeout. It is **RECOMMENDED** (but not required) that a global / default timeout setting be available from the command line (CLI).
|
122
|
+
|
123
|
+
* `protocol` if implemented, **MUST** return the same value that was originally set by `env['rack.upgrade?']`.
|
124
|
+
|
125
|
+
|
126
|
+
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 to detect network failure. Clients **SHOULD** also consider sending `ping`s to detect network errors (dropped connections).
|
127
|
+
|
128
|
+
Server settings **MAY** be provided to allow for customization and adaptation for different network environments or WebSocket extensions. It is **RECOMMENDED** that any settings be available as command line arguments and **not** incorporated into the application's logic.
|
94
129
|
|
95
130
|
---
|
96
131
|
|
@@ -100,62 +135,65 @@ Server settings **MAY** (not required) be provided to allow for customization an
|
|
100
135
|
|
101
136
|
* **Server**:
|
102
137
|
|
138
|
+
When a regular HTTP request arrives (non-upgradeable), the server will set the `env['rack.upgrade?']` flag to `false`, indicating that: 1. this specific request is NOT upgradable; and 2. the server supports this specification for either WebSocket and/or EventSource connections.
|
139
|
+
|
103
140
|
When a WebSocket upgrade request arrives, the server will set the `env['rack.upgrade?']` flag to `:websocket`, indicating that: 1. this specific request is upgradable; and 2. the server supports this specification for WebSocket connections.
|
104
141
|
|
105
|
-
When an EventSource request arrives, the server will set the `env['rack.upgrade?']` flag to `:sse`, indicating that: 1. this specific request is an EventSource request; and 2. the server supports this specification.
|
142
|
+
When an EventSource request arrives, the server will set the `env['rack.upgrade?']` flag to `:sse`, indicating that: 1. this specific request is an EventSource request; and 2. the server supports this specification for EventSource connections.
|
106
143
|
|
107
144
|
* **Client**:
|
108
145
|
|
109
|
-
|
146
|
+
If a client decides to upgrade a request, they will place an appropriate Callback Object in the `env['rack.upgrade']` Hash key.
|
110
147
|
|
111
148
|
* **Server**:
|
112
149
|
|
113
|
-
1.
|
150
|
+
1. If the application's response status indicates an error or a redirection (status code `>= 300`), the server shall ignore the Callback Object and/or remove it from the `env` Hash, ignoring the rest of the steps that follow.
|
114
151
|
|
115
|
-
2. The server will
|
152
|
+
2. The server will review the `env` Hash *before* sending the response. If the `env['rack.upgrade']` was set, the server will perform the upgrade.
|
116
153
|
|
117
|
-
|
154
|
+
3. The server will send the correct response status and headers, as well as any headers present in the response object. The server will also perform any required housekeeping, such as closing the response body, if it exists.
|
118
155
|
|
119
|
-
|
156
|
+
The response status provided by the response object shall be ignored and the correct response status shall be set by the server.
|
120
157
|
|
121
|
-
|
158
|
+
4. Once the upgrade had completed, the server will call the `on_open` callback.
|
122
159
|
|
123
160
|
No other callbacks shall be called until the `on_open` callback had returned.
|
124
161
|
|
125
|
-
WebSocket messages shall be handled by the `on_message` callback in the same order in which they arrive and the `on_message`
|
162
|
+
WebSocket messages shall be handled by the `on_message` callback in the same order in which they arrive and the `on_message` **SHOULD NOT** be executed concurrently for the same connection.
|
126
163
|
|
127
|
-
The `on_close` callback
|
164
|
+
The `on_close` callback **MUST NOT** be called while any other callback is running (`on_open`, `on_message`, `on_drained`, etc').
|
128
165
|
|
129
|
-
The `on_drained` callback
|
166
|
+
The `on_drained` callback **MAY** be called concurrently with the `on_message` callback, allowing data to be sent even while incoming data is being processed. Multi-threading considerations apply.
|
130
167
|
|
131
168
|
## Example Usage
|
132
169
|
|
133
170
|
The following is an example WebSocket echo server implemented using this specification:
|
134
171
|
|
135
172
|
```ruby
|
136
|
-
|
173
|
+
module WSConnection
|
137
174
|
def on_open(client)
|
138
|
-
puts "WebSocket connection established."
|
175
|
+
puts "WebSocket connection established (#{client.object_id})."
|
139
176
|
end
|
140
177
|
def on_message(client, data)
|
141
|
-
client.write data
|
178
|
+
client.write data # echo the data back
|
142
179
|
puts "on_drained MUST be implemented if #{ pending } != 0."
|
143
180
|
end
|
144
181
|
def on_drained(client)
|
145
|
-
puts "
|
182
|
+
puts "If this line prints out, on_drained is supported by the server."
|
146
183
|
end
|
147
184
|
def on_shutdown(client)
|
148
185
|
client.write "The server is going away. Goodbye."
|
149
186
|
end
|
150
187
|
def on_close(client)
|
151
|
-
puts "WebSocket connection closed."
|
188
|
+
puts "WebSocket connection closed (#{client.object_id})."
|
152
189
|
end
|
190
|
+
extend self
|
153
191
|
end
|
154
192
|
|
155
193
|
module App
|
156
194
|
def self.call(env)
|
157
195
|
if(env['rack.upgrade?'.freeze] == :websocket)
|
158
|
-
env['rack.upgrade'.freeze] = WSConnection
|
196
|
+
env['rack.upgrade'.freeze] = WSConnection
|
159
197
|
return [0, {}, []]
|
160
198
|
end
|
161
199
|
return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]]
|
@@ -165,23 +203,22 @@ end
|
|
165
203
|
run App
|
166
204
|
```
|
167
205
|
|
168
|
-
The following example uses Push notifications for both WebSocket and SSE connections. The Pub/Sub API isn't part of this specification
|
206
|
+
The following example uses Push notifications for both WebSocket and SSE connections. The Pub/Sub API is subject to a separate Pub/Sub API extension and isn't part of this specification (it is, however, supported by iodine):
|
169
207
|
|
170
208
|
```ruby
|
171
|
-
|
172
|
-
def initialize(nickname)
|
173
|
-
@nickname = nickname
|
174
|
-
end
|
209
|
+
module Chat
|
175
210
|
def on_open(client)
|
211
|
+
client.class.prepend MyPubSubModule unless client.pubsub?
|
176
212
|
client.subscribe "chat"
|
177
|
-
client.publish "chat", "#{
|
213
|
+
client.publish "chat", "#{env[:nickname]} joined the chat."
|
178
214
|
end
|
179
215
|
def on_message(client, data)
|
180
|
-
client.publish "chat", "#{
|
216
|
+
client.publish "chat", "#{env[:nickname]}: #{data}"
|
181
217
|
end
|
182
218
|
def on_close(client)
|
183
|
-
client.publish "chat", "#{
|
219
|
+
client.publish "chat", "#{env[:nickname]}: left the chat."
|
184
220
|
end
|
221
|
+
extend self
|
185
222
|
end
|
186
223
|
|
187
224
|
module App
|
@@ -189,7 +226,7 @@ module App
|
|
189
226
|
if(env['rack.upgrade?'.freeze])
|
190
227
|
nickname = env['PATH_INFO'][1..-1]
|
191
228
|
nickname = "Someone" if nickname == "".freeze
|
192
|
-
env[
|
229
|
+
env[:nickname] = nickname
|
193
230
|
return [0, {}, []]
|
194
231
|
end
|
195
232
|
return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]]
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# This is a task scheduling WebSocket push example application.
|
2
|
+
#
|
3
|
+
# Benchmark HTTPP with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients):
|
4
|
+
#
|
5
|
+
# ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/
|
6
|
+
# wrk -c2000 -d5 -t12 http://localhost:3000/
|
7
|
+
#
|
8
|
+
# Test websocket tasks using the browser. For example:
|
9
|
+
# ws = new WebSocket("ws://localhost:3000/userID"); ws.onmessage = function(e) {console.log(e.data);}; ws.onclose = function(e) {console.log("closed")};
|
10
|
+
# ws.onopen = function(e) {ws.send(JSON.stringify({'task': 'echo', 'data': 'Hello!'}));};
|
11
|
+
require 'iodine'
|
12
|
+
require 'json'
|
13
|
+
|
14
|
+
TASK_PUBLISHING_ENGINE = Iodine::PubSub::PROCESS
|
15
|
+
|
16
|
+
# This module handles tasks and send them back to the frontend
|
17
|
+
module TaskHandler
|
18
|
+
def echo msg
|
19
|
+
msg = Iodine::JSON.parse(msg, symbolize_names: true)
|
20
|
+
publish_to = msg.delete(:from)
|
21
|
+
Iodine.publish(publish_to, msg.to_json, TASK_PUBLISHING_ENGINE) if publish_to
|
22
|
+
puts "performed 'echo' task"
|
23
|
+
rescue => e
|
24
|
+
puts "JSON task message error? #{e.message} - under attack?"
|
25
|
+
end
|
26
|
+
|
27
|
+
def add msg
|
28
|
+
msg = Iodine::JSON.parse(msg, symbolize_names: true)
|
29
|
+
raise "addition task requires an array of numbers" unless msg[:data].is_a?(Array)
|
30
|
+
msg[:data] = msg[:data].inject(0){|sum,x| sum + x }
|
31
|
+
publish_to = msg.delete(:from)
|
32
|
+
Iodine.publish(publish_to, msg.to_json, TASK_PUBLISHING_ENGINE) if publish_to
|
33
|
+
puts "performed 'add' task"
|
34
|
+
rescue => e
|
35
|
+
puts
|
36
|
+
"JSON task message error? #{e.message} - under attack?"
|
37
|
+
end
|
38
|
+
|
39
|
+
def listen2tasks
|
40
|
+
Iodine.subscribe(:echo) {|ch,msg| TaskHandler.echo(msg) }
|
41
|
+
Iodine.subscribe(:add) {|ch,msg| TaskHandler.add(msg) }
|
42
|
+
end
|
43
|
+
|
44
|
+
extend self
|
45
|
+
end
|
46
|
+
|
47
|
+
module WebsocketClient
|
48
|
+
def on_open client
|
49
|
+
# Pub/Sub directly to the client (or use a block to process the messages)
|
50
|
+
client.subscribe client.env['PATH_INFO'.freeze]
|
51
|
+
end
|
52
|
+
def on_message client, data
|
53
|
+
# Strings and symbol channel names are equivalent.
|
54
|
+
msg = Iodine::JSON.parse(data, symbolize_names: true)
|
55
|
+
raise "no valid task" unless ["echo".freeze, "add".freeze].include? msg[:task]
|
56
|
+
msg[:from] = client.env['PATH_INFO'.freeze]
|
57
|
+
client.publish msg[:task], msg.to_json, TASK_PUBLISHING_ENGINE
|
58
|
+
rescue => e
|
59
|
+
puts "JSON message error? #{e.message}\n\t#{data}\n\t#{msg}"
|
60
|
+
end
|
61
|
+
extend self
|
62
|
+
end
|
63
|
+
|
64
|
+
APP = Proc.new do |env|
|
65
|
+
if env['rack.upgrade?'.freeze] == :websocket
|
66
|
+
env['rack.upgrade'.freeze] = WebsocketClient
|
67
|
+
[0,{}, []] # It's possible to set cookies for the response.
|
68
|
+
elsif env['rack.upgrade?'.freeze] == :sse
|
69
|
+
puts "SSE connections can only receive data from the server, the can't write."
|
70
|
+
env['rack.upgrade'.freeze] = WebsocketClient
|
71
|
+
[0,{}, []] # It's possible to set cookies for the response.
|
72
|
+
else
|
73
|
+
[200, {"Content-Type" => "text/plain"}, ["Send messages with WebSockets using JSON.\ni.e.: {\"task\":\"add\", \"data\":[1,2]}"]]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# test automatically for Redis extensions.
|
78
|
+
if(Iodine::PubSub.default.is_a? Iodine::PubSub::Redis)
|
79
|
+
TASK_PUBLISHING_ENGINE = Iodine::PubSub.default
|
80
|
+
if(ARGV.include? "worker")
|
81
|
+
TaskHandler.listen2tasks
|
82
|
+
Iodine.workers = 1
|
83
|
+
Iodine.threads = 16 if Iodine.threads == 0
|
84
|
+
Iodine.start
|
85
|
+
exit(0)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
TaskHandler.listen2tasks
|
89
|
+
end
|
90
|
+
|
91
|
+
# # or in config.ru
|
92
|
+
run APP
|
data/exe/iodine
CHANGED
data/ext/iodine/extconf.rb
CHANGED
@@ -1,34 +1,5 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
|
3
|
-
if ENV['CC']
|
4
|
-
ENV['CPP'] ||= ENV['CC']
|
5
|
-
puts "detected user prefered compiler (#{ENV['CC']}):", `#{ENV['CC']} -v`
|
6
|
-
elsif find_executable('clang') && puts('testing clang for stdatomic support...').nil? && system("printf \"\#include <stdatomic.h>\nint main(void) {}\" | clang -include stdatomic.h -xc -o /dev/null -", out: '/dev/null')
|
7
|
-
$CC = ENV['CC'] = 'clang'
|
8
|
-
$CPP = ENV['CPP'] = 'clang'
|
9
|
-
puts "using clang compiler v. #{`clang -dumpversion`}."
|
10
|
-
elsif find_executable('gcc') && (`gcc -dumpversion 2>&1`.to_i >= 5)
|
11
|
-
$CC = ENV['CC'] = 'gcc'
|
12
|
-
$CPP = ENV['CPP'] = find_executable('g++') ? 'g++' : 'gcc'
|
13
|
-
puts "using gcc #{ `gcc -dumpversion 2>&1`.to_i }"
|
14
|
-
elsif find_executable('gcc-6')
|
15
|
-
$CC = ENV['CC'] = 'gcc-6'
|
16
|
-
$CPP = ENV['CPP'] = find_executable('g++-6') ? 'g++-6' : 'gcc-6'
|
17
|
-
puts 'using gcc-6 compiler.'
|
18
|
-
elsif find_executable('gcc-5')
|
19
|
-
$CC = ENV['CC'] = 'gcc-5'
|
20
|
-
$CPP = ENV['CPP'] = find_executable('g++-5') ? 'g++-5' : 'gcc-5'
|
21
|
-
puts 'using gcc-5 compiler.'
|
22
|
-
elsif find_executable('gcc-4.9')
|
23
|
-
$CC = ENV['CC'] = 'gcc-4.9'
|
24
|
-
$CPP = ENV['CPP'] = find_executable('g++-4.9') ? 'g++-4.9' : 'gcc-4.9'
|
25
|
-
puts 'using gcc-4.9 compiler.'
|
26
|
-
else
|
27
|
-
puts 'using an unknown (old?) compiler... who knows if this will work out... we hope.'
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
3
|
# Test polling
|
33
4
|
def iodine_test_polling_support
|
34
5
|
iodine_poll_test_kqueue = <<EOS
|
@@ -129,10 +100,5 @@ EOS
|
|
129
100
|
end
|
130
101
|
end
|
131
102
|
end
|
132
|
-
# $defs << "-DFIO_USE_RISKY_HASH"
|
133
|
-
|
134
|
-
RbConfig::MAKEFILE_CONFIG['CFLAGS'] = $CFLAGS = "-std=c11 -DFIO_PRINT_STATE=0 #{$CFLAGS} #{$CFLAGS == ENV['CFLAGS'] ? "" : ENV['CFLAGS']}"
|
135
|
-
RbConfig::MAKEFILE_CONFIG['CC'] = $CC = ENV['CC'] if ENV['CC']
|
136
|
-
RbConfig::MAKEFILE_CONFIG['CPP'] = $CPP = ENV['CPP'] if ENV['CPP']
|
137
103
|
|
138
104
|
create_makefile 'iodine/iodine'
|
data/ext/iodine/fio.c
CHANGED
@@ -36,6 +36,12 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
36
36
|
|
37
37
|
#include <arpa/inet.h>
|
38
38
|
|
39
|
+
#if HAVE_OPENSSL
|
40
|
+
#include <openssl/bio.h>
|
41
|
+
#include <openssl/err.h>
|
42
|
+
#include <openssl/ssl.h>
|
43
|
+
#endif
|
44
|
+
|
39
45
|
/* force poll for testing? */
|
40
46
|
#ifndef FIO_ENGINE_POLL
|
41
47
|
#define FIO_ENGINE_POLL 0
|
@@ -277,26 +283,6 @@ static inline fio_packet_s *fio_packet_alloc(void) {
|
|
277
283
|
Core Connection Data Clearing
|
278
284
|
***************************************************************************** */
|
279
285
|
|
280
|
-
/* set the minimal max_protocol_fd */
|
281
|
-
static void fio_max_fd_min(uint32_t fd) {
|
282
|
-
if (fio_data->max_protocol_fd > fd)
|
283
|
-
return;
|
284
|
-
fio_lock(&fio_data->lock);
|
285
|
-
if (fio_data->max_protocol_fd < fd)
|
286
|
-
fio_data->max_protocol_fd = fd;
|
287
|
-
fio_unlock(&fio_data->lock);
|
288
|
-
}
|
289
|
-
|
290
|
-
/* set the minimal max_protocol_fd */
|
291
|
-
static void fio_max_fd_shrink(void) {
|
292
|
-
fio_lock(&fio_data->lock);
|
293
|
-
uint32_t fd = fio_data->max_protocol_fd;
|
294
|
-
while (fd && fd_data(fd).protocol == NULL)
|
295
|
-
--fd;
|
296
|
-
fio_data->max_protocol_fd = fd;
|
297
|
-
fio_unlock(&fio_data->lock);
|
298
|
-
}
|
299
|
-
|
300
286
|
/* resets connection data, marking it as either open or closed. */
|
301
287
|
static inline int fio_clear_fd(intptr_t fd, uint8_t is_open) {
|
302
288
|
fio_packet_s *packet;
|
@@ -318,6 +304,13 @@ static inline int fio_clear_fd(intptr_t fd, uint8_t is_open) {
|
|
318
304
|
.counter = fd_data(fd).counter + 1,
|
319
305
|
.packet_last = &fd_data(fd).packet,
|
320
306
|
};
|
307
|
+
if (fio_data->max_protocol_fd < fd) {
|
308
|
+
fio_data->max_protocol_fd = fd;
|
309
|
+
} else {
|
310
|
+
while (fio_data->max_protocol_fd &&
|
311
|
+
!fd_data(fio_data->max_protocol_fd).open)
|
312
|
+
--fio_data->max_protocol_fd;
|
313
|
+
}
|
321
314
|
fio_unlock(&(fd_data(fd).sock_lock));
|
322
315
|
if (rw_hooks && rw_hooks->cleanup)
|
323
316
|
rw_hooks->cleanup(rw_udata);
|
@@ -336,8 +329,8 @@ static inline int fio_clear_fd(intptr_t fd, uint8_t is_open) {
|
|
336
329
|
if (protocol && protocol->on_close) {
|
337
330
|
fio_defer(deferred_on_close, (void *)fd2uuid(fd), protocol);
|
338
331
|
}
|
339
|
-
|
340
|
-
|
332
|
+
FIO_LOG_DEBUG("FD %d re-initialized (state: %p-%s).", (int)fd,
|
333
|
+
(void *)fd2uuid(fd), (is_open ? "open" : "closed"));
|
341
334
|
return 0;
|
342
335
|
}
|
343
336
|
|
@@ -2887,7 +2880,7 @@ void fio_close(intptr_t uuid) {
|
|
2887
2880
|
}
|
2888
2881
|
if (uuid_data(uuid).packet || uuid_data(uuid).sock_lock) {
|
2889
2882
|
uuid_data(uuid).close = 1;
|
2890
|
-
|
2883
|
+
fio_force_event(uuid, FIO_EVENT_ON_READY);
|
2891
2884
|
return;
|
2892
2885
|
}
|
2893
2886
|
fio_force_close(uuid);
|
@@ -3019,6 +3012,7 @@ test_errno:
|
|
3019
3012
|
case ENOSPC: /* fallthrough */
|
3020
3013
|
case EADDRNOTAVAIL: /* fallthrough */
|
3021
3014
|
case EINTR:
|
3015
|
+
case 0:
|
3022
3016
|
return 1;
|
3023
3017
|
case EFAULT:
|
3024
3018
|
FIO_LOG_ERROR("fio_flush EFAULT - possible memory address error sent to "
|
@@ -3032,8 +3026,8 @@ test_errno:
|
|
3032
3026
|
fio_force_close(uuid);
|
3033
3027
|
return -1;
|
3034
3028
|
}
|
3035
|
-
|
3036
|
-
|
3029
|
+
FIO_LOG_DEBUG("UUID error: %p (%d): %s\n", (void *)uuid, errno,
|
3030
|
+
strerror(errno));
|
3037
3031
|
return 0;
|
3038
3032
|
|
3039
3033
|
invalid:
|
@@ -3099,6 +3093,19 @@ const fio_rw_hook_s FIO_DEFAULT_RW_HOOKS = {
|
|
3099
3093
|
.cleanup = fio_hooks_default_cleanup,
|
3100
3094
|
};
|
3101
3095
|
|
3096
|
+
static inline void fio_rw_hook_validate(fio_rw_hook_s *rw_hooks) {
|
3097
|
+
if (!rw_hooks->read)
|
3098
|
+
rw_hooks->read = fio_hooks_default_read;
|
3099
|
+
if (!rw_hooks->write)
|
3100
|
+
rw_hooks->write = fio_hooks_default_write;
|
3101
|
+
if (!rw_hooks->flush)
|
3102
|
+
rw_hooks->flush = fio_hooks_default_flush;
|
3103
|
+
if (!rw_hooks->before_close)
|
3104
|
+
rw_hooks->before_close = fio_hooks_default_before_close;
|
3105
|
+
if (!rw_hooks->cleanup)
|
3106
|
+
rw_hooks->cleanup = fio_hooks_default_cleanup;
|
3107
|
+
}
|
3108
|
+
|
3102
3109
|
/**
|
3103
3110
|
* Replaces an existing read/write hook with another from within a read/write
|
3104
3111
|
* hook callback.
|
@@ -3112,19 +3119,10 @@ int fio_rw_hook_replace_unsafe(intptr_t uuid, fio_rw_hook_s *rw_hooks,
|
|
3112
3119
|
int replaced = -1;
|
3113
3120
|
uint8_t was_locked;
|
3114
3121
|
intptr_t fd = fio_uuid2fd(uuid);
|
3115
|
-
|
3116
|
-
rw_hooks->read = fio_hooks_default_read;
|
3117
|
-
if (!rw_hooks->write)
|
3118
|
-
rw_hooks->write = fio_hooks_default_write;
|
3119
|
-
if (!rw_hooks->flush)
|
3120
|
-
rw_hooks->flush = fio_hooks_default_flush;
|
3121
|
-
if (!rw_hooks->before_close)
|
3122
|
-
rw_hooks->before_close = fio_hooks_default_before_close;
|
3123
|
-
if (!rw_hooks->cleanup)
|
3124
|
-
rw_hooks->cleanup = fio_hooks_default_cleanup;
|
3122
|
+
fio_rw_hook_validate(rw_hooks);
|
3125
3123
|
/* protect against some fulishness... but not all of it. */
|
3126
3124
|
was_locked = fio_trylock(&fd_data(fd).sock_lock);
|
3127
|
-
if (
|
3125
|
+
if (uuid_is_valid(uuid)) {
|
3128
3126
|
fd_data(fd).rw_hooks = rw_hooks;
|
3129
3127
|
fd_data(fd).rw_udata = udata;
|
3130
3128
|
replaced = 0;
|
@@ -3138,16 +3136,7 @@ int fio_rw_hook_replace_unsafe(intptr_t uuid, fio_rw_hook_s *rw_hooks,
|
|
3138
3136
|
int fio_rw_hook_set(intptr_t uuid, fio_rw_hook_s *rw_hooks, void *udata) {
|
3139
3137
|
if (fio_is_closed(uuid))
|
3140
3138
|
goto invalid_uuid;
|
3141
|
-
|
3142
|
-
rw_hooks->read = fio_hooks_default_read;
|
3143
|
-
if (!rw_hooks->write)
|
3144
|
-
rw_hooks->write = fio_hooks_default_write;
|
3145
|
-
if (!rw_hooks->flush)
|
3146
|
-
rw_hooks->flush = fio_hooks_default_flush;
|
3147
|
-
if (!rw_hooks->before_close)
|
3148
|
-
rw_hooks->before_close = fio_hooks_default_before_close;
|
3149
|
-
if (!rw_hooks->cleanup)
|
3150
|
-
rw_hooks->cleanup = fio_hooks_default_cleanup;
|
3139
|
+
fio_rw_hook_validate(rw_hooks);
|
3151
3140
|
intptr_t fd = fio_uuid2fd(uuid);
|
3152
3141
|
fio_rw_hook_s *old_rw_hooks;
|
3153
3142
|
void *old_udata;
|
@@ -3249,7 +3238,6 @@ static int fio_attach__internal(void *uuid_, void *protocol_) {
|
|
3249
3238
|
/* adding a new uuid to the reactor */
|
3250
3239
|
fio_poll_add(fio_uuid2fd(uuid));
|
3251
3240
|
}
|
3252
|
-
fio_max_fd_min(fio_uuid2fd(uuid));
|
3253
3241
|
return 0;
|
3254
3242
|
|
3255
3243
|
invalid_uuid:
|
@@ -3510,18 +3498,19 @@ static void fio_on_fork(void) {
|
|
3510
3498
|
fio_poll_init();
|
3511
3499
|
fio_state_callback_on_fork();
|
3512
3500
|
|
3501
|
+
/* don't pass open connections belonging to the parent onto the child. */
|
3513
3502
|
const size_t limit = fio_data->capa;
|
3514
3503
|
for (size_t i = 0; i < limit; ++i) {
|
3515
3504
|
fd_data(i).sock_lock = FIO_LOCK_INIT;
|
3516
3505
|
fd_data(i).protocol_lock = FIO_LOCK_INIT;
|
3517
|
-
if (fd_data(i).protocol) {
|
3506
|
+
if (fd_data(i).protocol && fd_data(i).open) {
|
3507
|
+
/* open without protocol might be waiting for the child (listening) */
|
3518
3508
|
fd_data(i).protocol->rsv = 0;
|
3519
3509
|
fio_force_close(fd2uuid(i));
|
3520
3510
|
}
|
3521
3511
|
}
|
3522
3512
|
|
3523
3513
|
fio_pubsub_on_fork();
|
3524
|
-
fio_max_fd_shrink();
|
3525
3514
|
uint16_t old_active = fio_data->active;
|
3526
3515
|
fio_data->active = 0;
|
3527
3516
|
fio_defer_perform();
|
@@ -3681,24 +3670,30 @@ static void fio_review_timeout(void *arg, void *ignr) {
|
|
3681
3670
|
uint16_t timeout = fd_data(fd).timeout;
|
3682
3671
|
if (!timeout)
|
3683
3672
|
timeout = 300; /* enforced timout settings */
|
3684
|
-
if (!fd_data(fd).
|
3673
|
+
if (!fd_data(fd).open || fd_data(fd).active + timeout >= review)
|
3685
3674
|
goto finish;
|
3686
|
-
|
3687
|
-
|
3688
|
-
if (
|
3689
|
-
|
3690
|
-
|
3675
|
+
if (fd_data(fd).protocol) {
|
3676
|
+
tmp = protocol_try_lock(fd, FIO_PR_LOCK_STATE);
|
3677
|
+
if (!tmp) {
|
3678
|
+
if (errno == EBADF)
|
3679
|
+
goto finish;
|
3680
|
+
goto reschedule;
|
3681
|
+
}
|
3682
|
+
if (prt_meta(tmp).locks[FIO_PR_LOCK_TASK] ||
|
3683
|
+
prt_meta(tmp).locks[FIO_PR_LOCK_WRITE])
|
3684
|
+
goto unlock;
|
3685
|
+
fio_defer_push_task(deferred_ping, (void *)fio_fd2uuid((int)fd), NULL);
|
3686
|
+
unlock:
|
3687
|
+
protocol_unlock(tmp, FIO_PR_LOCK_STATE);
|
3688
|
+
} else {
|
3689
|
+
/* open FD but no protocol? RW hook thing or listening sockets? */
|
3690
|
+
if (fd_data(fd).rw_hooks != &FIO_DEFAULT_RW_HOOKS)
|
3691
|
+
fio_close(fd2uuid(fd));
|
3691
3692
|
}
|
3692
|
-
if (prt_meta(tmp).locks[FIO_PR_LOCK_TASK] ||
|
3693
|
-
prt_meta(tmp).locks[FIO_PR_LOCK_WRITE])
|
3694
|
-
goto unlock;
|
3695
|
-
fio_defer_push_task(deferred_ping, (void *)fio_fd2uuid((int)fd), NULL);
|
3696
|
-
unlock:
|
3697
|
-
protocol_unlock(tmp, FIO_PR_LOCK_STATE);
|
3698
3693
|
finish:
|
3699
3694
|
do {
|
3700
3695
|
fd++;
|
3701
|
-
} while (!fd_data(fd).
|
3696
|
+
} while (!fd_data(fd).open && (fd <= fio_data->max_protocol_fd));
|
3702
3697
|
|
3703
3698
|
if (fio_data->max_protocol_fd < fd) {
|
3704
3699
|
fio_data->need_review = 1;
|
@@ -3714,7 +3709,6 @@ static void fio_cycle_schedule_events(void) {
|
|
3714
3709
|
static time_t last_to_review = 0;
|
3715
3710
|
fio_mark_time();
|
3716
3711
|
fio_timer_schedule();
|
3717
|
-
fio_max_fd_shrink();
|
3718
3712
|
if (fio_signal_children_flag) {
|
3719
3713
|
/* hot restart support */
|
3720
3714
|
fio_signal_children_flag = 0;
|
@@ -3923,16 +3917,22 @@ void fio_start FIO_IGNORE_MACRO(struct fio_start_args args) {
|
|
3923
3917
|
fio_data->is_worker = 0;
|
3924
3918
|
|
3925
3919
|
fio_state_callback_force(FIO_CALL_PRE_START);
|
3926
|
-
|
3927
3920
|
FIO_LOG_INFO(
|
3928
3921
|
"Server is running %u %s X %u %s with facil.io " FIO_VERSION_STRING
|
3929
3922
|
" (%s)\n"
|
3923
|
+
#if HAVE_OPENSSL
|
3924
|
+
"* Linked to %s\n"
|
3925
|
+
#endif
|
3930
3926
|
"* Detected capacity: %d open file limit\n"
|
3931
3927
|
"* Root pid: %d\n"
|
3932
3928
|
"* Press ^C to stop\n",
|
3933
3929
|
fio_data->workers, fio_data->workers > 1 ? "workers" : "worker",
|
3934
3930
|
fio_data->threads, fio_data->threads > 1 ? "threads" : "thread",
|
3935
|
-
fio_engine(),
|
3931
|
+
fio_engine(),
|
3932
|
+
#if HAVE_OPENSSL
|
3933
|
+
OpenSSL_version(0),
|
3934
|
+
#endif
|
3935
|
+
fio_data->capa, (int)fio_data->parent);
|
3936
3936
|
|
3937
3937
|
if (args.workers > 1) {
|
3938
3938
|
for (int i = 0; i < args.workers && fio_data->active; ++i) {
|