iodine 0.7.39 → 0.7.44

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 WebSocket / EventSource connection support for Rack servers.
8
+ This document details a Rack specification extension for WebSocket / EventSource servers.
9
9
 
10
- The purpose of these specifications is:
10
+ The purpose of this specification is:
11
11
 
12
- 1. To allow separation of concerns between the transport layer and the application, thereby allowing the application to be server agnostic.
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
- Simply put, when choosing between conforming servers, the application doesn’t need to have any knowledge about the chosen server.
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 support “native" (server-side) WebSocket and EventSource (SSE) connections and using application side callbacks.
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, to make it easy for applications to accept WebSocket and EventSource (SSE) connections from WebSocket and EventSource clients (commonly browsers) while abstracting away any transport layer details.
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
- If a connection is not "upgradable", a conforming server SHOULD set `env['rack.upgrade?']` to either `nil` or `false`.
38
+ * The server **SHOULD** ignore the Callback Object and process the response as if it did not exist.
31
39
 
32
- The historical use of `upgrade.websocket?` and `upgrade.websocket` (iodine 0.4.x) will be gradually deprecated.
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)` WILL be called once the connection had been established.
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)` WILL be called when incoming WebSocket data is received.
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` will 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).
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 and it is *not* required.
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 the `client.write` buffer becomes empty. **If** `client.pending` returns a non-zero value, the `on_drained` callback **MUST** be called once the write buffer becomes empty.
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
- * `write(data)` will schedule the data to be sent. `data` **MUST** be a String.
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
- A call to `write` only promises that the data is scheduled to be sent. Servers are encouraged to avoid blocking and return immediately, deferring the actual `write` operation for later.
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 whether `write` will block. It is **RECOMMENDED** that servers implement buffered IO, allowing `write` to return immediately when resources allow and block (or, possibly, disconnect) when the IO buffer is full.
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 in the outgoing queue was sent. If `close` is called while there is still data to be sent, `close` will only take effect once the data was sent.
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?` returns `true` if the connection isn't known to have been closed and `false` if the connection is known to be closed or marked to be closed.
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` if they never call the `on_drained` callback and the connection is open.
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
- Servers that return a positive number MUST call the `on_drained` callback when a call to `pending` would return the value `0`.
105
+ * `class` **MUST** return the client's Class, allowing it be extended with additional features (such as Pub/Sub, etc').
86
106
 
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.
107
+ **Note**: Ruby adds this method automatically to every class, no need to do a thing.
88
108
 
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.
109
+ The server **MAY** support the following (optional) methods for the `client` object:
90
110
 
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.
111
+ * `handler` if implemented, **MUST** return the callback object linked to the `client` object.
92
112
 
93
- Server settings **MAY** (not required) 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.
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
- When a client decides to upgrade a request, they will place an appropriate Callback Object in the `env['rack.upgrade']` Hash key.
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. The server will review the `env` Hash *before* sending the response. If the `env['rack.upgrade']` was set, the server will perform the upgrade.
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 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.
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
- The response status provided by the response object shall be ignored and the correct response status shall be set by the server.
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
- If the application's response status indicates an error or a redirection (status code >= 300), the server shall ignore the Callback Object.
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
- 3. Once the upgrade had completed, the server will call the `on_open` callback.
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` will **not** be executed concurrently for the same connection.
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 will **not** be called while any other callback is running (`on_open`, `on_message`, `on_drained`, etc').
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 might be called concurrently with the `on_message` callback, allowing data to be sent even while other data is being processed. Multi-threading considerations may apply.
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
- class WSConnection
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 "Yap,on_drained is implemented."
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.new
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 but it is supported by iodine:
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
- class Chat
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", "#{@nickname} joined the chat."
213
+ client.publish "chat", "#{env[:nickname]} joined the chat."
178
214
  end
179
215
  def on_message(client, data)
180
- client.publish "chat", "#{@nickname}: #{data}"
216
+ client.publish "chat", "#{env[:nickname]}: #{data}"
181
217
  end
182
218
  def on_close(client)
183
- client.publish "chat", "#{@nickname}: left the 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['rack.upgrade'.freeze] = Chat.new(nickname)
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
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
+ IODINE_PARSE_CLI = true
3
4
  require 'iodine'
4
5
 
5
6
  # Load Rack if available (assume it will be used).
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
- if (is_open)
340
- fio_max_fd_min(fd);
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
- fio_poll_add_write(fio_uuid2fd(uuid));
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
- fprintf(stderr, "UUID error: %p (%d)\n", (void *)uuid, errno);
3036
- perror("No errno handler");
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
- if (!rw_hooks->read)
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 (fd2uuid(fd) == uuid) {
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
- if (!rw_hooks->read)
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).protocol || (fd_data(fd).active + timeout >= review))
3673
+ if (!fd_data(fd).open || fd_data(fd).active + timeout >= review)
3685
3674
  goto finish;
3686
- tmp = protocol_try_lock(fd, FIO_PR_LOCK_STATE);
3687
- if (!tmp) {
3688
- if (errno == EBADF)
3689
- goto finish;
3690
- goto reschedule;
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).protocol && (fd <= fio_data->max_protocol_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(), fio_data->capa, (int)fio_data->parent);
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) {