iodine 0.2.2 → 0.2.3
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/README.md +40 -2
- data/SPEC-Websocket-Draft.md +83 -0
- data/ext/iodine/http_response.c +2 -2
- data/ext/iodine/iodine_core.c +2 -1
- data/ext/iodine/iodine_websocket.c +10 -2
- data/ext/iodine/libserver.c +17 -1
- data/ext/iodine/libserver.h +60 -41
- data/ext/iodine/libsock.c +54 -7
- data/ext/iodine/libsock.h +13 -8
- data/iodine.gemspec +5 -4
- data/lib/iodine/version.rb +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8757fa617add5c9d6d4c8b7feee4c3b97952a7e3
|
4
|
+
data.tar.gz: b66b35b5662458733a47d5358901278e428811b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65bc4f93c07a4e81eacdc5ce203bf16516c079a246ca4aee6bec078e3180b0d9aebeb653c711a5519d45f57ab817a188ad1c2509bb87b2587c50ab7763155e4b
|
7
|
+
data.tar.gz: ec57fa3e6674bb4393017f188671bf88ce851b6948b1c3b53917a5b78bef1b6822967e1b525e73250288e4d020a29aaa977f1167201ba22b3b12e300020070c7
|
data/CHANGELOG.md
CHANGED
@@ -8,6 +8,14 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
8
8
|
|
9
9
|
***
|
10
10
|
|
11
|
+
Change log v.0.2.3
|
12
|
+
|
13
|
+
**Update** The `write` system call is now deferred when resources allow, meaning that (as long as the `write` buffer isn't full) `write` is not only non-blocking, but it's performed as a separate event, outside of the Ruby GIL.
|
14
|
+
|
15
|
+
**Update**: The global socket `write` buffer was increased to ~16Mb (from ~4Mb), allowing for more concurrent `write` operations. However, the `write` buffer is still limited and `write` might block while the buffer is full. Blocking and "hanging" the server until there's enough room in the buffer for the requested `write` will slow the server down while keeping it healthy and more secure. IMHO, it is the lesser of two evils.
|
16
|
+
|
17
|
+
***
|
18
|
+
|
11
19
|
Change log v.0.2.2
|
12
20
|
|
13
21
|
**Update** The static file service now supports `ETag` caching, sending a 304 (not changed) response for valid ETags.
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ Iodine is a C extension for Ruby, developed for Ruby MRI 2.2.2 and up... it shou
|
|
28
28
|
|
29
29
|
## Iodine::Rack - an HTTP and Websockets server
|
30
30
|
|
31
|
-
Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC).
|
31
|
+
Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC) and the Websocket draft extension.
|
32
32
|
|
33
33
|
### Running the web server
|
34
34
|
|
@@ -47,7 +47,19 @@ Puma's model of 16 threads and 4 processes is easily adopted and proved to provi
|
|
47
47
|
bundler exec iodine -p $PORT -t 16 -w 4
|
48
48
|
```
|
49
49
|
|
50
|
-
It should be noted that Websocket support means that no automatic process scaling is provided... It is important to use `iodine` with the `-w` option and set the number of desired processes (ideally equal to the number of CPU cores).
|
50
|
+
It should be noted that Websocket support means that no automatic process scaling is provided (since Websockets don't share data across processes without your help)... It is important to use `iodine` with the `-w` option and set the number of desired processes (ideally equal to the number of CPU cores).
|
51
|
+
|
52
|
+
### Writing data to the network layer
|
53
|
+
|
54
|
+
Iodine allows Ruby to write strings to the network layer. This includes HTTP and Websocket responses.
|
55
|
+
|
56
|
+
Iodine will handle an internal buffer (~4 to ~16 Mb, version depending) so that `write` can return immediately (non-blocking).
|
57
|
+
|
58
|
+
However, when the buffer is full, `write` will block until enough space in he buffer becomes available. Sending up to 16Kb of data (a single buffer "packet") is optimal. Sending a larger response might effect concurrency. Best Websocket response length is ~1Kb (1 TCP / IP packet) and allows for faster transmissions.
|
59
|
+
|
60
|
+
When using the Iodine's web server (`Iodine::Rack`), the static file service offered by Iodine streams files (instead of using the buffer). Every file response will require up to 2 buffer "packets" (~32Kb), one for the header and the other for file streaming.
|
61
|
+
|
62
|
+
This means that even a Gigabyte long response will use ~32Kb of memory, as long as it uses the static file service or the `X-Sendfile` extension (Iodine's static file service can be invoked by the Ruby application using the `X-Sendfile` header).
|
51
63
|
|
52
64
|
### Static file serving support
|
53
65
|
|
@@ -73,6 +85,32 @@ app = Proc.new { out }
|
|
73
85
|
run app
|
74
86
|
```
|
75
87
|
|
88
|
+
#### X-Sendfile
|
89
|
+
|
90
|
+
Sending can be performed by using the `X-Sendfile` header in the Ruby application response.
|
91
|
+
|
92
|
+
i.e. (example `config.ru` for Iodine):
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
app = proc do |env|
|
96
|
+
request = Rack::Request.new(env)
|
97
|
+
if request.path_info == '/source'.freeze
|
98
|
+
[200, { 'X-Sendfile' => File.expand_path(__FILE__) }, []]
|
99
|
+
elsif request.path_info == '/file'.freeze
|
100
|
+
[200, { 'X-Header' => 'This was a Rack::Sendfile response sent as text' }, File.open(__FILE__)]
|
101
|
+
else
|
102
|
+
[200, { 'Content-Type'.freeze => 'text/html'.freeze,
|
103
|
+
'Content-Length'.freeze => request.path_info.length.to_s },
|
104
|
+
[request.path_info]]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
# # optional:
|
108
|
+
# use Rack::Sendfile
|
109
|
+
run app
|
110
|
+
```
|
111
|
+
|
112
|
+
Go to [localhost:3000/source](http://localhost:3000/source) to download the `config.ru` file using the `X-Sendfile` extension.
|
113
|
+
|
76
114
|
### Special HTTP `Upgrade` support
|
77
115
|
|
78
116
|
Iodine's HTTP server includes special support for the Upgrade directive using Rack's `env` Hash, allowing the application to focus on services and data while Iodine takes care of the network layer.
|
@@ -0,0 +1,83 @@
|
|
1
|
+
## Rack Websockets
|
2
|
+
|
3
|
+
This is the proposed Websocket support extension for Rack servers.
|
4
|
+
|
5
|
+
Servers that publish Websocket support using the `env['upgrade.websocket?']` value are assume by users to follow the requirements set in this document and thus should follow the requirements set in herein.
|
6
|
+
|
7
|
+
This document reserves the Rack `env` Hash keys of `upgrade.websocket?` and `upgrade.websocket`.
|
8
|
+
|
9
|
+
## The Websocket Callback Object
|
10
|
+
|
11
|
+
Websocket connection upgrade and handling is performed using a Websocket Callback Object.
|
12
|
+
|
13
|
+
The Websocket Callback Object should be a class (or an instance of such class) who's instances implement any of the following callbacks:
|
14
|
+
|
15
|
+
* `on_open()` WILL be called once the upgrade had completed.
|
16
|
+
|
17
|
+
* `on_message(data)` WILL be called when incoming Websocket data is received. `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).
|
18
|
+
|
19
|
+
The *client* **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.
|
20
|
+
|
21
|
+
* `on_ready()` MAY be called when the state of the out-going socket buffer changes from full to not full (data can be sent to the socket). **If** `has_pending?` returns `true`, the `on_ready` callback **must** be called once the buffer state changes.
|
22
|
+
|
23
|
+
* `on_shutdown()` 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.
|
24
|
+
|
25
|
+
* `on_close()` WILL be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `close` being called, etc').
|
26
|
+
|
27
|
+
* `on_open`, `on_ready`, `on_shutdown` and `on_close` shouldn't expect any arguments (`arity == 0`).
|
28
|
+
|
29
|
+
The following method names are reserved for the network implementation: `write`, `close` and `has_pending?`.
|
30
|
+
|
31
|
+
The server **must** extend the Websocket Callback Object's *class* using `extend`, so that the Websocket Callback Object inherits the following methods:
|
32
|
+
|
33
|
+
* `write(data)` will attempt to send the data through the websocket connection. `data` **must** be a String. If `data` is UTF-8 encoded, the data will be sent as text. If `data` is binary encoded it will be sent as non-text (as specified by the Websocket Protocol).
|
34
|
+
|
35
|
+
`write` has the same delivery promise as `Socket#write` (a successful `write` does **not** mean any of the data will reach the other side).
|
36
|
+
|
37
|
+
`write` shall return `true` on success and `false` if the websocket is closed.
|
38
|
+
|
39
|
+
A server **should** document whether `write` will block or return immediately. 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.
|
40
|
+
|
41
|
+
* `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.
|
42
|
+
|
43
|
+
`close` shall always return `nil`.
|
44
|
+
|
45
|
+
* `has_pending?` queries the state of the server's buffer for the specific connection (i.e., if the server has any data it is waiting to send through the socket).
|
46
|
+
|
47
|
+
`has_pending?`, shall return `true` **if** the server has data waiting to be written to the socket **and** the server promises to call the `on_ready` callback once the buffer is empty and the socket is writable. Otherwise (i.e., if the server doesn't support the `on_ready` callback), `has_pending?` shall return `false`.
|
48
|
+
|
49
|
+
To clarify: **implementing `has_pending?` is semi-optional**, meaning that a server may choose to always return `false`, no matter the actual state of the socket's buffer.
|
50
|
+
|
51
|
+
The following keywords (both as method names and instance variable names) are reserved for the internal server implementation: `_server_ws` and `conn_id`.
|
52
|
+
|
53
|
+
* The `_server_ws` object is private and shouldn't be accessed by the client.
|
54
|
+
|
55
|
+
* The `conn_id` object may be used as a connection ID for any functionality not specified herein.
|
56
|
+
|
57
|
+
Connection `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.
|
58
|
+
|
59
|
+
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.
|
60
|
+
|
61
|
+
## Upgrading
|
62
|
+
|
63
|
+
* **Server**: When an upgrade request is received, the server will set the `env['upgrade.websockets?']` flag to `true`, indicating that: 1. this specific request is upgradable; and 2. this server supports specification.
|
64
|
+
|
65
|
+
* **Client**: When a client decides to upgrade a request, they will place a Websocket Callback Object (either a class or an instance) in the `env['upgrade.websockets']` Hash key.
|
66
|
+
|
67
|
+
* **Server**: The server will review the `env` Hash *before* sending the response. If the `env['upgrade.websockets']` was set, the server will perform the upgrade.
|
68
|
+
|
69
|
+
* **Server**: The server will send the correct response status and headers, as will as any headers present in the response. The server will also perform any required housekeeping, such as closing the response body, if exists.
|
70
|
+
|
71
|
+
The response status provided by the response object shall be ignored and the correct response status shall be set by the server.
|
72
|
+
|
73
|
+
* **Server**: Once the upgrade had completed, The server will add the required websocket/network functions to the callback handler or it's class (as aforementioned). If the callback handler is a Class object, the server will create a new instance of that class.
|
74
|
+
|
75
|
+
* **Server**: The server will call the `on_open` callback.
|
76
|
+
|
77
|
+
No other callbacks shall be called until the `on_open` callback had returned.
|
78
|
+
|
79
|
+
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.
|
80
|
+
|
81
|
+
The `on_close` callback will **not** be called while `on_message` or `on_open` callbacks are running.
|
82
|
+
|
83
|
+
The `on_ready` 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.
|
data/ext/iodine/http_response.c
CHANGED
@@ -295,8 +295,8 @@ int http_response_sendfile2(http_response_s *response, http_request_s *request,
|
|
295
295
|
|
296
296
|
response->last_modified = file_data.st_mtime;
|
297
297
|
http_response_write_header(response, .name = "Cache-Control",
|
298
|
-
.name_length = 13, .value = "
|
299
|
-
.value_length =
|
298
|
+
.name_length = 13, .value = "max-age=3600",
|
299
|
+
.value_length = 12);
|
300
300
|
|
301
301
|
/* check etag */
|
302
302
|
if ((ext = http_request_find_header(request, "if-none-match", 13)) &&
|
data/ext/iodine/iodine_core.c
CHANGED
@@ -558,7 +558,8 @@ static void *srv_start_no_gvl(void *_) {
|
|
558
558
|
// print a warnning if settings are sub optimal
|
559
559
|
#ifdef _SC_NPROCESSORS_ONLN
|
560
560
|
size_t cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
|
561
|
-
if (
|
561
|
+
if (cpu_count > 0 &&
|
562
|
+
((processes << 1) < cpu_count || processes > (cpu_count << 1)))
|
562
563
|
fprintf(
|
563
564
|
stderr, "* Performance warnning:\n"
|
564
565
|
" - This computer has %lu CPUs available and you'll be "
|
@@ -116,14 +116,22 @@ static VALUE iodine_ws_close(VALUE self) {
|
|
116
116
|
return self;
|
117
117
|
}
|
118
118
|
|
119
|
-
/**
|
119
|
+
/**
|
120
|
+
* Writes data to the websocket.
|
121
|
+
*
|
122
|
+
* Returns `true` on success or `false if the websocket was closed or an error
|
123
|
+
* occurred.
|
124
|
+
*
|
125
|
+
* `write` will return immediately UNLESS resources are insufficient. If the
|
126
|
+
* global `write` buffer is full, `write` will block until a buffer "packet"
|
127
|
+
* becomes available and can be assigned to the socket. */
|
120
128
|
static VALUE iodine_ws_write(VALUE self, VALUE data) {
|
121
129
|
ws_s *ws = get_ws(self);
|
122
130
|
if (((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
|
123
131
|
return Qfalse;
|
124
132
|
websocket_write(ws, RSTRING_PTR(data), RSTRING_LEN(data),
|
125
133
|
rb_enc_get(data) == UTF8Encoding);
|
126
|
-
return
|
134
|
+
return Qtrue;
|
127
135
|
}
|
128
136
|
|
129
137
|
/** Returns the number of active websocket connections (including connections
|
data/ext/iodine/libserver.c
CHANGED
@@ -80,10 +80,11 @@ These macros help prevent code changes when changing the data struct.
|
|
80
80
|
// run through any open sockets and call the shutdown handler
|
81
81
|
static inline void server_on_shutdown(void) {
|
82
82
|
if (server_data.fds && server_data.capacity > 0) {
|
83
|
+
intptr_t uuid;
|
83
84
|
for (size_t i = 0; i < server_data.capacity; i++) {
|
84
85
|
if (server_data.fds[i].protocol == NULL)
|
85
86
|
continue;
|
86
|
-
|
87
|
+
uuid = sock_fd2uuid(i);
|
87
88
|
if (uuid != -1) {
|
88
89
|
if (server_data.fds[i].protocol->on_shutdown != NULL)
|
89
90
|
server_data.fds[i].protocol->on_shutdown(uuid,
|
@@ -547,12 +548,26 @@ ssize_t server_run(struct ServerSettings settings) {
|
|
547
548
|
async_join();
|
548
549
|
else
|
549
550
|
async_perform();
|
551
|
+
|
552
|
+
/*
|
553
|
+
* start a single worker thread for shutdown tasks and async close operations
|
554
|
+
*/
|
555
|
+
async_start(1);
|
556
|
+
|
550
557
|
listener_on_server_shutdown();
|
551
558
|
reactor_review();
|
552
559
|
server_on_shutdown();
|
560
|
+
/* cycle until no IO events occure. */
|
561
|
+
while (reactor_review() > 0)
|
562
|
+
;
|
553
563
|
if (settings.on_finish)
|
554
564
|
settings.on_finish();
|
555
565
|
|
566
|
+
/*
|
567
|
+
* Wait for any unfinished tasks.
|
568
|
+
*/
|
569
|
+
async_finish();
|
570
|
+
|
556
571
|
if (children) {
|
557
572
|
if (rootpid == getpid()) {
|
558
573
|
while (waitpid(-1, NULL, 0) >= 0)
|
@@ -625,6 +640,7 @@ void server_set_timeout(intptr_t fd, uint8_t timeout) {
|
|
625
640
|
if (valid_uuid(fd) == 0)
|
626
641
|
return;
|
627
642
|
lock_uuid(fd);
|
643
|
+
uuid_data(fd).active = server_data.last_tick;
|
628
644
|
uuid_data(fd).timeout = timeout;
|
629
645
|
unlock_uuid(fd);
|
630
646
|
}
|
data/ext/iodine/libserver.h
CHANGED
@@ -5,7 +5,10 @@ license: MIT
|
|
5
5
|
Feel free to copy, use and enjoy according to the license provided.
|
6
6
|
*/
|
7
7
|
#ifndef LIB_SERVER
|
8
|
-
#define LIB_SERVER "0.4.
|
8
|
+
#define LIB_SERVER "0.4.1"
|
9
|
+
#define LIB_SERVER_VERSION_MAJOR 0
|
10
|
+
#define LIB_SERVER_VERSION_MINOR 4
|
11
|
+
#define LIB_SERVER_VERSION_PATCH 1
|
9
12
|
|
10
13
|
#ifndef _GNU_SOURCE
|
11
14
|
#define _GNU_SOURCE
|
@@ -30,7 +33,7 @@ messages regarding the server state (start / finish / listen messages).
|
|
30
33
|
#define SERVER_PRINT_STATE 1
|
31
34
|
#endif
|
32
35
|
|
33
|
-
#if LIB_ASYNC_VERSION_MINOR != 4 || LIB_REACT_VERSION_MINOR != 3 ||
|
36
|
+
#if LIB_ASYNC_VERSION_MINOR != 4 || LIB_REACT_VERSION_MINOR != 3 || \
|
34
37
|
LIB_SOCK_VERSION_MINOR != 2
|
35
38
|
#warning Lib-Server dependency versions are not in sync. Please review API versions.
|
36
39
|
#endif
|
@@ -194,18 +197,18 @@ struct Protocol {
|
|
194
197
|
* The string should be a global constant, only a pointer comparison will be
|
195
198
|
* made (not `strcmp`).
|
196
199
|
*/
|
197
|
-
const char*
|
200
|
+
const char *service;
|
198
201
|
/** called when a data is available, but will not run concurrently */
|
199
|
-
void (*on_data)(intptr_t fduuid, protocol_s*
|
202
|
+
void (*on_data)(intptr_t fduuid, protocol_s *protocol);
|
200
203
|
/** called when the socket is ready to be written to. */
|
201
|
-
void (*on_ready)(intptr_t fduuid, protocol_s*
|
204
|
+
void (*on_ready)(intptr_t fduuid, protocol_s *protocol);
|
202
205
|
/** called when the server is shutting down,
|
203
206
|
* but before closing the connection. */
|
204
|
-
void (*on_shutdown)(intptr_t fduuid, protocol_s*
|
207
|
+
void (*on_shutdown)(intptr_t fduuid, protocol_s *protocol);
|
205
208
|
/** called when the connection was closed, but will not run concurrently */
|
206
|
-
void (*on_close)(protocol_s*
|
209
|
+
void (*on_close)(protocol_s *protocol);
|
207
210
|
/** called when a connection's timeout was reached */
|
208
|
-
void (*ping)(intptr_t fduuid, protocol_s*
|
211
|
+
void (*ping)(intptr_t fduuid, protocol_s *protocol);
|
209
212
|
/** private metadata used for object protection */
|
210
213
|
spn_lock_i callback_lock;
|
211
214
|
};
|
@@ -218,21 +221,21 @@ These settings will be used to setup listenning sockets.
|
|
218
221
|
struct ServerServiceSettings {
|
219
222
|
/** Called whenever a new connection is accepted. Should return a pointer to
|
220
223
|
* the connection's protocol. */
|
221
|
-
protocol_s*
|
224
|
+
protocol_s *(*on_open)(intptr_t fduuid, void *udata);
|
222
225
|
/** The network service / port. Defaults to "3000". */
|
223
|
-
const char*
|
226
|
+
const char *port;
|
224
227
|
/** The socket binding address. Defaults to the recommended NULL. */
|
225
|
-
const char*
|
228
|
+
const char *address;
|
226
229
|
/** Opaque user data. */
|
227
|
-
void*
|
230
|
+
void *udata;
|
228
231
|
/**
|
229
232
|
* Called when the server starts, allowing for further initialization, such as
|
230
233
|
* timed event scheduling.
|
231
234
|
*
|
232
235
|
* This will be called seperately for every process. */
|
233
|
-
void (*on_start)(void*
|
236
|
+
void (*on_start)(void *udata);
|
234
237
|
/** called when the server is done, to clean up any leftovers. */
|
235
|
-
void (*on_finish)(void*
|
238
|
+
void (*on_finish)(void *udata);
|
236
239
|
};
|
237
240
|
|
238
241
|
/**************************************************************************/ /**
|
@@ -273,7 +276,31 @@ struct ServerSettings {
|
|
273
276
|
*/
|
274
277
|
|
275
278
|
/**
|
276
|
-
Listens to a server with any of the
|
279
|
+
Listens to a server with any of the available service settings:
|
280
|
+
|
281
|
+
* `.on_open` called whenever a new connection is accepted.
|
282
|
+
|
283
|
+
Should return a pointer to the connection's protocol.
|
284
|
+
|
285
|
+
* `.port` the network service / port. Defaults to "3000".
|
286
|
+
|
287
|
+
* `.address` the socket binding address. Defaults to the recommended NULL.
|
288
|
+
|
289
|
+
* `.udata`opaque user data.
|
290
|
+
|
291
|
+
* `.on_start` called when the server starts, allowing for further
|
292
|
+
initialization, such as timed event scheduling.
|
293
|
+
|
294
|
+
This will be called seperately for every process.
|
295
|
+
|
296
|
+
* `.on_finish` called when the server is done, to clean up any leftovers.
|
297
|
+
|
298
|
+
*/
|
299
|
+
int server_listen(struct ServerServiceSettings);
|
300
|
+
#define server_listen(...) \
|
301
|
+
server_listen((struct ServerServiceSettings){__VA_ARGS__})
|
302
|
+
/**
|
303
|
+
Runs a server with any of the following server settings:
|
277
304
|
|
278
305
|
* `.threads` the number of threads to initiate in the server's thread pool.
|
279
306
|
|
@@ -292,12 +319,11 @@ initiate a `fork`).
|
|
292
319
|
This method blocks the current thread until the server is stopped when a
|
293
320
|
SIGINT/SIGTERM is received.
|
294
321
|
|
295
|
-
To
|
322
|
+
To shutdown the server use the `kill` function with a SIGINT.
|
323
|
+
|
324
|
+
This function only returns after the server had completed it's shutdown process.
|
325
|
+
|
296
326
|
*/
|
297
|
-
int server_listen(struct ServerServiceSettings);
|
298
|
-
#define server_listen(...) \
|
299
|
-
server_listen((struct ServerServiceSettings){__VA_ARGS__})
|
300
|
-
/** runs the server, hanging the current process and thread. */
|
301
327
|
ssize_t server_run(struct ServerSettings);
|
302
328
|
#define server_run(...) server_run((struct ServerSettings){__VA_ARGS__})
|
303
329
|
/** Stops the server, shouldn't be called unless int's impossible to send an
|
@@ -317,7 +343,7 @@ Gets the active protocol object for the requested file descriptor.
|
|
317
343
|
Returns NULL on error (i.e. connection closed), otherwise returns a `protocol_s`
|
318
344
|
pointer.
|
319
345
|
*/
|
320
|
-
protocol_s*
|
346
|
+
protocol_s *server_get_protocol(intptr_t uuid);
|
321
347
|
/**
|
322
348
|
Sets a new active protocol object for the requested file descriptor.
|
323
349
|
|
@@ -326,7 +352,7 @@ all resources are released.
|
|
326
352
|
|
327
353
|
Returns -1 on error (i.e. connection closed), otherwise returns 0.
|
328
354
|
*/
|
329
|
-
ssize_t server_switch_protocol(intptr_t fd, protocol_s*
|
355
|
+
ssize_t server_switch_protocol(intptr_t fd, protocol_s *new_protocol);
|
330
356
|
/**
|
331
357
|
Sets a connection's timeout.
|
332
358
|
|
@@ -340,7 +366,7 @@ based resources asynchronously (i.e. database resources etc').
|
|
340
366
|
|
341
367
|
On failure the fduuid_u.data.fd value will be -1.
|
342
368
|
*/
|
343
|
-
intptr_t server_attach(int fd, protocol_s*
|
369
|
+
intptr_t server_attach(int fd, protocol_s *protocol);
|
344
370
|
/** Hijack a socket (file descriptor) from the server, clearing up it's
|
345
371
|
resources and calling the protocol's `on_close` callback (making sure allocated
|
346
372
|
resources are freed).
|
@@ -355,7 +381,7 @@ The returned value is the fd for the socket, or -1 on error.
|
|
355
381
|
int server_hijack(intptr_t uuid);
|
356
382
|
/** Counts the number of connections for the specified protocol (NULL = all
|
357
383
|
protocols). */
|
358
|
-
long server_count(char*
|
384
|
+
long server_count(char *service);
|
359
385
|
|
360
386
|
/****************************************************************************
|
361
387
|
* Read and Write
|
@@ -390,13 +416,10 @@ callback.
|
|
390
416
|
It is recommended the `on_finish` callback is only used to perform any
|
391
417
|
resource cleanup necessary.
|
392
418
|
*/
|
393
|
-
void server_each(intptr_t origin_uuid,
|
394
|
-
|
395
|
-
void (*
|
396
|
-
|
397
|
-
void (*on_finish)(intptr_t origin_uuid,
|
398
|
-
protocol_s* protocol,
|
399
|
-
void* arg));
|
419
|
+
void server_each(intptr_t origin_uuid, const char *service,
|
420
|
+
void (*task)(intptr_t uuid, protocol_s *protocol, void *arg),
|
421
|
+
void *arg, void (*on_finish)(intptr_t origin_uuid,
|
422
|
+
protocol_s *protocol, void *arg));
|
400
423
|
/** Schedules a specific task to run asyncronously for a specific connection.
|
401
424
|
|
402
425
|
returns -1 on failure, 0 on success (success being scheduling the task).
|
@@ -409,26 +432,22 @@ and call the fallback function from within the main task, but other designes
|
|
409
432
|
are valid as well.
|
410
433
|
*/
|
411
434
|
void server_task(intptr_t uuid,
|
412
|
-
void (*task)(intptr_t uuid, protocol_s*
|
413
|
-
void* arg
|
414
|
-
void (*fallback)(intptr_t uuid, void* arg));
|
435
|
+
void (*task)(intptr_t uuid, protocol_s *protocol, void *arg),
|
436
|
+
void *arg, void (*fallback)(intptr_t uuid, void *arg));
|
415
437
|
/** Creates a system timer (at the cost of 1 file descriptor) and pushes the
|
416
438
|
timer to the reactor. The task will repeat `repetitions` times. if
|
417
439
|
`repetitions` is set to 0, task will repeat forever. Returns -1 on error
|
418
440
|
or the new file descriptor on succeess.
|
419
441
|
*/
|
420
|
-
int server_run_every(size_t milliseconds,
|
421
|
-
|
422
|
-
void (*
|
423
|
-
void* arg,
|
424
|
-
void (*on_finish)(void*));
|
442
|
+
int server_run_every(size_t milliseconds, size_t repetitions,
|
443
|
+
void (*task)(void *), void *arg,
|
444
|
+
void (*on_finish)(void *));
|
425
445
|
|
426
446
|
/** Creates a system timer (at the cost of 1 file descriptor) and pushes the
|
427
447
|
timer to the reactor. The task will NOT repeat. Returns -1 on error or the
|
428
448
|
new file descriptor on succeess. */
|
429
449
|
__unused static inline int server_run_after(size_t milliseconds,
|
430
|
-
void task(void*),
|
431
|
-
void* arg) {
|
450
|
+
void task(void *), void *arg) {
|
432
451
|
return server_run_every(milliseconds, 1, task, arg, NULL);
|
433
452
|
}
|
434
453
|
|
data/ext/iodine/libsock.c
CHANGED
@@ -44,6 +44,12 @@ Support timeout setting.
|
|
44
44
|
#pragma weak sock_touch
|
45
45
|
void sock_touch(intptr_t uuid) {}
|
46
46
|
|
47
|
+
/* *****************************************************************************
|
48
|
+
Support event based `write` scheduling.
|
49
|
+
*/
|
50
|
+
#pragma weak async_run
|
51
|
+
int async_run(void (*task)(void *), void *arg) { return -1; }
|
52
|
+
|
47
53
|
/* *****************************************************************************
|
48
54
|
OS Sendfile settings.
|
49
55
|
*/
|
@@ -72,6 +78,19 @@ Buffer and socket map memory allocation. Defaults to mmap.
|
|
72
78
|
#define USE_MALLOC 0
|
73
79
|
#endif
|
74
80
|
|
81
|
+
/* *****************************************************************************
|
82
|
+
The system call to `write` (non-blocking) can be defered when using `libasync`.
|
83
|
+
|
84
|
+
However, this will not prevent `sock_write` from cycling through the sockets and
|
85
|
+
flushing them (block emulation) when both the system and the user level buffers
|
86
|
+
are full.
|
87
|
+
|
88
|
+
Defaults to 1 (defered).
|
89
|
+
*/
|
90
|
+
#ifndef SOCK_DELAY_WRITE
|
91
|
+
#define SOCK_DELAY_WRITE 1
|
92
|
+
#endif
|
93
|
+
|
75
94
|
/* *****************************************************************************
|
76
95
|
Library related helper functions
|
77
96
|
*/
|
@@ -188,7 +207,8 @@ static inline void set_fd(int fd, unsigned int state) {
|
|
188
207
|
};
|
189
208
|
// unlock
|
190
209
|
spn_unlock(&fd_info[fd].lock);
|
191
|
-
// should be called within the lock? - no function calling within a
|
210
|
+
// should be called within the lock? - no function calling within a
|
211
|
+
// spinlock.
|
192
212
|
if (old_data.rw_hooks && old_data.rw_hooks->on_clear)
|
193
213
|
old_data.rw_hooks->on_clear(old_data.fduuid.uuid, old_data.rw_hooks);
|
194
214
|
// clear old data
|
@@ -213,6 +233,9 @@ Call this function before calling any `libsock` functions.
|
|
213
233
|
static void destroy_lib_data(void) {
|
214
234
|
if (fd_info) {
|
215
235
|
while (fd_capacity--) { // include 0 in countdown
|
236
|
+
if (fd_info[fd_capacity].open) {
|
237
|
+
fprintf(stderr, "Socket %lu is marked as open\n", fd_capacity);
|
238
|
+
}
|
216
239
|
set_fd(fd_capacity, LIB_SOCK_STATE_CLOSED);
|
217
240
|
}
|
218
241
|
#if USE_MALLOC == 1
|
@@ -463,12 +486,30 @@ static void sock_flush_unsafe(int fd) {
|
|
463
486
|
}
|
464
487
|
}
|
465
488
|
|
489
|
+
#if SOCK_DELAY_WRITE == 1
|
490
|
+
|
491
|
+
static inline void sock_flush_schd(intptr_t uuid) {
|
492
|
+
if (async_run((void *)sock_flush, (void *)uuid) == -1)
|
493
|
+
goto fallback;
|
494
|
+
return;
|
495
|
+
fallback:
|
496
|
+
sock_flush_unsafe(sock_uuid2fd(uuid));
|
497
|
+
}
|
498
|
+
|
499
|
+
#define _write_to_sock() sock_flush_schd(sfd->fduuid.uuid)
|
500
|
+
|
501
|
+
#else
|
502
|
+
|
503
|
+
#define _write_to_sock() sock_flush_unsafe(fd)
|
504
|
+
|
505
|
+
#endif
|
506
|
+
|
466
507
|
static inline void sock_send_packet_unsafe(int fd, sock_packet_s *packet) {
|
467
508
|
fd_info_s *sfd = fd_info + fd;
|
468
509
|
if (sfd->packet == NULL) {
|
469
510
|
/* no queue, nothing to check */
|
470
511
|
sfd->packet = packet;
|
471
|
-
|
512
|
+
_write_to_sock();
|
472
513
|
return;
|
473
514
|
|
474
515
|
} else if (packet->metadata.urgent == 0) {
|
@@ -477,7 +518,7 @@ static inline void sock_send_packet_unsafe(int fd, sock_packet_s *packet) {
|
|
477
518
|
while (pos->metadata.next)
|
478
519
|
pos = pos->metadata.next;
|
479
520
|
pos->metadata.next = packet;
|
480
|
-
|
521
|
+
_write_to_sock();
|
481
522
|
return;
|
482
523
|
|
483
524
|
} else {
|
@@ -494,7 +535,7 @@ static inline void sock_send_packet_unsafe(int fd, sock_packet_s *packet) {
|
|
494
535
|
*pos = tail;
|
495
536
|
}
|
496
537
|
}
|
497
|
-
|
538
|
+
_write_to_sock();
|
498
539
|
}
|
499
540
|
|
500
541
|
/* *****************************************************************************
|
@@ -676,7 +717,8 @@ static inline sock_packet_s *sock_try_checkout_packet(void) {
|
|
676
717
|
|
677
718
|
/**
|
678
719
|
Checks out a `sock_packet_s` from the packet pool, transfering the
|
679
|
-
ownership of the memory to the calling function. The function will hang until
|
720
|
+
ownership of the memory to the calling function. The function will hang until
|
721
|
+
a
|
680
722
|
packet becomes available, so never check out more then a single packet at a
|
681
723
|
time.
|
682
724
|
*/
|
@@ -713,7 +755,8 @@ ssize_t sock_send_packet(intptr_t uuid, sock_packet_s *packet) {
|
|
713
755
|
}
|
714
756
|
|
715
757
|
/**
|
716
|
-
Returns TRUE (non 0) if there is data waiting to be written to the socket in
|
758
|
+
Returns TRUE (non 0) if there is data waiting to be written to the socket in
|
759
|
+
the
|
717
760
|
user-land buffer.
|
718
761
|
*/
|
719
762
|
_Bool sock_packets_pending(intptr_t uuid) {
|
@@ -765,7 +808,8 @@ ssize_t sock_read(intptr_t uuid, void *buf, size_t count) {
|
|
765
808
|
}
|
766
809
|
if (i_read == -1 && (ERR_OK || ERR_TRY_AGAIN))
|
767
810
|
return 0;
|
768
|
-
// fprintf(stderr, "Read Error for %lu bytes from fd %d (closing))\n",
|
811
|
+
// fprintf(stderr, "Read Error for %lu bytes from fd %d (closing))\n",
|
812
|
+
// count,
|
769
813
|
// sock_uuid2fd(uuid));
|
770
814
|
sock_close(uuid);
|
771
815
|
return -1;
|
@@ -778,9 +822,12 @@ Flushing
|
|
778
822
|
ssize_t sock_flush(intptr_t uuid) {
|
779
823
|
if (!fd_info || !is_valid(uuid))
|
780
824
|
return -1;
|
825
|
+
if (uuid2info(uuid).packet == NULL)
|
826
|
+
goto no_packet;
|
781
827
|
spn_lock(&uuid2info(uuid).lock);
|
782
828
|
sock_flush_unsafe(sock_uuid2fd(uuid));
|
783
829
|
spn_unlock(&uuid2info(uuid).lock);
|
830
|
+
no_packet:
|
784
831
|
if (uuid2info(uuid).close) {
|
785
832
|
sock_force_close(uuid);
|
786
833
|
return -1;
|
data/ext/iodine/libsock.h
CHANGED
@@ -5,10 +5,10 @@ license: MIT
|
|
5
5
|
Feel free to copy, use and enjoy according to the license provided.
|
6
6
|
*/
|
7
7
|
#ifndef LIB_SOCK
|
8
|
-
#define LIB_SOCK "0.2.
|
8
|
+
#define LIB_SOCK "0.2.2"
|
9
9
|
#define LIB_SOCK_VERSION_MAJOR 0
|
10
10
|
#define LIB_SOCK_VERSION_MINOR 2
|
11
|
-
#define LIB_SOCK_VERSION_PATCH
|
11
|
+
#define LIB_SOCK_VERSION_PATCH 2
|
12
12
|
|
13
13
|
/** \file
|
14
14
|
The libsock is a non-blocking socket helper library, using a user level buffer,
|
@@ -42,7 +42,7 @@ This information is also useful when implementing read / write hooks.
|
|
42
42
|
#define BUFFER_FILE_READ_SIZE BUFFER_PACKET_SIZE
|
43
43
|
#endif
|
44
44
|
#ifndef BUFFER_PACKET_POOL
|
45
|
-
#define BUFFER_PACKET_POOL
|
45
|
+
#define BUFFER_PACKET_POOL 1024
|
46
46
|
#endif
|
47
47
|
|
48
48
|
/* *****************************************************************************
|
@@ -218,12 +218,17 @@ void sock_touch(intptr_t uuid);
|
|
218
218
|
`sock_read` attempts to read up to count bytes from the socket into the buffer
|
219
219
|
starting at buf.
|
220
220
|
|
221
|
-
|
222
|
-
|
223
|
-
read using sock_read (i.e., when using a transport layer, such as TLS).
|
221
|
+
`sock_read`'s return values are wildly different then the native return values
|
222
|
+
and they aim at making far simpler sense.
|
224
223
|
|
225
|
-
|
226
|
-
|
224
|
+
`sock_read` returns the number of bytes read (0 is a valid return value which
|
225
|
+
simply means that no bytes were read from the buffer).
|
226
|
+
|
227
|
+
On a connection error (NOT EAGAIN or EWOULDBLOCK) or signal interrupt,
|
228
|
+
`sock_read` returns -1.
|
229
|
+
|
230
|
+
Data might be available in the kernel's buffer while it is not available to be
|
231
|
+
read using `sock_read` (i.e., when using a transport layer, such as TLS).
|
227
232
|
*/
|
228
233
|
ssize_t sock_read(intptr_t uuid, void *buf, size_t count);
|
229
234
|
|
data/iodine.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ['Boaz Segev']
|
10
10
|
spec.email = ['Boaz@2be.co.il']
|
11
11
|
|
12
|
-
spec.summary = ' Iodine -
|
13
|
-
spec.description = ' Iodine -
|
12
|
+
spec.summary = ' Iodine - leveraging C for Ruby servers.'
|
13
|
+
spec.description = ' Iodine - leveraging C for Ruby servers.'
|
14
14
|
spec.homepage = 'https://github.com/boazsegev/iodine'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
@@ -34,9 +34,10 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency 'rack'
|
35
35
|
spec.add_dependency 'rake-compiler'
|
36
36
|
|
37
|
-
spec.requirements << 'A Unix based system
|
38
|
-
spec.requirements << 'An updated C compiler
|
37
|
+
spec.requirements << 'A Unix based system: Linux / macOS / BSD.'
|
38
|
+
spec.requirements << 'An updated C compiler.'
|
39
39
|
spec.requirements << 'Ruby >= 2.2.2'
|
40
|
+
spec.requirements << 'Ruby >= 2.0.0 is recommended.'
|
40
41
|
|
41
42
|
spec.add_development_dependency 'bundler', '~> 1.10'
|
42
43
|
spec.add_development_dependency 'rake', '~> 10.0'
|
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.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
description: " Iodine -
|
83
|
+
description: " Iodine - leveraging C for Ruby servers."
|
84
84
|
email:
|
85
85
|
- Boaz@2be.co.il
|
86
86
|
executables:
|
@@ -96,6 +96,7 @@ files:
|
|
96
96
|
- LICENSE.txt
|
97
97
|
- README.md
|
98
98
|
- Rakefile
|
99
|
+
- SPEC-Websocket-Draft.md
|
99
100
|
- bin/config.ru
|
100
101
|
- bin/console
|
101
102
|
- bin/echo
|
@@ -194,12 +195,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
194
195
|
- !ruby/object:Gem::Version
|
195
196
|
version: '0'
|
196
197
|
requirements:
|
197
|
-
- 'A Unix based system
|
198
|
-
- An updated C compiler
|
198
|
+
- 'A Unix based system: Linux / macOS / BSD.'
|
199
|
+
- An updated C compiler.
|
199
200
|
- Ruby >= 2.2.2
|
201
|
+
- Ruby >= 2.0.0 is recommended.
|
200
202
|
rubyforge_project:
|
201
203
|
rubygems_version: 2.5.1
|
202
204
|
signing_key:
|
203
205
|
specification_version: 4
|
204
|
-
summary: Iodine -
|
206
|
+
summary: Iodine - leveraging C for Ruby servers.
|
205
207
|
test_files: []
|