diff options
Diffstat (limited to 'opentracker.c')
| -rw-r--r-- | opentracker.c | 214 |
1 files changed, 49 insertions, 165 deletions
diff --git a/opentracker.c b/opentracker.c index b47759e..2f2746d 100644 --- a/opentracker.c +++ b/opentracker.c | |||
| @@ -29,12 +29,14 @@ | |||
| 29 | #include "ot_ip.h" | 29 | #include "ot_ip.h" |
| 30 | #include "ot_livesync.h" | 30 | #include "ot_livesync.h" |
| 31 | #include "ot_mutex.h" | 31 | #include "ot_mutex.h" |
| 32 | #include "ot_workqueue.h" | ||
| 32 | #include "ot_random.h" | 33 | #include "ot_random.h" |
| 33 | #include "ot_scan.h" | 34 | #include "ot_scan.h" |
| 34 | #include "ot_event.h" | 35 | #include "ot_event.h" |
| 35 | #include "ot_fdstate.h" | 36 | #include "ot_fdstate.h" |
| 36 | #include "ot_socket.h" | 37 | #include "ot_socket.h" |
| 37 | #include "ot_stats.h" | 38 | #include "ot_stats.h" |
| 39 | #include "ot_tcp.h" | ||
| 38 | #include "ot_udp.h" | 40 | #include "ot_udp.h" |
| 39 | #include "trackerlogic.h" | 41 | #include "trackerlogic.h" |
| 40 | 42 | ||
| @@ -96,142 +98,6 @@ static void install_signal_handlers(void) { | |||
| 96 | } | 98 | } |
| 97 | 99 | ||
| 98 | 100 | ||
| 99 | static ssize_t header_complete(char *request, ssize_t byte_count) { | ||
| 100 | ssize_t i = 0, state = 0; | ||
| 101 | |||
| 102 | for (i = 1; i < byte_count; i += 2) | ||
| 103 | if (request[i] <= 13) { | ||
| 104 | i--; | ||
| 105 | for (state = 0; i < byte_count; ++i) { | ||
| 106 | char c = request[i]; | ||
| 107 | if (c == '\r' || c == '\n') | ||
| 108 | state = (state >> 2) | ((c << 6) & 0xc0); | ||
| 109 | else | ||
| 110 | break; | ||
| 111 | if (state >= 0xa0 || state == 0x99) | ||
| 112 | return i + 1; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | return 0; | ||
| 116 | } | ||
| 117 | |||
| 118 | static void handle_read(const int64_t sock, struct ot_workstruct *ws) { | ||
| 119 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 120 | ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE); | ||
| 121 | |||
| 122 | if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { | ||
| 123 | http_close(sock); | ||
| 124 | return; | ||
| 125 | } | ||
| 126 | |||
| 127 | if (byte_count == -1) | ||
| 128 | return; | ||
| 129 | |||
| 130 | ws->request = NULL; | ||
| 131 | ws->request_size = 0; | ||
| 132 | ws->header_size = 0; | ||
| 133 | ws->keep_alive = 0; | ||
| 134 | |||
| 135 | /* If we get the whole request in one packet, handle it without copying */ | ||
| 136 | if (!cookie->request.data) { | ||
| 137 | if ((ws->header_size = header_complete(ws->inbuf, byte_count))) { | ||
| 138 | ws->request = ws->inbuf; | ||
| 139 | ws->request_size = byte_count; | ||
| 140 | http_handle_request(sock, ws); | ||
| 141 | } else if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count)) | ||
| 142 | http_close(sock); | ||
| 143 | return; | ||
| 144 | } | ||
| 145 | |||
| 146 | if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count) || cookie->request.size > G_INBUF_SIZE) { | ||
| 147 | http_issue_error(sock, ws, CODE_HTTPERROR_500); | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | |||
| 151 | while ((ws->header_size = header_complete(cookie->request.data, (ssize_t)cookie->request.size))) { | ||
| 152 | ws->request = cookie->request.data; | ||
| 153 | ws->request_size = (ssize_t)cookie->request.size; | ||
| 154 | http_handle_request(sock, ws); | ||
| 155 | #ifdef WANT_KEEPALIVE | ||
| 156 | if (!ws->keep_alive) | ||
| 157 | #endif | ||
| 158 | return; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | static void handle_write(const int64_t sock) { | ||
| 163 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 164 | int chunked = 0; | ||
| 165 | int keep_alive = 0; | ||
| 166 | |||
| 167 | if (cookie) { | ||
| 168 | chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0; | ||
| 169 | keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0; | ||
| 170 | |||
| 171 | if (cookie->batch) { | ||
| 172 | if (batch_send(sock, cookie->batch) == BATCH_FAILED) { | ||
| 173 | http_close(sock); | ||
| 174 | return; | ||
| 175 | } | ||
| 176 | if (batch_pending(cookie->batch)) | ||
| 177 | return; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | /* In a chunked transfer, wait for the next piece once everything queued has gone out */ | ||
| 182 | if (chunked) | ||
| 183 | http_interest_set(sock, cookie, 0); | ||
| 184 | else if (keep_alive) { | ||
| 185 | /* The reply is out and the client asked to keep the connection, so go | ||
| 186 | back to waiting for the next request */ | ||
| 187 | http_interest_set(sock, cookie, EVENT_IN); | ||
| 188 | fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT)); | ||
| 189 | } else | ||
| 190 | http_close(sock); | ||
| 191 | } | ||
| 192 | |||
| 193 | /* The fast path, assuming that the complete request has usually arrived by the time a | ||
| 194 | connection is accepted, so it is answered here outside the event loop. The more load | ||
| 195 | a tracker has, the more likely it is that by the time it accepts, the roundtrip to | ||
| 196 | the peer has completed. | ||
| 197 | If the request could not be completely handled, the stack cookie is promoted to heap | ||
| 198 | and the connection handed over to the event loop to finish */ | ||
| 199 | static void handle_accept(const int64_t serversocket, struct ot_workstruct *ws) { | ||
| 200 | int64_t sock; | ||
| 201 | ot_ip6 ip; | ||
| 202 | uint16_t port; | ||
| 203 | |||
| 204 | while ((sock = socket_accept(serversocket, ip, &port)) != -1) { | ||
| 205 | struct http_data cookie; | ||
| 206 | uint32_t generation; | ||
| 207 | |||
| 208 | memset(&cookie, 0, sizeof(cookie)); | ||
| 209 | memcpy(cookie.ip, ip, sizeof(ot_ip6)); | ||
| 210 | cookie.interest = EVENT_IN; | ||
| 211 | cookie.flag = STRUCT_HTTP_FLAG_ONSTACK; | ||
| 212 | |||
| 213 | generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT)); | ||
| 214 | if (!fdstate_claim((int)sock, generation)) { | ||
| 215 | fdstate_close(sock); | ||
| 216 | event_close((int)sock); | ||
| 217 | continue; | ||
| 218 | } | ||
| 219 | |||
| 220 | stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); | ||
| 221 | |||
| 222 | handle_read(sock, ws); | ||
| 223 | |||
| 224 | /* Gone already if it was answered and closed inline */ | ||
| 225 | if (fdstate_generation((int)sock) != generation) | ||
| 226 | continue; | ||
| 227 | |||
| 228 | if (http_adopt(sock, &cookie) == -1) | ||
| 229 | http_close(sock); | ||
| 230 | else | ||
| 231 | fdstate_release((int)sock); | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | /* How many ready descriptors one wait hands back at a time */ | 101 | /* How many ready descriptors one wait hands back at a time */ |
| 236 | #define OT_EVENT_BATCH 512 | 102 | #define OT_EVENT_BATCH 512 |
| 237 | 103 | ||
| @@ -244,20 +110,9 @@ static void *server_mainloop(void *args) { | |||
| 244 | 110 | ||
| 245 | (void)args; | 111 | (void)args; |
| 246 | 112 | ||
| 247 | /* Initialize our "thread local storage" */ | 113 | if (workstruct_init(&ws) == -1) |
| 248 | memset(&ws, 0, sizeof(ws)); | ||
| 249 | stats_counters_register(&ws); | ||
| 250 | ws.inbuf = malloc(G_INBUF_SIZE); | ||
| 251 | ws.outbuf = malloc(G_OUTBUF_SIZE); | ||
| 252 | #ifdef _DEBUG_HTTPERROR | ||
| 253 | ws.debugbuf = malloc(G_DEBUGBUF_SIZE); | ||
| 254 | #endif | ||
| 255 | |||
| 256 | if (!ws.inbuf || !ws.outbuf) | ||
| 257 | panic("Initializing worker failed"); | 114 | panic("Initializing worker failed"); |
| 258 | 115 | ||
| 259 | random_bytes(ws.rand48_state, sizeof(ws.rand48_state)); | ||
| 260 | |||
| 261 | while (g_opentracker_running) { | 116 | while (g_opentracker_running) { |
| 262 | ot_event ready[OT_EVENT_BATCH]; | 117 | ot_event ready[OT_EVENT_BATCH]; |
| 263 | int64_t sock; | 118 | int64_t sock; |
| @@ -277,22 +132,24 @@ static void *server_mainloop(void *args) { | |||
| 277 | cookie = fdstate_cookie((int)sock); | 132 | cookie = fdstate_cookie((int)sock); |
| 278 | 133 | ||
| 279 | if ((intptr_t)cookie == FLAG_TCP) | 134 | if ((intptr_t)cookie == FLAG_TCP) |
| 280 | handle_accept(sock, &ws); | 135 | tcp_accept(sock, &ws); |
| 281 | else if ((intptr_t)cookie == FLAG_UDP) | 136 | else if ((intptr_t)cookie == FLAG_UDP) |
| 282 | handle_udp6(sock, &ws); | 137 | handle_udp6(sock, &ws); |
| 283 | else if ((intptr_t)cookie == FLAG_SELFPIPE) | 138 | else if ((intptr_t)cookie == FLAG_SELFPIPE) |
| 284 | while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0) | 139 | while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0) |
| 285 | ; | 140 | ; |
| 286 | else { | 141 | else if (fdstate_claim((int)sock, ready[i].generation)) { |
| 287 | if (ready[i].what & EVENT_IN) | 142 | if (ready[i].what & EVENT_IN) |
| 288 | handle_read(sock, &ws); | 143 | tcp_read(sock, &ws); |
| 289 | /* The read may have closed it, so look again before writing */ | 144 | /* The read may have closed it, so look again before writing */ |
| 290 | if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation) | 145 | if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation) |
| 291 | handle_write(sock); | 146 | tcp_write(sock); |
| 147 | if (fdstate_generation((int)sock) == ready[i].generation) | ||
| 148 | fdstate_release((int)sock); | ||
| 292 | } | 149 | } |
| 293 | } | 150 | } |
| 294 | 151 | ||
| 295 | while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) | 152 | while ((sock = workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) |
| 296 | http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); | 153 | http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); |
| 297 | 154 | ||
| 298 | if (time_now_seconds() > next_timeout_check) { | 155 | if (time_now_seconds() > next_timeout_check) { |
| @@ -308,13 +165,7 @@ static void *server_mainloop(void *args) { | |||
| 308 | 165 | ||
| 309 | /* This work struct is about to go out of scope, so hand its tally over | 166 | /* This work struct is about to go out of scope, so hand its tally over |
| 310 | before anything can read it there */ | 167 | before anything can read it there */ |
| 311 | stats_counters_retire(&ws); | 168 | workstruct_deinit(&ws); |
| 312 | |||
| 313 | free(ws.inbuf); | ||
| 314 | free(ws.outbuf); | ||
| 315 | #ifdef _DEBUG_HTTPERROR | ||
| 316 | free(ws.debugbuf); | ||
| 317 | #endif | ||
| 318 | return 0; | 169 | return 0; |
| 319 | } | 170 | } |
| 320 | 171 | ||
| @@ -323,7 +174,7 @@ static void *server_mainloop(void *args) { | |||
| 323 | member asked for it before binding, the first one included, so whether | 174 | member asked for it before binding, the first one included, so whether |
| 324 | this entry will have workers has to be known here. */ | 175 | this entry will have workers has to be known here. */ |
| 325 | static socket_sharing listen_sharing_for(const ot_listen *entry) { | 176 | static socket_sharing listen_sharing_for(const ot_listen *entry) { |
| 326 | if (entry->proto == FLAG_UDP && entry->workers > 1 && socket_can_balance()) | 177 | if (entry->proto != FLAG_MCA && entry->workers > 1 && socket_can_balance()) |
| 327 | return SOCKET_BALANCED; | 178 | return SOCKET_BALANCED; |
| 328 | return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; | 179 | return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; |
| 329 | } | 180 | } |
| @@ -379,10 +230,41 @@ static void listen_bind_all(void) { | |||
| 379 | listen_bind_one(entries + i); | 230 | listen_bind_one(entries + i); |
| 380 | } | 231 | } |
| 381 | 232 | ||
| 382 | /* The socket the event loop was using becomes the first worker's. Where the | 233 | /* The socket the event loop was using is passed to the first worker. |
| 383 | kernel spreads datagrams, every further worker binds one of its own, so | 234 | If start up tests show that the kernel can spread connections over |
| 384 | none of them share a receive queue. | 235 | a group bound to one address, every worker binds one of its own. |
| 385 | All others read from the first */ | 236 | If not, all workers call accept on the first. */ |
| 237 | static void listen_start_tcp_workers(void) { | ||
| 238 | size_t count, i; | ||
| 239 | ot_listen *entries = config_listen_entries(&count); | ||
| 240 | |||
| 241 | for (i = 0; i < count; ++i) { | ||
| 242 | ot_listen *entry = entries + i; | ||
| 243 | int balance = listen_sharing_for(entry) == SOCKET_BALANCED; | ||
| 244 | int worker; | ||
| 245 | |||
| 246 | if (entry->proto != FLAG_TCP || entry->workers <= 0) | ||
| 247 | continue; | ||
| 248 | |||
| 249 | event_del((int)entry->sock); | ||
| 250 | tcp_worker_install(entry->sock); | ||
| 251 | |||
| 252 | for (worker = 1; worker < entry->workers; ++worker) { | ||
| 253 | int64_t sock = entry->sock; | ||
| 254 | |||
| 255 | if (balance) { | ||
| 256 | sock = socket_tcp_listen(entry->ip, entry->port, SOCKET_BALANCED); | ||
| 257 | if (sock == -1) { | ||
| 258 | fprintf(stderr, "Warning: could not bind another socket for tcp worker %d: %s. Sharing the first instead.\n", worker, | ||
| 259 | strerror(errno)); | ||
| 260 | sock = entry->sock; | ||
| 261 | } | ||
| 262 | } | ||
| 263 | tcp_worker_install(sock); | ||
| 264 | } | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 386 | static void listen_start_udp_workers(void) { | 268 | static void listen_start_udp_workers(void) { |
| 387 | size_t count, i; | 269 | size_t count, i; |
| 388 | ot_listen *entries = config_listen_entries(&count); | 270 | ot_listen *entries = config_listen_entries(&count); |
| @@ -543,9 +425,11 @@ int main(int argc, char **argv) { | |||
| 543 | 425 | ||
| 544 | install_signal_handlers(); | 426 | install_signal_handlers(); |
| 545 | 427 | ||
| 428 | listen_start_tcp_workers(); | ||
| 546 | listen_start_udp_workers(); | 429 | listen_start_udp_workers(); |
| 547 | 430 | ||
| 548 | server_mainloop(0); | 431 | server_mainloop(0); |
| 432 | tcp_workers_deinit(); | ||
| 549 | udp_workers_deinit(); | 433 | udp_workers_deinit(); |
| 550 | 434 | ||
| 551 | /* No thread is serving any more, so the teardown can run where a debugger | 435 | /* No thread is serving any more, so the teardown can run where a debugger |
