iodine 0.1.21 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- data/lib/iodine/timers.rb +0 -95
@@ -0,0 +1,244 @@
|
|
1
|
+
/*
|
2
|
+
copyright: Boaz segev, 2016
|
3
|
+
license: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#ifndef LIB_REACT
|
8
|
+
#define LIB_REACT "0.3.0"
|
9
|
+
#define LIB_REACT_VERSION_MAJOR 0
|
10
|
+
#define LIB_REACT_VERSION_MINOR 3
|
11
|
+
#define LIB_REACT_VERSION_PATCH 0
|
12
|
+
|
13
|
+
#ifndef _GNU_SOURCE
|
14
|
+
#define _GNU_SOURCE
|
15
|
+
#endif
|
16
|
+
|
17
|
+
#ifndef REACTOR_MAX_EVENTS
|
18
|
+
#define REACTOR_MAX_EVENTS 64
|
19
|
+
#endif
|
20
|
+
#ifndef REACTOR_TICK
|
21
|
+
#define REACTOR_TICK 256 /** in milliseconsd */
|
22
|
+
#endif
|
23
|
+
|
24
|
+
#include <stdint.h>
|
25
|
+
#include <unistd.h>
|
26
|
+
#include <sys/time.h>
|
27
|
+
#include <sys/types.h>
|
28
|
+
|
29
|
+
#if !defined(__unix__) && !defined(__linux__) && !defined(__APPLE__) && \
|
30
|
+
!defined(__CYGWIN__)
|
31
|
+
#error This library currently supports only Unix based systems (i.e. Linux and BSD)
|
32
|
+
#endif
|
33
|
+
|
34
|
+
/* until linux supports KQueue, which might not happen... */
|
35
|
+
#if !defined(__linux__) && !defined(__CYGWIN__)
|
36
|
+
#define reactor_epoll 1
|
37
|
+
#define reactor_kqueue 0
|
38
|
+
#else
|
39
|
+
#define reactor_epoll 0
|
40
|
+
#define reactor_kqueue 1
|
41
|
+
#endif
|
42
|
+
|
43
|
+
/* *****************************************************************************
|
44
|
+
A simple, predictable UUID for file-descriptors, for collision prevention
|
45
|
+
|
46
|
+
It's protected against duplicate definition (i.e., when including `libsock.h`)
|
47
|
+
*/
|
48
|
+
#ifndef FD_UUID_TYPE_DEFINED
|
49
|
+
#define FD_UUID_TYPE_DEFINED
|
50
|
+
/** fduuid_u is used to identify a specific connection, helping to manage file
|
51
|
+
* descriptor collisions (when a new connection receives an old connection's
|
52
|
+
* file descriptor), especially when the `on_close` event is fired after an
|
53
|
+
* `accept` was called and the old file descriptor was already recycled.
|
54
|
+
*
|
55
|
+
* This requires that sizeof(int) < sizeof(uintptr_t) or sizeof(int)*8 >= 32
|
56
|
+
*/
|
57
|
+
typedef union {
|
58
|
+
intptr_t uuid;
|
59
|
+
struct {
|
60
|
+
int fd : (sizeof(int) < sizeof(intptr_t) ? (sizeof(int) * 8) : 24);
|
61
|
+
unsigned counter : (sizeof(int) < sizeof(intptr_t)
|
62
|
+
? ((sizeof(intptr_t) - sizeof(int)) * 8)
|
63
|
+
: ((sizeof(intptr_t) * 8) - 24));
|
64
|
+
} data;
|
65
|
+
} fduuid_u;
|
66
|
+
|
67
|
+
#define FDUUID_FAIL(uuid) (uuid == -1)
|
68
|
+
#define sock_uuid2fd(uuid) ((fduuid_u)(uuid)).data.fd
|
69
|
+
#endif
|
70
|
+
|
71
|
+
/*****************************/ /** \file
|
72
|
+
This small library implements a reactor pattern using callbacks.
|
73
|
+
|
74
|
+
Here are the supported events and their callbacks:
|
75
|
+
|
76
|
+
- File Descriptor events:
|
77
|
+
|
78
|
+
- Ready to Read (`reactor_on_data` callback).
|
79
|
+
|
80
|
+
- Ready to Write (`reactor_on_ready` callback).
|
81
|
+
|
82
|
+
- Closed (`reactor_on_close` callback).
|
83
|
+
|
84
|
+
Here's a quick example for an HTTP hello world (no HTTP parsing required)...:
|
85
|
+
|
86
|
+
#include "libsock.h" // easy socket functions, also allows integration.
|
87
|
+
#include "libreact.h" // the reactor library
|
88
|
+
|
89
|
+
// a global server socket
|
90
|
+
int srvfd = -1;
|
91
|
+
// a global running flag
|
92
|
+
int run = 1;
|
93
|
+
|
94
|
+
// create the callback. This callback will be global and hardcoded,
|
95
|
+
// so there are no runtime function pointer resolutions.
|
96
|
+
void reactor_on_data(int fd) {
|
97
|
+
if (fd == srvfd) {
|
98
|
+
int new_client;
|
99
|
+
// accept connections.
|
100
|
+
while ((new_client = sock_accept(fd)) > 0) {
|
101
|
+
fprintf(stderr, "Accepted new connetion\n");
|
102
|
+
reactor_add(new_client);
|
103
|
+
}
|
104
|
+
fprintf(stderr, "No more clients... (or error)?\n");
|
105
|
+
} else {
|
106
|
+
fprintf(stderr, "Handling incoming data.\n");
|
107
|
+
// handle data
|
108
|
+
char data[1024];
|
109
|
+
ssize_t len;
|
110
|
+
while ((len = sock_read(fd, data, 1024)) > 0)
|
111
|
+
sock_write(fd,
|
112
|
+
"HTTP/1.1 200 OK\r\n"
|
113
|
+
"Content-Length: 12\r\n"
|
114
|
+
"Connection: keep-alive\r\n"
|
115
|
+
"Keep-Alive: 1;timeout=5\r\n"
|
116
|
+
"\r\n"
|
117
|
+
"Hello World!",
|
118
|
+
100);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
void reactor_on_close(int fd) {
|
123
|
+
fprintf(stderr, "%d closed the connection.\n", fd);
|
124
|
+
}
|
125
|
+
|
126
|
+
void stop_signal(int sig) {
|
127
|
+
run = 0;
|
128
|
+
signal(sig, SIG_DFL);
|
129
|
+
}
|
130
|
+
int main() {
|
131
|
+
srvfd = sock_listen(NULL, "3000");
|
132
|
+
signal(SIGINT, stop_signal);
|
133
|
+
signal(SIGTERM, stop_signal);
|
134
|
+
sock_lib_init();
|
135
|
+
reactor_init();
|
136
|
+
reactor_add(srvfd);
|
137
|
+
fprintf(stderr, "Starting reactor loop\n");
|
138
|
+
while (run && reactor_review() >= 0)
|
139
|
+
;
|
140
|
+
fprintf(stderr, "\nGoodbye.\n");
|
141
|
+
}
|
142
|
+
|
143
|
+
*/
|
144
|
+
|
145
|
+
/* *****************************************************************************
|
146
|
+
Initialization and global workflow.
|
147
|
+
*/
|
148
|
+
|
149
|
+
/**
|
150
|
+
Initializes the processes reactor object.
|
151
|
+
|
152
|
+
Reactor objects are a per-process object. Avoid forking a process with an active
|
153
|
+
reactor, as some unexpected results might occur.
|
154
|
+
|
155
|
+
Returns -1 on error, otherwise returns 0.
|
156
|
+
*/
|
157
|
+
ssize_t reactor_init();
|
158
|
+
/**
|
159
|
+
Reviews any pending events (up to REACTOR_MAX_EVENTS) and calls any callbacks.
|
160
|
+
|
161
|
+
Returns -1 on error, otherwise returns the number of events handled by the
|
162
|
+
reactor.
|
163
|
+
*/
|
164
|
+
int reactor_review();
|
165
|
+
/**
|
166
|
+
Closes the reactor, releasing it's resources.
|
167
|
+
*/
|
168
|
+
void reactor_stop();
|
169
|
+
|
170
|
+
/* *****************************************************************************
|
171
|
+
Adding and removing normal file descriptors.
|
172
|
+
*/
|
173
|
+
|
174
|
+
/**
|
175
|
+
Adds a file descriptor to the reactor, so that callbacks will be called for it's
|
176
|
+
events.
|
177
|
+
|
178
|
+
Returns -1 on error, otherwise return value is system dependent.
|
179
|
+
*/
|
180
|
+
int reactor_add(intptr_t uuid);
|
181
|
+
|
182
|
+
/**
|
183
|
+
Adds a timer file descriptor, so that callbacks will be called for it's events.
|
184
|
+
|
185
|
+
Returns -1 on error, otherwise return value is system dependent.
|
186
|
+
*/
|
187
|
+
int reactor_add_timer(intptr_t uuid, long milliseconds);
|
188
|
+
/**
|
189
|
+
Removes a file descriptor from the reactor - further callbacks for this file
|
190
|
+
descriptor won't be called.
|
191
|
+
|
192
|
+
Returns -1 on error, otherwise return value is system dependent. If the file
|
193
|
+
descriptor wasn't owned by the reactor, it isn't an error.
|
194
|
+
*/
|
195
|
+
int reactor_remove(intptr_t uuid);
|
196
|
+
/**
|
197
|
+
Removes a timer file descriptor from the reactor - further callbacks for this
|
198
|
+
file descriptor's timer events won't be called.
|
199
|
+
|
200
|
+
Returns -1 on error, otherwise return value is system dependent. If the file
|
201
|
+
descriptor wasn't owned by the reactor, it isn't an error.
|
202
|
+
*/
|
203
|
+
int reactor_remove_timer(intptr_t uuid);
|
204
|
+
/**
|
205
|
+
Closes a file descriptor, calling it's callback if it was registered with the
|
206
|
+
reactor.
|
207
|
+
*/
|
208
|
+
void reactor_close(intptr_t uuid);
|
209
|
+
|
210
|
+
/* *****************************************************************************
|
211
|
+
Timers.
|
212
|
+
*/
|
213
|
+
|
214
|
+
/**
|
215
|
+
Adds a file descriptor as a timer object.
|
216
|
+
|
217
|
+
Returns -1 on error, otherwise return value is system dependent.
|
218
|
+
*/
|
219
|
+
int reactor_add_timer(intptr_t uuid, long milliseconds);
|
220
|
+
/**
|
221
|
+
epoll requires the timer to be "reset" before repeating. Kqueue requires no such
|
222
|
+
thing.
|
223
|
+
|
224
|
+
This method promises that the timer will be repeated when running on epoll. This
|
225
|
+
method is redundent on kqueue.
|
226
|
+
*/
|
227
|
+
void reactor_reset_timer(intptr_t uuid);
|
228
|
+
/**
|
229
|
+
Creates a timer file descriptor, system dependent.
|
230
|
+
|
231
|
+
Returns -1 on error, or a valid fd on success (not an fd UUID, as these are
|
232
|
+
controlled by `libsock` and `libreact` can be used independently as well).
|
233
|
+
*/
|
234
|
+
intptr_t reactor_make_timer();
|
235
|
+
|
236
|
+
#if defined(__cplusplus)
|
237
|
+
extern "C" {
|
238
|
+
#endif
|
239
|
+
|
240
|
+
#if defined(__cplusplus)
|
241
|
+
} /* extern "C" */
|
242
|
+
#endif
|
243
|
+
|
244
|
+
#endif /* end of include guard: LIB_REACT */
|
@@ -0,0 +1,912 @@
|
|
1
|
+
/*
|
2
|
+
copyright: Boaz segev, 2016
|
3
|
+
license: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#ifndef _GNU_SOURCE
|
8
|
+
#define _GNU_SOURCE
|
9
|
+
#endif
|
10
|
+
#include "libserver.h"
|
11
|
+
#include <string.h>
|
12
|
+
#include <signal.h>
|
13
|
+
#include <pthread.h>
|
14
|
+
#include <sys/mman.h>
|
15
|
+
#include <sys/wait.h>
|
16
|
+
#include <errno.h>
|
17
|
+
|
18
|
+
/* *****************************************************************************
|
19
|
+
Connection Data
|
20
|
+
*/
|
21
|
+
typedef struct {
|
22
|
+
protocol_s* protocol;
|
23
|
+
time_t active;
|
24
|
+
uint8_t timeout;
|
25
|
+
spn_lock_i lock;
|
26
|
+
} fd_data_s;
|
27
|
+
|
28
|
+
/*
|
29
|
+
These macros mean we won't need to change code if we change the locking system.
|
30
|
+
*/
|
31
|
+
|
32
|
+
#define lock_fd_init(fd) (fd)->lock = SPN_LOCK_INIT
|
33
|
+
#define lock_fd_destroy(fd) spn_unlock(&((fd)->lock))
|
34
|
+
/** returns 0 on success, value on failure */
|
35
|
+
#define try_lock_fd(fd) spn_trylock(&((fd)->lock))
|
36
|
+
#define lock_fd(fd) spn_lock(&((fd)->lock))
|
37
|
+
#define unlock_fd(fd) spn_unlock(&((fd)->lock))
|
38
|
+
#define clear_fd_data(fd_data) \
|
39
|
+
{ *(fd_data) = (fd_data_s){.lock = (fd_data)->lock}; }
|
40
|
+
|
41
|
+
/* *****************************************************************************
|
42
|
+
Server Core Data
|
43
|
+
*/
|
44
|
+
static struct {
|
45
|
+
fd_data_s* fds;
|
46
|
+
time_t last_tick;
|
47
|
+
void (*on_idle)(void);
|
48
|
+
size_t capacity;
|
49
|
+
uint8_t running;
|
50
|
+
} server_data = {.fds = NULL};
|
51
|
+
|
52
|
+
/*
|
53
|
+
These macros help prevent code changes when changing the data struct.
|
54
|
+
*/
|
55
|
+
|
56
|
+
#define valid_uuid(uuid) sock_isvalid(uuid)
|
57
|
+
|
58
|
+
#define fd_data(fd) server_data.fds[(fd)]
|
59
|
+
|
60
|
+
#define uuid_data(uuid) fd_data(sock_uuid2fd(uuid))
|
61
|
+
|
62
|
+
#define clear_uuid(uuid) clear_fd_data(server_data.fds + sock_uuid2fd(uuid))
|
63
|
+
|
64
|
+
#define protocol_fd(fd) (server_data.fds[(fd)].protocol)
|
65
|
+
#define protocol_uuid(uuid) protocol_fd(sock_uuid2fd(uuid))
|
66
|
+
|
67
|
+
#define fduuid_get(ifd) (server_data.fds[(ifd)].uuid)
|
68
|
+
|
69
|
+
#define protocol_is_busy(protocol) \
|
70
|
+
spn_is_locked(&(((protocol_s*)(protocol))->callback_lock))
|
71
|
+
#define protocol_unset_busy(protocol) \
|
72
|
+
spn_unlock(&(((protocol_s*)(protocol))->callback_lock))
|
73
|
+
#define protocol_set_busy(protocol) \
|
74
|
+
spn_trylock(&(((protocol_s*)(protocol))->callback_lock))
|
75
|
+
|
76
|
+
#define try_lock_uuid(uuid) try_lock_fd(server_data.fds + sock_uuid2fd(uuid))
|
77
|
+
#define lock_uuid(uuid) lock_fd(server_data.fds + sock_uuid2fd(uuid))
|
78
|
+
#define unlock_uuid(uuid) unlock_fd(server_data.fds + sock_uuid2fd(uuid))
|
79
|
+
|
80
|
+
// run through any open sockets and call the shutdown handler
|
81
|
+
static inline void server_on_shutdown(void) {
|
82
|
+
if (server_data.fds && server_data.capacity > 0) {
|
83
|
+
for (size_t i = 0; i < server_data.capacity; i++) {
|
84
|
+
if (server_data.fds[i].protocol == NULL)
|
85
|
+
continue;
|
86
|
+
intptr_t uuid = sock_fd2uuid(i);
|
87
|
+
if (uuid != -1) {
|
88
|
+
if (server_data.fds[i].protocol->on_shutdown != NULL)
|
89
|
+
server_data.fds[i].protocol->on_shutdown(uuid,
|
90
|
+
server_data.fds[i].protocol);
|
91
|
+
sock_close(uuid);
|
92
|
+
sock_flush_strong(uuid);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
static void server_cleanup(void) {
|
99
|
+
// run through any open sockets and call the shutdown handler
|
100
|
+
server_on_shutdown();
|
101
|
+
// free any lock objects (no need to change code if changing locking systems)
|
102
|
+
for (size_t i = 0; i < server_data.capacity - 1; i++) {
|
103
|
+
server_data.fds[i] = (fd_data_s){0};
|
104
|
+
lock_fd_destroy(server_data.fds + i);
|
105
|
+
}
|
106
|
+
// free memory
|
107
|
+
if (server_data.fds) {
|
108
|
+
munmap(server_data.fds, sizeof(fd_data_s) * server_data.capacity);
|
109
|
+
server_data.fds = NULL;
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
static void init_server(void) {
|
114
|
+
pthread_mutex_t inner_lock = PTHREAD_MUTEX_INITIALIZER;
|
115
|
+
pthread_mutex_lock(&inner_lock);
|
116
|
+
if (server_data.fds == NULL) {
|
117
|
+
server_data.capacity = sock_max_capacity();
|
118
|
+
atexit(server_cleanup);
|
119
|
+
server_data.fds = mmap(NULL, sizeof(fd_data_s) * server_data.capacity,
|
120
|
+
PROT_READ | PROT_WRITE | PROT_EXEC,
|
121
|
+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
122
|
+
for (size_t i = 0; i < server_data.capacity - 1; i++) {
|
123
|
+
server_data.fds[i] = (fd_data_s){0};
|
124
|
+
lock_fd_init(server_data.fds + i);
|
125
|
+
}
|
126
|
+
}
|
127
|
+
pthread_mutex_unlock(&inner_lock);
|
128
|
+
}
|
129
|
+
|
130
|
+
/** initializes the library if it wasn't already initialized. */
|
131
|
+
#define validate_mem() \
|
132
|
+
{ \
|
133
|
+
if (server_data.fds == NULL) \
|
134
|
+
init_server(); \
|
135
|
+
}
|
136
|
+
|
137
|
+
/* *****************************************************************************
|
138
|
+
`libsock` Callback Implementation
|
139
|
+
*/
|
140
|
+
|
141
|
+
void sock_touch(intptr_t uuid) {
|
142
|
+
if (server_data.fds != NULL)
|
143
|
+
uuid_data(uuid).active = server_data.last_tick;
|
144
|
+
}
|
145
|
+
|
146
|
+
/* *****************************************************************************
|
147
|
+
The Reactor Callback Implementation
|
148
|
+
*/
|
149
|
+
|
150
|
+
void reactor_on_close_async(void* _pr) {
|
151
|
+
if (protocol_set_busy(_pr) == 0) {
|
152
|
+
((protocol_s*)_pr)->on_close(_pr);
|
153
|
+
return;
|
154
|
+
}
|
155
|
+
async_run(reactor_on_close_async, _pr);
|
156
|
+
}
|
157
|
+
|
158
|
+
void reactor_on_close(intptr_t uuid) {
|
159
|
+
if (server_data.fds) {
|
160
|
+
// get the currect state
|
161
|
+
lock_uuid(uuid);
|
162
|
+
protocol_s* protocol = protocol_uuid(uuid);
|
163
|
+
// clear state
|
164
|
+
clear_uuid(uuid);
|
165
|
+
unlock_uuid(uuid);
|
166
|
+
// call callback
|
167
|
+
if (protocol && protocol->on_close)
|
168
|
+
reactor_on_close_async(protocol);
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
void reactor_on_data_async(void* _fduuid) {
|
173
|
+
intptr_t fduuid = (intptr_t)_fduuid;
|
174
|
+
if (!valid_uuid(fduuid) || protocol_uuid(fduuid) == NULL)
|
175
|
+
return;
|
176
|
+
// try to lock the socket
|
177
|
+
if (try_lock_uuid(fduuid))
|
178
|
+
goto no_lock;
|
179
|
+
// get current state (protocol might have changed during this time)
|
180
|
+
protocol_s* protocol = protocol_uuid(fduuid);
|
181
|
+
// review protocol and get use privilage
|
182
|
+
if (protocol == NULL || protocol_set_busy(protocol)) {
|
183
|
+
// fprintf(stderr, "fduuid is busy %p\n", _fduuid);
|
184
|
+
unlock_uuid(fduuid);
|
185
|
+
goto no_lock;
|
186
|
+
}
|
187
|
+
// unlock
|
188
|
+
unlock_uuid(fduuid);
|
189
|
+
// fire event
|
190
|
+
if (protocol && protocol->on_data)
|
191
|
+
protocol->on_data(fduuid, protocol);
|
192
|
+
// clear the original busy flag
|
193
|
+
protocol_unset_busy(protocol);
|
194
|
+
return;
|
195
|
+
no_lock:
|
196
|
+
// fprintf(stderr, "no lock for %p\n", _fduuid);
|
197
|
+
// failed to aquire lock / busy
|
198
|
+
async_run(reactor_on_data_async, _fduuid);
|
199
|
+
}
|
200
|
+
|
201
|
+
void reactor_on_data(intptr_t fd) {
|
202
|
+
async_run(reactor_on_data_async, (void*)fd);
|
203
|
+
}
|
204
|
+
|
205
|
+
void reactor_on_ready(intptr_t uuid) {
|
206
|
+
uuid_data(uuid).active = server_data.last_tick;
|
207
|
+
lock_uuid(uuid);
|
208
|
+
protocol_s* protocol = protocol_uuid(uuid);
|
209
|
+
unlock_uuid(uuid);
|
210
|
+
if (protocol && protocol->on_ready)
|
211
|
+
protocol->on_ready(uuid, protocol);
|
212
|
+
}
|
213
|
+
|
214
|
+
/* *****************************************************************************
|
215
|
+
Zombie Reaping
|
216
|
+
With thanks to Dr Graham D Shaw.
|
217
|
+
http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html
|
218
|
+
*/
|
219
|
+
|
220
|
+
void reap_child_handler(int sig) {
|
221
|
+
int old_errno = errno;
|
222
|
+
while (waitpid(-1, NULL, WNOHANG) > 0)
|
223
|
+
;
|
224
|
+
errno = old_errno;
|
225
|
+
}
|
226
|
+
|
227
|
+
inline static void reap_children(void) {
|
228
|
+
struct sigaction sa;
|
229
|
+
sa.sa_handler = reap_child_handler;
|
230
|
+
sigemptyset(&sa.sa_mask);
|
231
|
+
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
232
|
+
if (sigaction(SIGCHLD, &sa, 0) == -1) {
|
233
|
+
perror("Child reaping initialization failed");
|
234
|
+
exit(1);
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
/* *****************************************************************************
|
239
|
+
Exit Signal Handling
|
240
|
+
*/
|
241
|
+
|
242
|
+
static void stop_server_handler(int sig) {
|
243
|
+
server_data.running = 0;
|
244
|
+
async_signal();
|
245
|
+
#if defined(SERVER_PRINT_STATE) && SERVER_PRINT_STATE == 1
|
246
|
+
fprintf(stderr, " --- Stop signal received ---\n");
|
247
|
+
#endif
|
248
|
+
signal(sig, SIG_DFL);
|
249
|
+
}
|
250
|
+
|
251
|
+
inline static void listen_for_stop_signal(void) {
|
252
|
+
struct sigaction sa;
|
253
|
+
sa.sa_handler = stop_server_handler;
|
254
|
+
sigemptyset(&sa.sa_mask);
|
255
|
+
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
256
|
+
if (sigaction(SIGINT, &sa, 0) || sigaction(SIGTERM, &sa, 0)) {
|
257
|
+
perror("Signal registration failed");
|
258
|
+
exit(2);
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
262
|
+
/* *****************************************************************************
|
263
|
+
The Listenning Protocol
|
264
|
+
*/
|
265
|
+
|
266
|
+
static const char* listener_protocol_name = "listening protocol __internal__";
|
267
|
+
|
268
|
+
struct ListenerProtocol {
|
269
|
+
protocol_s protocol;
|
270
|
+
protocol_s* (*on_open)(intptr_t uuid, void* udata);
|
271
|
+
void* udata;
|
272
|
+
void (*on_start)(void* udata);
|
273
|
+
void (*on_finish)(void* udata);
|
274
|
+
};
|
275
|
+
|
276
|
+
static void listener_on_data(intptr_t uuid, protocol_s* _listener) {
|
277
|
+
intptr_t new_client;
|
278
|
+
struct ListenerProtocol* listener = (void*)_listener;
|
279
|
+
while ((new_client = sock_accept(uuid)) != -1) {
|
280
|
+
// make sure it's a clean slate... although it should be assumed to be.
|
281
|
+
lock_uuid(new_client);
|
282
|
+
clear_uuid(new_client);
|
283
|
+
unlock_uuid(new_client);
|
284
|
+
// assume that sock_accept calls reactor_on_close if needed
|
285
|
+
protocol_uuid(new_client) = listener->on_open(new_client, listener->udata);
|
286
|
+
if (protocol_uuid(new_client)) {
|
287
|
+
uuid_data(new_client).active = server_data.last_tick;
|
288
|
+
protocol_unset_busy(protocol_uuid(new_client));
|
289
|
+
reactor_add(new_client);
|
290
|
+
continue;
|
291
|
+
} else {
|
292
|
+
sock_close(new_client);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
static void free_listenner(void* _li) {
|
298
|
+
free(_li);
|
299
|
+
}
|
300
|
+
|
301
|
+
static void listener_on_close(protocol_s* _listener) {
|
302
|
+
if (((struct ListenerProtocol*)_listener)->on_finish)
|
303
|
+
((struct ListenerProtocol*)_listener)
|
304
|
+
->on_finish(((struct ListenerProtocol*)_listener)->udata);
|
305
|
+
free_listenner(_listener);
|
306
|
+
}
|
307
|
+
|
308
|
+
static inline struct ListenerProtocol* listener_alloc(
|
309
|
+
struct ServerServiceSettings settings) {
|
310
|
+
struct ListenerProtocol* listener = malloc(sizeof(*listener));
|
311
|
+
if (listener) {
|
312
|
+
*listener = (struct ListenerProtocol){
|
313
|
+
.protocol.service = listener_protocol_name,
|
314
|
+
.protocol.on_data = listener_on_data,
|
315
|
+
.protocol.on_close = listener_on_close,
|
316
|
+
.on_open = settings.on_open,
|
317
|
+
.udata = settings.udata,
|
318
|
+
.on_start = settings.on_start,
|
319
|
+
.on_finish = settings.on_finish,
|
320
|
+
};
|
321
|
+
return listener;
|
322
|
+
}
|
323
|
+
return NULL;
|
324
|
+
}
|
325
|
+
|
326
|
+
inline static void listener_on_server_start(void) {
|
327
|
+
for (size_t i = 0; i < server_data.capacity; i++) {
|
328
|
+
if (protocol_fd(i) && protocol_fd(i)->service == listener_protocol_name) {
|
329
|
+
if (reactor_add(sock_fd2uuid(i)))
|
330
|
+
perror("Couldn't register listenning socket"), exit(4);
|
331
|
+
// call the on_init callback
|
332
|
+
if (((struct ListenerProtocol*)protocol_fd(i))->on_start)
|
333
|
+
((struct ListenerProtocol*)protocol_fd(i))
|
334
|
+
->on_start(((struct ListenerProtocol*)protocol_fd(i))->udata);
|
335
|
+
}
|
336
|
+
}
|
337
|
+
}
|
338
|
+
inline static void listener_on_server_shutdown(void) {
|
339
|
+
for (size_t i = 0; i < server_data.capacity; i++) {
|
340
|
+
if (protocol_fd(i) && protocol_fd(i)->service == listener_protocol_name) {
|
341
|
+
sock_close(sock_fd2uuid(i));
|
342
|
+
}
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
/* *****************************************************************************
|
347
|
+
* The timer protocol
|
348
|
+
*/
|
349
|
+
|
350
|
+
/* *******
|
351
|
+
Timer Protocol
|
352
|
+
******* */
|
353
|
+
typedef struct {
|
354
|
+
protocol_s protocol;
|
355
|
+
size_t milliseconds;
|
356
|
+
size_t repetitions;
|
357
|
+
void (*task)(void*);
|
358
|
+
void (*on_finish)(void*);
|
359
|
+
void* arg;
|
360
|
+
} timer_protocol_s;
|
361
|
+
|
362
|
+
#define prot2timer(protocol) (*((timer_protocol_s*)(protocol)))
|
363
|
+
|
364
|
+
const char* timer_protocol_name = "timer protocol __internal__";
|
365
|
+
|
366
|
+
static void timer_on_data(intptr_t uuid, protocol_s* protocol) {
|
367
|
+
prot2timer(protocol).task(prot2timer(protocol).arg);
|
368
|
+
if (prot2timer(protocol).repetitions) {
|
369
|
+
prot2timer(protocol).repetitions -= 1;
|
370
|
+
if (prot2timer(protocol).repetitions == 0) {
|
371
|
+
// fprintf(stderr, "closing timer?\n");
|
372
|
+
reactor_remove_timer(uuid);
|
373
|
+
sock_force_close(uuid);
|
374
|
+
}
|
375
|
+
}
|
376
|
+
reactor_reset_timer(uuid);
|
377
|
+
}
|
378
|
+
|
379
|
+
static void timer_on_close(protocol_s* protocol) {
|
380
|
+
// fprintf(stderr, "timer closed\n");
|
381
|
+
if (prot2timer(protocol).on_finish)
|
382
|
+
prot2timer(protocol).on_finish(prot2timer(protocol).arg);
|
383
|
+
free(protocol);
|
384
|
+
}
|
385
|
+
|
386
|
+
static inline timer_protocol_s* timer_alloc(void (*task)(void*),
|
387
|
+
void* arg,
|
388
|
+
size_t milliseconds,
|
389
|
+
size_t repetitions,
|
390
|
+
void (*on_finish)(void*)) {
|
391
|
+
timer_protocol_s* t = malloc(sizeof(*t));
|
392
|
+
if (t)
|
393
|
+
*t = (timer_protocol_s){
|
394
|
+
.protocol.service = timer_protocol_name,
|
395
|
+
.protocol.on_data = timer_on_data,
|
396
|
+
.protocol.on_close = timer_on_close,
|
397
|
+
.arg = arg,
|
398
|
+
.task = task,
|
399
|
+
.on_finish = on_finish,
|
400
|
+
.milliseconds = milliseconds,
|
401
|
+
.repetitions = repetitions,
|
402
|
+
};
|
403
|
+
return t;
|
404
|
+
}
|
405
|
+
|
406
|
+
inline static void timer_on_server_start(void) {
|
407
|
+
for (size_t i = 0; i < server_data.capacity; i++) {
|
408
|
+
if (protocol_fd(i) && protocol_fd(i)->service == timer_protocol_name) {
|
409
|
+
if (reactor_add_timer(sock_fd2uuid(i),
|
410
|
+
prot2timer(protocol_fd(i)).milliseconds))
|
411
|
+
perror("Couldn't register a required timed event."), exit(4);
|
412
|
+
}
|
413
|
+
}
|
414
|
+
}
|
415
|
+
|
416
|
+
/* *****************************************************************************
|
417
|
+
Reactor cycling and timeout handling
|
418
|
+
*/
|
419
|
+
|
420
|
+
static inline void timeout_review(void) {
|
421
|
+
static time_t review = 0;
|
422
|
+
if (review >= server_data.last_tick)
|
423
|
+
return;
|
424
|
+
time(&review);
|
425
|
+
for (size_t i = 0; i < server_data.capacity; i++) {
|
426
|
+
if (protocol_fd(i) == NULL)
|
427
|
+
continue; // Protocol objects are required for open connections.
|
428
|
+
if (fd_data(i).timeout == 0) {
|
429
|
+
if (protocol_fd(i) && protocol_fd(i)->service != listener_protocol_name &&
|
430
|
+
protocol_fd(i)->service != timer_protocol_name &&
|
431
|
+
review - fd_data(i).active > 300) {
|
432
|
+
sock_close(sock_fd2uuid(i));
|
433
|
+
}
|
434
|
+
continue;
|
435
|
+
}
|
436
|
+
if (fd_data(i).active + fd_data(i).timeout < review) {
|
437
|
+
if (protocol_fd(i)->ping) {
|
438
|
+
protocol_fd(i)->ping(sock_fd2uuid(i), protocol_fd(i));
|
439
|
+
} else if (!protocol_is_busy(protocol_fd(i)) ||
|
440
|
+
(review - fd_data(i).active > 300)) {
|
441
|
+
sock_close(sock_fd2uuid(i));
|
442
|
+
}
|
443
|
+
}
|
444
|
+
}
|
445
|
+
}
|
446
|
+
|
447
|
+
static void server_cycle(void* _) {
|
448
|
+
static int8_t perform_idle = 1;
|
449
|
+
time(&server_data.last_tick);
|
450
|
+
if (server_data.running) {
|
451
|
+
timeout_review();
|
452
|
+
int e_count = reactor_review();
|
453
|
+
if (e_count < 0) {
|
454
|
+
return;
|
455
|
+
}
|
456
|
+
if (e_count == 0) {
|
457
|
+
if (perform_idle && server_data.on_idle)
|
458
|
+
server_data.on_idle();
|
459
|
+
perform_idle = 0;
|
460
|
+
} else {
|
461
|
+
perform_idle = 1;
|
462
|
+
}
|
463
|
+
async_run(server_cycle, NULL);
|
464
|
+
}
|
465
|
+
}
|
466
|
+
/* *****************************************************************************
|
467
|
+
* The Server API
|
468
|
+
* (and helper functions)
|
469
|
+
*/
|
470
|
+
|
471
|
+
/* *****************************************************************************
|
472
|
+
* Server actions
|
473
|
+
*/
|
474
|
+
|
475
|
+
#undef server_listen
|
476
|
+
#undef server_run
|
477
|
+
/**
|
478
|
+
Listens to a server with the following server settings (which MUST include
|
479
|
+
a default protocol).
|
480
|
+
|
481
|
+
This method blocks the current thread until the server is stopped (either
|
482
|
+
though a `srv_stop` function or when a SIGINT/SIGTERM is received).
|
483
|
+
*/
|
484
|
+
int server_listen(struct ServerServiceSettings settings) {
|
485
|
+
validate_mem();
|
486
|
+
if (settings.on_open == NULL || settings.port == NULL)
|
487
|
+
return -1;
|
488
|
+
intptr_t fduuid = sock_listen(settings.address, settings.port);
|
489
|
+
if (fduuid == -1)
|
490
|
+
return -1;
|
491
|
+
server_data.fds[sock_uuid2fd(fduuid)].protocol =
|
492
|
+
(void*)listener_alloc(settings);
|
493
|
+
if (server_data.fds[sock_uuid2fd(fduuid)].protocol == NULL)
|
494
|
+
goto error;
|
495
|
+
if (server_data.running && reactor_add(fduuid))
|
496
|
+
goto error;
|
497
|
+
#if defined(SERVER_PRINT_STATE) && SERVER_PRINT_STATE == 1
|
498
|
+
fprintf(stderr, "* Listenning on port %s\n", settings.port);
|
499
|
+
#endif
|
500
|
+
return 0;
|
501
|
+
error:
|
502
|
+
sock_close(fduuid);
|
503
|
+
return -1;
|
504
|
+
}
|
505
|
+
/** runs the server, hanging the current process and thread. */
|
506
|
+
ssize_t server_run(struct ServerSettings settings) {
|
507
|
+
validate_mem();
|
508
|
+
if (server_data.running) {
|
509
|
+
return -1;
|
510
|
+
}
|
511
|
+
reap_children();
|
512
|
+
listen_for_stop_signal();
|
513
|
+
server_data.running = 1;
|
514
|
+
server_data.on_idle = settings.on_idle;
|
515
|
+
if (settings.processes == 0)
|
516
|
+
settings.processes = 1;
|
517
|
+
|
518
|
+
#if defined(SERVER_PRINT_STATE) && SERVER_PRINT_STATE == 1
|
519
|
+
if (settings.threads == 0)
|
520
|
+
fprintf(stderr,
|
521
|
+
"* Running %lu processes"
|
522
|
+
" in single thread mode.\n",
|
523
|
+
settings.processes);
|
524
|
+
else
|
525
|
+
fprintf(stderr,
|
526
|
+
"* Running %lu processes"
|
527
|
+
" X %lu threads.\n",
|
528
|
+
settings.processes, settings.threads);
|
529
|
+
#endif
|
530
|
+
|
531
|
+
pid_t rootpid = getpid();
|
532
|
+
pid_t* children = NULL;
|
533
|
+
if (settings.processes > 1) {
|
534
|
+
children = malloc(sizeof(*children) * settings.processes);
|
535
|
+
for (size_t i = 0; i < settings.processes - 1; i++) {
|
536
|
+
if (fork() == 0)
|
537
|
+
break;
|
538
|
+
}
|
539
|
+
}
|
540
|
+
if (reactor_init() < 0)
|
541
|
+
perror("Reactor initialization failed"), exit(3);
|
542
|
+
listener_on_server_start();
|
543
|
+
timer_on_server_start();
|
544
|
+
#if defined(SERVER_PRINT_STATE) && SERVER_PRINT_STATE == 1
|
545
|
+
fprintf(stderr, "* [%d] Running.\n", getpid());
|
546
|
+
#endif
|
547
|
+
async_start(settings.threads);
|
548
|
+
if (settings.on_init)
|
549
|
+
settings.on_init();
|
550
|
+
async_run(server_cycle, NULL);
|
551
|
+
if (settings.threads > 0)
|
552
|
+
async_join();
|
553
|
+
else
|
554
|
+
async_perform();
|
555
|
+
listener_on_server_shutdown();
|
556
|
+
reactor_review();
|
557
|
+
server_on_shutdown();
|
558
|
+
if (settings.on_finish)
|
559
|
+
settings.on_finish();
|
560
|
+
|
561
|
+
if (children) {
|
562
|
+
if (rootpid == getpid()) {
|
563
|
+
while (waitpid(-1, NULL, 0) >= 0)
|
564
|
+
;
|
565
|
+
}
|
566
|
+
free(children);
|
567
|
+
}
|
568
|
+
#if defined(SERVER_PRINT_STATE) && SERVER_PRINT_STATE == 1
|
569
|
+
fprintf(stderr, "* [%d] Shutdown.\n", getpid());
|
570
|
+
if (rootpid == getpid())
|
571
|
+
fprintf(stderr, "* Shutdown process complete.\n");
|
572
|
+
#endif
|
573
|
+
if (rootpid != getpid())
|
574
|
+
exit(0);
|
575
|
+
|
576
|
+
return 0;
|
577
|
+
}
|
578
|
+
|
579
|
+
void server_stop(void) {
|
580
|
+
server_data.running = 0;
|
581
|
+
}
|
582
|
+
/**
|
583
|
+
Returns the last time the server reviewed any pending IO events.
|
584
|
+
*/
|
585
|
+
time_t server_last_tick(void) {
|
586
|
+
return server_data.last_tick;
|
587
|
+
}
|
588
|
+
|
589
|
+
/* *****************************************************************************
|
590
|
+
* Socket actions
|
591
|
+
*/
|
592
|
+
|
593
|
+
/**
|
594
|
+
Sets a new active protocol object for the requested file descriptor.
|
595
|
+
|
596
|
+
This also schedules the old protocol's `on_close` callback to run, making sure
|
597
|
+
all resources are released.
|
598
|
+
|
599
|
+
Returns -1 on error (i.e. connection closed), otherwise returns 0.
|
600
|
+
*/
|
601
|
+
ssize_t server_switch_protocol(intptr_t fd, protocol_s* new_protocol) {
|
602
|
+
if (new_protocol == NULL || valid_uuid(fd) == 0)
|
603
|
+
return -1;
|
604
|
+
protocol_s* old_protocol;
|
605
|
+
lock_uuid(fd);
|
606
|
+
old_protocol = uuid_data(fd).protocol;
|
607
|
+
uuid_data(fd).protocol = new_protocol;
|
608
|
+
unlock_uuid(fd);
|
609
|
+
if (old_protocol && old_protocol->on_close)
|
610
|
+
reactor_on_close_async(old_protocol);
|
611
|
+
return 0;
|
612
|
+
}
|
613
|
+
/**
|
614
|
+
Gets the active protocol object for the requested file descriptor.
|
615
|
+
|
616
|
+
Returns NULL on error (i.e. connection closed), otherwise returns a `protocol_s`
|
617
|
+
pointer.
|
618
|
+
*/
|
619
|
+
protocol_s* server_get_protocol(intptr_t uuid) {
|
620
|
+
if (valid_uuid(uuid) == 0)
|
621
|
+
return NULL;
|
622
|
+
protocol_s* protocol;
|
623
|
+
lock_uuid(uuid);
|
624
|
+
protocol = uuid_data(uuid).protocol;
|
625
|
+
unlock_uuid(uuid);
|
626
|
+
return protocol;
|
627
|
+
}
|
628
|
+
/**
|
629
|
+
Sets a connection's timeout.
|
630
|
+
|
631
|
+
Returns -1 on error (i.e. connection closed), otherwise returns 0.
|
632
|
+
*/
|
633
|
+
void server_set_timeout(intptr_t fd, uint8_t timeout) {
|
634
|
+
if (valid_uuid(fd) == 0)
|
635
|
+
return;
|
636
|
+
lock_uuid(fd);
|
637
|
+
uuid_data(fd).timeout = timeout;
|
638
|
+
unlock_uuid(fd);
|
639
|
+
}
|
640
|
+
|
641
|
+
/** Attaches an existing connection (fd) to the server's reactor and protocol
|
642
|
+
management system, so that the server can be used also to manage connection
|
643
|
+
based resources asynchronously (i.e. database resources etc').
|
644
|
+
|
645
|
+
On failure the fduuid_u.data.fd value will be -1.
|
646
|
+
*/
|
647
|
+
intptr_t server_attach(int fd, protocol_s* protocol) {
|
648
|
+
intptr_t uuid = sock_open(fd);
|
649
|
+
if (uuid == -1)
|
650
|
+
return -1;
|
651
|
+
protocol_fd(fd) = protocol;
|
652
|
+
if (reactor_add(uuid)) {
|
653
|
+
sock_close(uuid);
|
654
|
+
return -1;
|
655
|
+
}
|
656
|
+
return uuid;
|
657
|
+
}
|
658
|
+
/** Hijack a socket (file descriptor) from the server, clearing up it's
|
659
|
+
resources. The control of hte socket is totally relinquished.
|
660
|
+
|
661
|
+
This method will block until all the data in the buffer is sent before
|
662
|
+
releasing control of the socket.
|
663
|
+
|
664
|
+
The returned value is the fd for the socket, or -1 on error.
|
665
|
+
*/
|
666
|
+
int server_hijack(intptr_t uuid) {
|
667
|
+
if (sock_isvalid(uuid) == 0)
|
668
|
+
return -1;
|
669
|
+
reactor_remove(uuid);
|
670
|
+
sock_flush_strong(uuid);
|
671
|
+
if (sock_isvalid(uuid) == 0)
|
672
|
+
return -1;
|
673
|
+
protocol_s* old_protocol;
|
674
|
+
lock_uuid(uuid);
|
675
|
+
old_protocol = uuid_data(uuid).protocol;
|
676
|
+
uuid_data(uuid).protocol = NULL;
|
677
|
+
unlock_uuid(uuid);
|
678
|
+
if (old_protocol && old_protocol->on_close)
|
679
|
+
reactor_on_close_async(old_protocol);
|
680
|
+
return sock_uuid2fd(uuid);
|
681
|
+
}
|
682
|
+
/** Counts the number of connections for the specified protocol (NULL = all
|
683
|
+
protocols). */
|
684
|
+
long server_count(char* service) {
|
685
|
+
long count = 0;
|
686
|
+
if (service == NULL) {
|
687
|
+
for (size_t i = 0; i < server_data.capacity; i++) {
|
688
|
+
if (protocol_fd(i) && protocol_fd(i)->service != listener_protocol_name &&
|
689
|
+
protocol_fd(i)->service != timer_protocol_name)
|
690
|
+
count++;
|
691
|
+
}
|
692
|
+
} else {
|
693
|
+
for (size_t i = 0; i < server_data.capacity; i++) {
|
694
|
+
if (protocol_fd(i) && protocol_fd(i)->service == service)
|
695
|
+
count++;
|
696
|
+
}
|
697
|
+
}
|
698
|
+
return count;
|
699
|
+
}
|
700
|
+
|
701
|
+
/* *****************************************************************************
|
702
|
+
* Connection Tasks (each and deffer tactics implementations)
|
703
|
+
*/
|
704
|
+
|
705
|
+
/* *******
|
706
|
+
Task core data
|
707
|
+
******* */
|
708
|
+
typedef struct {
|
709
|
+
intptr_t origin;
|
710
|
+
intptr_t target;
|
711
|
+
const char* service;
|
712
|
+
void (*task)(intptr_t fd, protocol_s* protocol, void* arg);
|
713
|
+
void* on_finish;
|
714
|
+
void* arg;
|
715
|
+
} srv_task_s;
|
716
|
+
|
717
|
+
/* Get task from void pointer. */
|
718
|
+
#define p2task(task) (*((srv_task_s*)(task)))
|
719
|
+
|
720
|
+
/* Get fallback callback from the task object. */
|
721
|
+
#define task2fallback(task) \
|
722
|
+
((void (*)(intptr_t, void*))(p2task(task).on_finish))
|
723
|
+
|
724
|
+
/* Get on_finished callback from the task object. */
|
725
|
+
#define task2on_done(task) \
|
726
|
+
((void (*)(intptr_t, protocol_s*, void*))(p2task(task).on_finish))
|
727
|
+
/* allows for later implementation of a task pool with minimal code updates. */
|
728
|
+
static inline srv_task_s* task_alloc(void) {
|
729
|
+
return malloc(sizeof(srv_task_s));
|
730
|
+
}
|
731
|
+
|
732
|
+
/* allows for later implementation of a task pool with minimal code updates. */
|
733
|
+
static inline void task_free(srv_task_s* task) {
|
734
|
+
return free(task);
|
735
|
+
}
|
736
|
+
|
737
|
+
/* performs a single connection task. */
|
738
|
+
static void perform_single_task(void* task) {
|
739
|
+
if (sock_isvalid(p2task(task).target) == 0) {
|
740
|
+
if (p2task(task).on_finish) // an invalid connection fallback
|
741
|
+
task2fallback(task)(p2task(task).origin, p2task(task).arg);
|
742
|
+
task_free(task);
|
743
|
+
return;
|
744
|
+
}
|
745
|
+
if (try_lock_uuid(p2task(task).target) == 0) {
|
746
|
+
// get protocol
|
747
|
+
protocol_s* protocol = protocol_uuid(p2task(task).target);
|
748
|
+
if (protocol_set_busy(protocol) == 0) {
|
749
|
+
// clear the original busy flag
|
750
|
+
unlock_uuid(p2task(task).target);
|
751
|
+
p2task(task).task(p2task(task).target, protocol, p2task(task).arg);
|
752
|
+
protocol_unset_busy(protocol);
|
753
|
+
task_free(task);
|
754
|
+
return;
|
755
|
+
}
|
756
|
+
unlock_uuid(p2task(task).target);
|
757
|
+
}
|
758
|
+
async_run(perform_single_task, task);
|
759
|
+
}
|
760
|
+
|
761
|
+
/* performs a connection group task. */
|
762
|
+
static void perform_each_task(void* task) {
|
763
|
+
intptr_t uuid;
|
764
|
+
protocol_s* protocol;
|
765
|
+
while (p2task(task).target < server_data.capacity) {
|
766
|
+
uuid = sock_fd2uuid(p2task(task).target);
|
767
|
+
if (uuid == -1 || uuid == p2task(task).origin) {
|
768
|
+
++p2task(task).target;
|
769
|
+
continue;
|
770
|
+
}
|
771
|
+
if (try_lock_uuid(uuid) == 0) {
|
772
|
+
protocol = protocol_uuid(uuid);
|
773
|
+
if (protocol == NULL || protocol->service != p2task(task).service) {
|
774
|
+
unlock_uuid(uuid);
|
775
|
+
++p2task(task).target;
|
776
|
+
continue;
|
777
|
+
} else if (protocol_set_busy(protocol) == 0) {
|
778
|
+
// unlock uuid
|
779
|
+
unlock_uuid(uuid);
|
780
|
+
// perform task
|
781
|
+
p2task(task).task(uuid, protocol, p2task(task).arg);
|
782
|
+
// clear the busy flag
|
783
|
+
protocol_unset_busy(protocol);
|
784
|
+
// step forward
|
785
|
+
++p2task(task).target;
|
786
|
+
continue;
|
787
|
+
}
|
788
|
+
// it's the right protocol and service, but we couldn't lock the protocol
|
789
|
+
unlock_uuid(uuid);
|
790
|
+
}
|
791
|
+
async_run(perform_each_task, task);
|
792
|
+
return;
|
793
|
+
}
|
794
|
+
if (p2task(task).on_finish) { // finished group task callback
|
795
|
+
task2on_done(task)(
|
796
|
+
p2task(task).origin,
|
797
|
+
(sock_isvalid(p2task(task).origin) ? protocol_uuid(p2task(task).origin)
|
798
|
+
: NULL),
|
799
|
+
p2task(task).arg);
|
800
|
+
}
|
801
|
+
task_free(task);
|
802
|
+
return;
|
803
|
+
}
|
804
|
+
|
805
|
+
/* *******
|
806
|
+
API
|
807
|
+
******* */
|
808
|
+
|
809
|
+
/**
|
810
|
+
Schedules a specific task to run asyncronously for each connection (except the
|
811
|
+
origin connection) on a specific protocol.
|
812
|
+
*/
|
813
|
+
void server_each(intptr_t origin_fd,
|
814
|
+
const char* service,
|
815
|
+
void (*task)(intptr_t fd, protocol_s* protocol, void* arg),
|
816
|
+
void* arg,
|
817
|
+
void (*on_finish)(intptr_t fd,
|
818
|
+
protocol_s* protocol,
|
819
|
+
void* arg)) {
|
820
|
+
srv_task_s* t = NULL;
|
821
|
+
if (service == NULL || task == NULL)
|
822
|
+
goto error;
|
823
|
+
t = task_alloc();
|
824
|
+
if (t == NULL)
|
825
|
+
goto error;
|
826
|
+
*t = (srv_task_s){.service = service,
|
827
|
+
.origin = origin_fd,
|
828
|
+
.task = task,
|
829
|
+
.on_finish = on_finish,
|
830
|
+
.arg = arg};
|
831
|
+
if (async_run(perform_each_task, t))
|
832
|
+
goto error;
|
833
|
+
return;
|
834
|
+
error:
|
835
|
+
if (t)
|
836
|
+
task_free(t);
|
837
|
+
if (on_finish)
|
838
|
+
on_finish(origin_fd,
|
839
|
+
(sock_isvalid(origin_fd) ? protocol_uuid(origin_fd) : NULL), arg);
|
840
|
+
}
|
841
|
+
|
842
|
+
/** Schedules a specific task to run asyncronously for a specific connection.
|
843
|
+
*/
|
844
|
+
void server_task(intptr_t caller_fd,
|
845
|
+
void (*task)(intptr_t fd, protocol_s* protocol, void* arg),
|
846
|
+
void* arg,
|
847
|
+
void (*fallback)(intptr_t fd, void* arg)) {
|
848
|
+
srv_task_s* t = NULL;
|
849
|
+
if (task == NULL)
|
850
|
+
goto error;
|
851
|
+
t = task_alloc();
|
852
|
+
if (t == NULL)
|
853
|
+
goto error;
|
854
|
+
*t = (srv_task_s){
|
855
|
+
.target = caller_fd, .task = task, .on_finish = fallback, .arg = arg};
|
856
|
+
if (async_run(perform_single_task, t))
|
857
|
+
goto error;
|
858
|
+
return;
|
859
|
+
error:
|
860
|
+
if (t)
|
861
|
+
task_free(t);
|
862
|
+
if (fallback)
|
863
|
+
fallback(caller_fd, arg);
|
864
|
+
}
|
865
|
+
|
866
|
+
/* *****************************************************************************
|
867
|
+
* Timed tasks
|
868
|
+
*/
|
869
|
+
|
870
|
+
/** Creates a system timer (at the cost of 1 file descriptor) and pushes the
|
871
|
+
timer to the reactor. The task will repeat `repetitions` times. if
|
872
|
+
`repetitions` is set to 0, task will repeat forever. Returns -1 on error
|
873
|
+
or the new file descriptor on succeess.
|
874
|
+
*/
|
875
|
+
int server_run_every(size_t milliseconds,
|
876
|
+
size_t repetitions,
|
877
|
+
void (*task)(void*),
|
878
|
+
void* arg,
|
879
|
+
void (*on_finish)(void*)) {
|
880
|
+
validate_mem();
|
881
|
+
if (task == NULL)
|
882
|
+
return -1;
|
883
|
+
timer_protocol_s* protocol = NULL;
|
884
|
+
intptr_t uuid = -1;
|
885
|
+
int fd = reactor_make_timer();
|
886
|
+
if (fd == -1) {
|
887
|
+
// perror("couldn't create a timer fd");
|
888
|
+
goto error;
|
889
|
+
}
|
890
|
+
uuid = sock_open(fd);
|
891
|
+
if (uuid == -1)
|
892
|
+
goto error;
|
893
|
+
clear_uuid(uuid);
|
894
|
+
protocol = timer_alloc(task, arg, milliseconds, repetitions, on_finish);
|
895
|
+
if (protocol == NULL)
|
896
|
+
goto error;
|
897
|
+
protocol_fd(fd) = (protocol_s*)protocol;
|
898
|
+
if (server_data.running && reactor_add_timer(uuid, milliseconds))
|
899
|
+
goto error;
|
900
|
+
return 0;
|
901
|
+
error:
|
902
|
+
if (uuid != -1)
|
903
|
+
sock_close(uuid);
|
904
|
+
else if (fd != -1)
|
905
|
+
close(fd);
|
906
|
+
|
907
|
+
if (protocol != NULL) {
|
908
|
+
protocol_fd(fd) = NULL;
|
909
|
+
timer_on_close((protocol_s*)protocol);
|
910
|
+
}
|
911
|
+
return -1;
|
912
|
+
}
|