An HTTP/1.1 server for zig
0

Configure Feed

Select the types of activity you want to include in your feed.

http.zig / src / worker.zig
90 kB 2252 lines
1const std = @import("std"); 2const builtin = @import("builtin"); 3 4const posix = @import("posix.zig"); 5const httpz = @import("httpz.zig"); 6const metrics = @import("metrics.zig"); 7const ws = @import("websocket").server; 8 9const Config = httpz.Config; 10const Request = httpz.Request; 11const Response = httpz.Response; 12 13const BufferPool = @import("buffer.zig").Pool; 14const ThreadPool = @import("thread_pool.zig").ThreadPool; 15 16const Io = std.Io; 17const Stream = Io.Stream; 18const Allocator = std.mem.Allocator; 19const ArenaAllocator = std.heap.ArenaAllocator; 20 21const log = std.log.scoped(.httpz); 22 23const MAX_TIMEOUT = 2_147_483_647; 24// Small test batches force lifecycle tests to cross wait() boundaries; the 25// production size remains tuned for throughput. 26const EVENT_BATCH_CAPACITY = if (builtin.is_test) 8 else 128; 27 28// This is our Blocking worker. It's very different than NonBlocking and much 29// simpler. (WSH is our websocket handler, and can be void) 30pub fn Blocking(comptime S: type, comptime WSH: type) type { 31 return struct { 32 io: Io, 33 server: S, 34 mut: Io.Mutex, 35 config: *const Config, 36 allocator: Allocator, 37 buffer_pool: *BufferPool, 38 http_conn_pool: HTTPConnPool, 39 websocket: *ws.Worker(WSH), 40 timeout_request: ?Timeout, 41 timeout_keepalive: ?Timeout, 42 timeout_write_error: Timeout, 43 retain_allocated_bytes_keepalive: usize, 44 connections: List(ConnNode), 45 conn_node_pool: std.heap.MemoryPool(ConnNode), 46 thread_pool: ThreadPool(Self.handleConnection), 47 48 const ConnNode = struct { 49 next: ?*ConnNode, 50 prev: ?*ConnNode, 51 socket: posix.fd_t, 52 }; 53 54 const Timeout = struct { 55 sec: u32, 56 timeval: [@sizeOf(posix.timeval)]u8, 57 58 // if sec is null, it means we want to cancel the timeout. 59 fn init(sec: ?u32) Timeout { 60 return .{ 61 .sec = if (sec) |s| s else MAX_TIMEOUT, 62 .timeval = std.mem.toBytes(posix.timeval{ .sec = @intCast(sec orelse 0), .usec = 0 }), 63 }; 64 } 65 }; 66 67 const Self = @This(); 68 69 pub fn init(io: Io, allocator: Allocator, server: S, config: *const Config) !Self { 70 const buffer_pool = try initializeBufferPool(io, allocator, config); 71 errdefer allocator.destroy(buffer_pool); 72 73 errdefer buffer_pool.deinit(); 74 75 var timeout_request: ?Timeout = null; 76 if (config.timeout.request) |sec| { 77 timeout_request = Timeout.init(sec); 78 } else if (config.timeout.keepalive != null) { 79 // We have to set this up, so that when we transition from 80 // keepalive state to a request parsing state, we remove the timeout 81 timeout_request = Timeout.init(0); 82 } 83 84 var timeout_keepalive: ?Timeout = null; 85 if (config.timeout.keepalive) |sec| { 86 timeout_keepalive = Timeout.init(sec); 87 } else if (timeout_request != null) { 88 // We have to set this up, so that when we transition from 89 // request processing state to a kepealive state, we remove the timeout 90 timeout_keepalive = Timeout.init(0); 91 } 92 93 const websocket = try allocator.create(ws.Worker(WSH)); 94 errdefer allocator.destroy(websocket); 95 websocket.* = try ws.Worker(WSH).init(allocator, &server._websocket_state); 96 errdefer websocket.deinit(); 97 98 var http_conn_pool = try HTTPConnPool.init(io, allocator, buffer_pool, websocket, 0, config); 99 errdefer http_conn_pool.deinit(); 100 101 const retain_allocated_bytes_keepalive = config.workers.retain_allocated_bytes orelse 8192; 102 103 var thread_pool = try ThreadPool(Self.handleConnection).init(io, allocator, .{ 104 .count = config.threadPoolCount(), 105 .backlog = config.thread_pool.backlog orelse 500, 106 .buffer_size = config.thread_pool.buffer_size orelse 32_768, 107 }); 108 errdefer { 109 thread_pool.stop(); 110 thread_pool.deinit(); 111 } 112 113 return .{ 114 .io = io, 115 .mut = .init, 116 .server = server, 117 .config = config, 118 .connections = .{}, 119 .allocator = allocator, 120 .websocket = websocket, 121 .buffer_pool = buffer_pool, 122 .thread_pool = thread_pool, 123 .http_conn_pool = http_conn_pool, 124 .timeout_request = timeout_request, 125 .timeout_keepalive = timeout_keepalive, 126 .timeout_write_error = Timeout.init(5), 127 .conn_node_pool = .empty, 128 .retain_allocated_bytes_keepalive = retain_allocated_bytes_keepalive, 129 }; 130 } 131 132 pub fn deinit(self: *Self) void { 133 const allocator = self.allocator; 134 135 self.websocket.deinit(); 136 self.thread_pool.deinit(); 137 allocator.destroy(self.websocket); 138 139 self.http_conn_pool.deinit(); 140 self.conn_node_pool.deinit(allocator); 141 142 self.buffer_pool.deinit(); 143 allocator.destroy(self.buffer_pool); 144 } 145 146 pub fn listen(self: *Self, listener: posix.socket_t) void { 147 const io = self.io; 148 var thread_pool = &self.thread_pool; 149 while (true) { 150 var address: posix.Address = undefined; 151 var address_len: posix.socklen_t = @sizeOf(posix.Address); 152 const socket = posix.accept(listener, &address.any, &address_len, posix.CLOEXEC) catch |err| { 153 if (err == error.ConnectionAborted or err == error.SocketNotListening) { 154 self.websocket.shutdown(); 155 break; 156 } 157 log.err("Failed to accept socket: {}", .{err}); 158 continue; 159 }; 160 metrics.connection(); 161 // calls handleConnection through the server's thread_pool 162 thread_pool.spawnOne(.{ self, socket, address }); 163 } 164 165 { 166 self.mut.lockUncancelable(io); 167 defer self.mut.unlock(io); 168 var node = self.connections.head; 169 while (node) |n| { 170 node = n.next; 171 posix.close(n.socket); 172 } 173 } 174 thread_pool.stop(); 175 } 176 177 pub fn stop(self: *const Self) void { 178 // The HTTP server will stop when the http.Server shutdown the listening socket. 179 self.websocket.shutdown(); 180 } 181 182 // Called in a worker thread. `thread_buf` is a thread-specific buffer that 183 // we are free to use as needed. 184 pub fn handleConnection(self: *Self, socket: posix.socket_t, address: posix.Address, thread_buf: []u8) void { 185 const io = self.io; 186 const connection_node = blk: { 187 self.mut.lockUncancelable(io); 188 defer self.mut.unlock(io); 189 const node = self.conn_node_pool.create(self.allocator) catch |err| { 190 log.err("Failed to initialize connection node: {}", .{err}); 191 return; 192 }; 193 node.* = .{ 194 .next = null, 195 .prev = null, 196 .socket = socket, 197 }; 198 self.connections.insert(node); 199 break :blk node; 200 }; 201 202 defer { 203 self.mut.lockUncancelable(io); 204 defer self.mut.unlock(io); 205 self.connections.remove(connection_node); 206 self.conn_node_pool.destroy(connection_node); 207 } 208 209 var conn = self.http_conn_pool.acquire() catch |err| { 210 log.err("Failed to initialize connection: {}", .{err}); 211 return; 212 }; 213 214 const ip_address = address.toIOAddress(); 215 conn.address = ip_address; 216 conn.handover = .unknown; 217 conn.stream = .{ .socket = .{ .handle = socket, .address = ip_address } }; 218 219 var is_keepalive = false; 220 while (true) { 221 switch (self.handleRequest(conn, is_keepalive, thread_buf) catch .close) { 222 .keepalive => { 223 is_keepalive = true; 224 conn.requestDone(self.retain_allocated_bytes_keepalive, false) catch unreachable; 225 }, 226 .close, .unknown => { 227 posix.close(socket); 228 // impossible for this to fail in blocking mode 229 conn.requestDone(self.retain_allocated_bytes_keepalive, false) catch unreachable; 230 self.http_conn_pool.release(conn); 231 return; 232 }, 233 .websocket => |ptr| { 234 const hc: *ws.HandlerConn(WSH) = @ptrCast(@alignCast(ptr)); 235 // impossible for this to fail in blocking mode 236 conn.requestDone(self.retain_allocated_bytes_keepalive, false) catch unreachable; 237 self.http_conn_pool.release(conn); 238 // blocking read loop 239 // will close the connection 240 self.handleWebSocket(hc) catch |err| { 241 log.err("({f} websocket connection error: {}", .{ address, err }); 242 }; 243 return; 244 }, 245 .disown => { 246 // impossible for this to fail in blocking mode 247 conn.requestDone(self.retain_allocated_bytes_keepalive, false) catch unreachable; 248 self.http_conn_pool.release(conn); 249 return; 250 }, 251 } 252 } 253 } 254 255 fn handleRequest(self: *const Self, conn: *HTTPConn, is_keepalive: bool, thread_buf: []u8) !HTTPConn.Handover { 256 const io = self.io; 257 const socket = conn.stream.socket.handle; 258 const timeout: ?Timeout = if (is_keepalive) self.timeout_keepalive else self.timeout_request; 259 260 var deadline: ?i64 = null; 261 262 if (timeout) |to| { 263 if (is_keepalive == false) { 264 deadline = timestamp(io) + to.sec; 265 } 266 try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &to.timeval); 267 } 268 269 var is_first = true; 270 while (true) { 271 const done = conn.req_state.parse(conn, socket) catch |err| { 272 switch (err) { 273 error.WouldBlock => { 274 if (is_keepalive and is_first) { 275 metrics.timeoutKeepalive(1); 276 } else { 277 metrics.timeoutRequest(1); 278 } 279 return .close; 280 }, 281 error.NotOpenForReading => { 282 // This can only happen when we're shutting down and our 283 // listener has called posix.close(socket) to unblock 284 // this thread. Using `.disown` is a bit of a hack, but 285 // disown is handled in handleConnection the way we want 286 // WE DO NOT WANT to return .close, else that would result 287 // in posix.close(socket) being called on an already-closed 288 // socket, which would panic. 289 return .disown; 290 }, 291 else => {}, 292 } 293 requestError(conn, err) catch {}; 294 posix.close(socket); 295 return .disown; 296 }; 297 298 if (done) { 299 // we have a complete request, time to process it 300 break; 301 } 302 303 if (is_keepalive) { 304 if (is_first) { 305 if (self.timeout_request) |to| { 306 // This was the first data from a keepalive request. 307 // We would have been on a keepalive timeout (if there was one), 308 // and now need to switch to a request timeout. This might be 309 // an actual timeout, or it could just be removing the keepalive timeout 310 // either way, it's the same code (timeval will just be set to 0 for 311 // the second case) 312 deadline = timestamp(io) + to.sec; 313 try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &to.timeval); 314 } 315 is_first = false; 316 } 317 } else if (deadline) |dl| { 318 if (timestamp(io) > dl) { 319 metrics.timeoutRequest(1); 320 return .close; 321 } 322 } 323 } 324 325 metrics.request(); 326 self.server.handleRequest(conn, thread_buf); 327 return conn.handover; 328 } 329 330 fn handleWebSocket(self: *const Self, hc: *ws.HandlerConn(WSH)) !void { 331 posix.setsockopt(hc.socket, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &std.mem.toBytes(posix.timeval{ .sec = 0, .usec = 0 })) catch |err| { 332 self.websocket.cleanupConn(hc); 333 return err; 334 }; 335 // closes the connection before returning 336 return self.websocket.worker.readLoop(hc); 337 } 338 }; 339} 340 341// This is a NonBlocking worker. We have N workers, each accepting connections 342// and largely working in isolation from each other (the only thing they share 343// is the *const config, a reference to the Server and to the Websocket server). 344// The bulk of the code in this file exists to support the NonBlocking Worker. 345// Our listening socket is nonblocking 346// Request sockets are blocking. WHAT? We'll use epoll/kqueue to know when 347// a read wouldn't block. But we want writes to block because we want a nice 348// and easy API for the app by giving their response a predictable/controllable 349// lifetime. 350// A previous version had the sockets in nonblocking and would switch to blocking 351// as necessary, but I don't see what value that adds. It's just more syscalls 352// and complexity. 353pub fn NonBlocking(comptime S: type, comptime WSH: type) type { 354 return struct { 355 io: Io, 356 357 // Reference to the httpz.Server. After we've parsed the request we 358 // call its handleRequest method. 359 server: S, 360 361 // The allocator passed to the server 362 allocator: Allocator, 363 364 // KQueue or Epoll, depending on the platform 365 loop: Loop, 366 367 // The maximum number of connection we should have at any given time 368 // This is specific to this worker (it's a per-worker config). 369 max_conn: usize, 370 371 // # of connections this worker is handling. 372 len: usize, 373 374 // whether or not the worker is full (len == max_conn) 375 full: bool, 376 377 config: *const Config, 378 379 websocket: *ws.Worker(WSH), 380 381 // how many bytes should we retain in a connection's arena allocator 382 retain_allocated_bytes: usize, 383 384 // The thread pool that'll actually handle any incoming data. This is what 385 // will call server.handleRequest which will eventually call the application 386 // action. 387 thread_pool: ThreadPool(Self.processData), 388 389 // List of connections waiting on for more data to complete a request 390 // (we've gotten some data, but not enough to service the request). 391 // This is only ever manipulated directly from the worker thread 392 // so doesn't need thread safety. 393 request_list: List(Conn(WSH)), 394 395 // Connections which have had at least 1 sucessful request and response 396 // and which we are now waiting for another request. 397 // This is only ever manipulated directly from the worker thread 398 // so doesn't need thread safety. 399 keepalive_list: ConcurrentList(Conn(WSH)), 400 401 // Requests currently being processed. 402 // The worker thread moves the connnection here, but the thread pool 403 // will remove it and put it into handover_list, so this has to be 404 // thread safe. 405 active_list: ConcurrentList(Conn(WSH)), 406 407 // List of connections which have had a request->response fully handled 408 // and are now being handed back to the worker (from the thread pool) to 409 // process, essentially telling the worker to handle the conn.handover case. 410 // The thread pool inserts into this (so it has to be thread safe for 411 // that alone) and the worker thread will process it. 412 handover_list: ConcurrentList(Conn(WSH)), 413 414 // Conn nodes whose websocket connection died in the thread pool. The 415 // pool thread can't release them itself — conn_mem_pool and len are 416 // reactor-owned — so it parks them here and signals the worker, which 417 // drains this after consuming the current event batch. Without this, 418 // every websocket 419 // connection permanently consumed a conn slot and the worker stopped 420 // accepting once len reached max_conn. 421 ws_graveyard: ConcurrentList(Conn(WSH)), 422 423 // A pool of HTTPConn objects. The pool maintains a configured min # of these. 424 // An HTTPConn is relatively expensive, since we pre-allocate all types 425 // of things (a Request.State and Response.State). 426 http_conn_pool: HTTPConnPool, 427 428 // The bulk of this code works with an *HTTPConn, but we wrap it in a 429 // Conn(WSH) because an actual connection can either be an HTTPConn or 430 // a ws.HandlerConn. So Conn(WSH) is essentially a union between these two. 431 // It also has a next/prev, allowing it to be inserted in the above 4 432 // lists (requets/keepalive/active/handover). 433 // It's lightweight enough that we can use a MemoryPool and don't need 434 // the fancier management of something like HTTPConnPool for the HTTPConns 435 conn_mem_pool: std.heap.MemoryPool(Conn(WSH)), 436 437 // Request and response processing may require larger buffers than the static 438 // buffered of our req/res states. The BufferPool has larger pre-allocated 439 // buffers that can be used and, when empty or when a larger buffer is needed, 440 // will do dynamic allocation 441 buffer_pool: *BufferPool, 442 443 // The timeout, in seconds, that connections in the request phase (in the 444 // request_list) will timeout. Entries in request_list are sorted with 445 // connections that are closest to timeing out at the head. 446 timeout_request: u32, 447 448 // The timeout, in seconds, that connections in the keepalive phase (in the 449 // keepalive_list will timeout. Entries in keepalive_list are sorted with 450 // connections that are closest to timeing out at the head. 451 timeout_keepalive: u32, 452 453 const Self = @This(); 454 455 const Loop = switch (loopType()) { 456 .kqueue => KQueue(WSH), 457 .epoll => EPoll(WSH), 458 }; 459 460 pub fn init(io: Io, allocator: Allocator, server: S, config: *const Config) !Self { 461 const loop = try Loop.init(); 462 errdefer loop.deinit(); 463 464 const websocket = try allocator.create(ws.Worker(WSH)); 465 errdefer allocator.destroy(websocket); 466 websocket.* = try ws.Worker(WSH).init(allocator, &server._websocket_state); 467 errdefer websocket.deinit(); 468 469 var buffer_pool = try initializeBufferPool(io, allocator, config); 470 errdefer buffer_pool.deinit(); 471 472 var conn_mem_pool: std.heap.MemoryPool(Conn(WSH)) = .empty; 473 errdefer conn_mem_pool.deinit(allocator); 474 475 var http_conn_pool = try HTTPConnPool.init(io, allocator, buffer_pool, websocket, loop.fd, config); 476 errdefer http_conn_pool.deinit(); 477 478 const thread_pool = try ThreadPool(Self.processData).init(io, allocator, .{ 479 .count = config.threadPoolCount(), 480 .backlog = config.thread_pool.backlog orelse 500, 481 .buffer_size = config.thread_pool.buffer_size orelse 32_768, 482 }); 483 484 errdefer { 485 thread_pool.stop(); 486 thread_pool.deinit(); 487 } 488 489 return .{ 490 .io = io, 491 .len = 0, 492 .full = false, 493 .loop = loop, 494 .config = config, 495 .server = server, 496 .allocator = allocator, 497 .websocket = websocket, 498 .thread_pool = thread_pool, 499 .active_list = .{}, 500 .request_list = .{}, 501 .handover_list = .{}, 502 .ws_graveyard = .{}, 503 .keepalive_list = .{}, 504 .buffer_pool = buffer_pool, 505 .conn_mem_pool = conn_mem_pool, 506 .http_conn_pool = http_conn_pool, 507 .max_conn = config.workers.max_conn orelse 8_192, 508 .timeout_request = config.timeout.request orelse MAX_TIMEOUT, 509 .timeout_keepalive = config.timeout.keepalive orelse MAX_TIMEOUT, 510 .retain_allocated_bytes = config.workers.retain_allocated_bytes orelse 8192, 511 }; 512 } 513 514 pub fn deinit(self: *Self) void { 515 const allocator = self.allocator; 516 517 // Join in-flight handlers before freeing ANY connection state they 518 // may still be reading. run() only stops the event loop, so a handler 519 // can still be mid-callback here. This must come before 520 // websocket.deinit(), which frees the per-connection read buffers via 521 // buffer_provider.deinit(): a handler still in dataAvailable() can be 522 // reading a parsed message that holds zero-copy slices into those 523 // buffers, so joining afterward would be too late -> use-after-free. 524 self.thread_pool.stop(); 525 526 self.websocket.deinit(); 527 allocator.destroy(self.websocket); 528 529 self.thread_pool.deinit(); 530 531 self.shutdownList(&self.request_list); 532 self.shutdownConcurrentList(&self.active_list); 533 self.shutdownConcurrentList(&self.handover_list); 534 self.shutdownConcurrentList(&self.keepalive_list); 535 536 self.buffer_pool.deinit(); 537 self.conn_mem_pool.deinit(allocator); 538 self.http_conn_pool.deinit(); 539 allocator.destroy(self.buffer_pool); 540 541 self.loop.deinit(); 542 } 543 544 pub fn stop(self: *Self) void { 545 // causes run to break out of its loop 546 self.loop.stop(); 547 } 548 549 pub fn run(self: *Self, listener: posix.fd_t, ready_sem: *Io.Semaphore) void { 550 const io = self.io; 551 var thread_pool = &self.thread_pool; 552 553 self.loop.start() catch |err| { 554 log.err("Failed to start event loop: {}", .{err}); 555 return; 556 }; 557 558 // Whether this fails or succeeds we can confidently signal upstream 559 // that we're ready enough to be stopped in necessary. 560 self.loop.monitorAccept(listener) catch |err| { 561 log.err("Failed to add monitor to listening socket: {}", .{err}); 562 ready_sem.post(io); 563 return; 564 }; 565 ready_sem.post(io); 566 defer self.websocket.shutdown(); 567 568 var now = timestamp(io); 569 var last_timeout = now; 570 while (true) { 571 var timeout: ?i32 = 1; 572 if (now - last_timeout > 1) { 573 574 // we don't all prepareToWait more than once per second 575 const had_timeouts, timeout = self.prepareToWait(now); 576 if (had_timeouts and self.full) { 577 self.enableListener(listener); 578 } 579 last_timeout = now; 580 } 581 582 var it = self.loop.wait(timeout) catch |err| { 583 log.err("Failed to wait on events: {}", .{err}); 584 io.sleep(.fromMilliseconds(100), .awake) catch |err2| { 585 log.err("Failed to do a mini recovery sleep: {}", .{err2}); 586 }; 587 continue; 588 }; 589 590 now = timestamp(io); 591 var closed_conn = false; 592 var retired_http: List(Conn(WSH)) = .{}; 593 594 // Defer signal handling (which disowns/frees handed-over 595 // connections) until the whole event batch is drained. epoll can 596 // return a .signal and a .recv for the same connection in one 597 // batch; processing the signal first would free the connection, 598 // and the later .recv would then dereference freed memory in 599 // getState(). By deferring, every .recv runs while its connection 600 // is still alive, sees the .handover state set before the signal, 601 // and skips it; the free happens safely afterward. 602 var has_signal = false; 603 604 while (it.next()) |event| { 605 switch (event) { 606 .accept => self.accept(listener, now) catch |err| { 607 log.err("Failed to accept connection: {}", .{err}); 608 io.sleep(.fromMilliseconds(5), .awake) catch |err2| { 609 log.err("Failed to do a mini recovery sleep: {}", .{err2}); 610 }; 611 }, 612 // Signal handling can recycle Conn storage. Defer it 613 // until every recv pointer in this batch is consumed. 614 .signal => has_signal = true, 615 .recv => |conn| switch (conn.protocol) { 616 .http => |http_conn| { 617 switch (http_conn.getState()) { 618 .request, .keepalive => {}, 619 .active, .handover => { 620 // we need to finish whatever we're doing 621 // before we can process more data (i.e. if 622 // the connection is being upgrade to websocket, 623 // this will be a websocket message.) 624 continue; 625 }, 626 } 627 628 // At this point, the connection is either in 629 // keepalive or request. Either way, we know no 630 // other thread is accessing the connection, so we 631 // can access _state directly. 632 633 const stream = http_conn.stream; 634 var reader = stream.reader(io, &.{}); // Request.State does its own buffering 635 const done = http_conn.req_state.parse(http_conn, &reader.interface) catch |err| { 636 // maybe a write fail or something, doesn't matter, we're closing the connection 637 requestError(http_conn, reader.err orelse err) catch {}; 638 639 // impossible to fail when false is passed 640 http_conn.requestDone(self.retain_allocated_bytes, false) catch unreachable; 641 self.loop.remove(conn); 642 conn.close(); 643 self.retireHttp(conn, &retired_http); 644 closed_conn = true; 645 continue; 646 }; 647 648 if (done == false) { 649 // A keepalive deadline only covers the idle 650 // gap between requests. As soon as bytes for 651 // the next request arrive, give that request 652 // the full request timeout. Keeping the old 653 // deadline can expire a split header/body in 654 // the middle of parsing. 655 if (http_conn.getState() == .keepalive) { 656 http_conn.timeout = now + self.timeout_request; 657 } 658 self.swapList(conn, .request); 659 continue; 660 } 661 662 self.swapList(conn, .active); 663 thread_pool.spawn(.{ self, now, conn }); 664 }, 665 .websocket => { 666 if (conn.acquireProcessing() == false) { 667 // Connection is already being processed. We need 668 // to wait for the current processing to complete. 669 // See the processing field in Conn 670 continue; 671 } 672 thread_pool.spawn(.{ self, now, conn }); 673 }, 674 // Stable tombstone for WebSocket userdata whose fd 675 // was closed on a pool thread. A queued kernel event 676 // may still carry this pointer; it must be ignored. 677 .retired => continue, 678 }, 679 .shutdown => return, 680 } 681 } 682 683 // Reclamation follows signal handling only after every event in 684 // this batch has stopped carrying a Conn pointer. 685 if (has_signal) self.processSignal(&closed_conn); 686 self.releaseRetiredHttp(&retired_http); 687 if (self.drainWebsocketGraveyard()) closed_conn = true; 688 const batch_size = thread_pool.batch_size; 689 if (batch_size > 0) { 690 thread_pool.flush(batch_size); 691 } 692 693 if (self.full and closed_conn) { 694 self.enableListener(listener); 695 } 696 } 697 } 698 699 fn swapList(self: *Self, conn: *Conn(WSH), new_state: HTTPConn.State) void { 700 const io = self.io; 701 const http_conn = conn.protocol.http; 702 http_conn._mut.lockUncancelable(io); 703 defer http_conn._mut.unlock(io); 704 705 switch (http_conn._state) { 706 .active => self.active_list.remove(io, conn), 707 .keepalive => self.keepalive_list.remove(io, conn), 708 .request => self.request_list.remove(conn), 709 .handover => self.handover_list.remove(io, conn), 710 } 711 712 http_conn.setState(new_state); 713 714 switch (new_state) { 715 .active => self.active_list.insert(io, conn), 716 .keepalive => self.keepalive_list.insert(io, conn), 717 .request => self.request_list.insert(conn), 718 .handover => self.handover_list.insert(io, conn), 719 } 720 } 721 722 fn accept(self: *Self, listener: posix.fd_t, now: u32) !void { 723 var len = self.len; 724 const max_conn = self.max_conn; 725 726 // Don't try to disconnect keepalive connections when len == max_conn. 727 // If you do, you'll get hard-to-reproduce segfaults. Why? 728 // This accept function is being called our event loop. If we call 729 // self.closeClose(keepalive_list.head) here, there's a chance that 730 // the connection which we're closing is in our poll event. 731 // And if it is, we'll segfault when we try to do anything with that 732 // connection after freeing it here. 733 while (true) { 734 if (len == max_conn) { 735 self.full = true; 736 self.loop.pauseAccept(listener) catch {}; 737 return; 738 } 739 var address: posix.Address = undefined; 740 var address_len: posix.socklen_t = @sizeOf(posix.Address); 741 742 const socket = posix.accept(listener, &address.any, &address_len, posix.CLOEXEC | posix.NONBLOCK) catch |err| { 743 // On BSD, REUSEPORT_LB means that only 1 worker should get notified 744 // of a connetion. On Linux, however, we only have REUSEPORT, which will 745 // notify all workers. However, we monitor the listener using EPOLLEXCLUSIVE. 746 // This makes it so that "one or more" workers receive it. 747 // In other words, no guarantee that there'll just be 1, but Linux will try 748 // to minimze the count. 749 // In the end, when we call accept, we might get a WouldBlock because 750 // Linux can wake up multiple epoll fds for a single connection. 751 return if (err == error.WouldBlock) {} else err; 752 }; 753 errdefer posix.close(socket); 754 metrics.connection(); 755 756 const socket_flags = try posix.fcntl(socket, posix.F.GETFL, 0); 757 const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true })); 758 std.debug.assert(socket_flags & nonblocking == nonblocking); 759 760 const conn = try self.conn_mem_pool.create(self.allocator); 761 errdefer self.conn_mem_pool.destroy(conn); 762 763 const ip_address = address.toIOAddress(); 764 765 const http_conn = try self.http_conn_pool.acquire(); 766 http_conn.request_count = 1; 767 http_conn._state = .request; 768 http_conn.handover = .unknown; 769 http_conn._io_mode = .nonblocking; 770 http_conn.address = ip_address; 771 http_conn.socket_flags = socket_flags; 772 http_conn.stream = .{ .socket = .{ .handle = socket, .address = ip_address } }; 773 http_conn.timeout = now + self.timeout_request; 774 775 self.len += 1; 776 conn.* = .{ 777 .next = null, 778 .prev = null, 779 .protocol = .{ .http = http_conn }, 780 }; 781 self.request_list.insert(conn); 782 errdefer { 783 conn.close(); 784 self.disown(conn); 785 } 786 787 try self.loop.monitorRead(conn); 788 len += 1; 789 } 790 } 791 792 fn processSignal(self: *Self, closed_bool: *bool) void { 793 const io = self.io; 794 const loop = &self.loop; 795 796 var hl = &self.handover_list; 797 798 // We take the handover list, and then re-initialize it. We do this 799 // under lock, so that any thread-pool waiting to handover will either 800 // make it into this snapshot, or into the new list. 801 // The benefit of this approach is that we don't ahve to hold the 802 // handover_list mutex for very long, just long enough to grab the 803 // head and re-initialize the inner list. 804 // This is ok, because _every_ connetion in the handover list is 805 // going to end up in another list (or closed) by the time we're 806 // done. 807 var c = blk: { 808 hl.mut.lockUncancelable(io); 809 defer hl.mut.unlock(io); 810 const head = hl.inner.head; 811 hl.inner = .{}; 812 break :blk head; 813 }; 814 815 // Every node in this snapshot was detached from handover_list when 816 // the inner list was re-initialized above. They must be released 817 // with releaseDetached, never disown: disown runs List.remove 818 // using the node's stale snapshot links, which grafts the rest of 819 // the snapshot back onto the live list. Those connections then get 820 // processed a second time on the next signal — reading a websocket 821 // union as http, double-releasing pooled HTTPConns, and ultimately 822 // interleaving responses across unrelated sockets. 823 while (c) |conn| { 824 c = conn.next; 825 const http_conn = conn.protocol.http; 826 switch (http_conn.handover) { 827 .close, .unknown => { 828 closed_bool.* = true; 829 // Keep close, event-loop removal, and storage recycling 830 // on the reactor thread, in that order. Closing on the 831 // handler thread can leave epoll armed with this Conn 832 // pointer after the allocator reuses its storage. 833 loop.remove(conn); 834 conn.close(); 835 self.releaseDetached(conn); 836 }, 837 .disown => { 838 // When res.disown() was called, we immediately removed 839 // the socket from the event loop. This is necessary 840 // because the new owner of the socket might close it 841 // before we get back here. 842 // https://github.com/karlseguin/http.zig/issues/129#issuecomment-3031411404 843 closed_bool.* = true; 844 self.releaseDetached(conn); 845 }, 846 .websocket => |ptr| { 847 if (comptime WSH == httpz.DummyWebsocketHandler) { 848 std.debug.print("Your httpz handler must have a `WebsocketHandler` declaration. This must be the same type passed to `httpz.upgradeWebsocket`. Closing the connection.\n", .{}); 849 closed_bool.* = true; 850 loop.remove(conn); 851 conn.close(); 852 self.releaseDetached(conn); 853 continue; 854 } 855 856 const hc: *ws.HandlerConn(WSH) = @ptrCast(@alignCast(ptr)); 857 loop.switchToOneShot(conn) catch { 858 metrics.internalError(); 859 closed_bool.* = true; 860 loop.remove(conn); 861 conn.close(); 862 self.releaseDetached(conn); 863 continue; 864 }; 865 866 self.http_conn_pool.release(http_conn); 867 conn.protocol = .{ .websocket = hc }; 868 conn.next = null; 869 conn.prev = null; 870 }, 871 .keepalive => unreachable, 872 } 873 } 874 } 875 876 fn releaseRetiredHttp(self: *Self, retired: *List(Conn(WSH))) void { 877 var conn = retired.head; 878 while (conn) |c| { 879 conn = c.next; 880 self.releaseDetached(c); 881 } 882 retired.* = .{}; 883 } 884 885 fn drainWebsocketGraveyard(self: *Self) bool { 886 var g = blk: { 887 const gl = &self.ws_graveyard; 888 gl.mut.lockUncancelable(self.io); 889 defer gl.mut.unlock(self.io); 890 const head = gl.inner.head; 891 gl.inner = .{}; 892 break :blk head; 893 }; 894 const released = g != null; 895 while (g) |conn| { 896 g = conn.next; 897 const hc = conn.protocol.websocket; 898 // The websocket worker has already closed the socket. Closing 899 // removes its kqueue/epoll registration; deleting by numeric fd 900 // here is unsafe because accept may already have reused that fd 901 // for another connection. In that race, this DEL removed the new 902 // connection's read monitor and left it stuck in request_list. 903 // More importantly, close happens on another thread, so the 904 // reactor cannot prove that a readiness notification carrying 905 // this userdata pointer is not still queued beyond the current 906 // epoll_wait batch. Never recycle websocket Conn userdata: an 907 // old notification must continue to point at retired websocket 908 // state, never at a newly accepted HTTP connection (the ABA 909 // that cross-wired responses in production). MemoryPool.deinit 910 // releases these tiny stable-lifetime nodes at server shutdown. 911 self.websocket.cleanupConn(hc); 912 conn.protocol = .{ .retired = {} }; 913 self.len -= 1; 914 } 915 return released; 916 } 917 918 // Entry-point of our thread pool. `thread_buf` is a thread-specific buffer 919 // that we are free to use as needed. 920 // Currently, for an HTTP connection, this is only called when we have a 921 // full HTTP request ready to process. For an WebSocket packet, it's called 922 // when we have data available - there may or may not be a full message ready 923 pub fn processData(self: *Self, now: u32, conn: *Conn(WSH), thread_buf: []u8) void { 924 switch (conn.protocol) { 925 .http => |http_conn| self.processHTTPData(now, conn, thread_buf, http_conn), 926 .websocket => |hc| self.processWebsocketData(conn, thread_buf, hc), 927 .retired => {}, 928 } 929 } 930 931 pub fn processHTTPData(self: *Self, now: u32, conn: *Conn(WSH), thread_buf: []u8, http_conn: *HTTPConn) void { 932 metrics.request(); 933 http_conn.request_count += 1; 934 self.server.handleRequest(http_conn, thread_buf); 935 936 var handover = http_conn.handover; 937 http_conn.requestDone(self.retain_allocated_bytes, handover == .keepalive or handover == .websocket) catch { 938 // This means we failed to put the connection into 939 // nonblocking mode. Rare, but safer to close the connection 940 // at this point. 941 handover = .close; 942 }; 943 944 switch (handover) { 945 .keepalive => { 946 http_conn.timeout = now + self.timeout_keepalive; 947 self.swapList(conn, .keepalive); 948 return; 949 }, 950 .close, .unknown => { 951 // Closing the socket (which removes it from epoll/kqueue) is 952 // deferred to processSignal on the event-loop thread. Closing 953 // here, on a worker thread, raced epoll_wait: the fd could 954 // stay armed past the point where disown() recycled the Conn, 955 // so a later batch delivered a .recv carrying a pointer to 956 // freed memory (getState on a null/recycled HTTPConn). The 957 // signal-vs-recv-in-one-batch case this used to guard against 958 // is now handled by deferring signal processing to the end of 959 // the event batch plus the getState() .handover check in recv. 960 }, 961 .websocket, .disown => {}, 962 } 963 self.swapList(conn, .handover); 964 self.loop.signal() catch |err| log.err("failed to signal worker: {}", .{err}); 965 } 966 967 pub fn processWebsocketData(self: *Self, conn: *Conn(WSH), thread_buf: []u8, hc: *ws.HandlerConn(WSH)) void { 968 var ws_conn = &hc.conn; 969 const success = self.websocket.worker.dataAvailable(hc, thread_buf); 970 if (success == false) { 971 ws_conn.close(.{ .code = 4997, .reason = "wsz" }) catch {}; 972 return self.wsConnDied(conn, hc); 973 } 974 if (ws_conn.isClosed()) { 975 return self.wsConnDied(conn, hc); 976 } 977 978 // EPOLLONESHOT must not be re-armed while `processing` is still 979 // held. A readable event in that window would consume the one-shot, 980 // fail acquireProcessing(), and leave the websocket permanently 981 // unarmed. Release first so the event can dispatch another worker. 982 conn.releaseProcessing(); 983 self.loop.rearmRead(conn) catch |err| { 984 log.debug("({f}) failed to add read event monitor: {}", .{ ws_conn.address, err }); 985 ws_conn.close(.{ .code = 4998, .reason = "wsz" }) catch {}; 986 return self.wsConnDied(conn, hc); 987 }; 988 } 989 990 // The websocket connection is dead and its socket has been closed. Hand 991 // both the websocket state and Conn node to the reactor for reclamation 992 // after the current kernel-event batch no longer carries its pointer. 993 // Callers must not touch the node after parking it here. A terminal read 994 // keeps `processing` held; a failed re-arm has already released it but 995 // cannot produce another event because the one-shot remains unarmed. 996 fn wsConnDied(self: *Self, conn: *Conn(WSH), hc: *ws.HandlerConn(WSH)) void { 997 _ = hc; 998 self.ws_graveyard.insert(self.io, conn); 999 self.loop.signal() catch |err| log.err("failed to signal worker: {}", .{err}); 1000 } 1001 1002 fn disown(self: *Self, conn: *Conn(WSH)) void { 1003 const http_conn = conn.protocol.http; 1004 self.detachHttp(conn); 1005 self.len -= 1; 1006 self.http_conn_pool.release(http_conn); 1007 self.conn_mem_pool.destroy(conn); 1008 } 1009 1010 // Remove an HTTP connection from its state list without recycling its 1011 // Conn/HTTPConn storage. Event-loop callers use this when the current 1012 // wait() batch may still contain another event carrying the Conn pointer. 1013 fn retireHttp(self: *Self, conn: *Conn(WSH), retired: *List(Conn(WSH))) void { 1014 self.detachHttp(conn); 1015 conn.protocol.http.setState(.handover); 1016 retired.insert(conn); 1017 } 1018 1019 fn detachHttp(self: *Self, conn: *Conn(WSH)) void { 1020 const io = self.io; 1021 const http_conn = conn.protocol.http; 1022 switch (http_conn._state) { 1023 .request => self.request_list.remove(conn), 1024 .handover => self.handover_list.remove(io, conn), 1025 .keepalive => self.keepalive_list.remove(io, conn), 1026 .active => unreachable, 1027 } 1028 } 1029 1030 // Enforces timeouts, and returns when the next timeout should be checked. 1031 fn prepareToWait(self: *Self, now: u32) struct { bool, ?i32 } { 1032 const request_timed_out, const request_count, const request_timeout = collectTimedOut(&self.request_list, now); 1033 1034 const io = self.io; 1035 const keepalive_timed_out, const keepalive_count, const keepalive_timeout = blk: { 1036 const list = &self.keepalive_list; 1037 list.mut.lockUncancelable(io); 1038 defer list.mut.unlock(io); 1039 break :blk collectTimedOut(&list.inner, now); 1040 }; 1041 1042 var closed = false; 1043 if (request_count > 0) { 1044 closed = true; 1045 self.closeList(request_timed_out); 1046 metrics.timeoutRequest(request_count); 1047 } 1048 if (keepalive_count > 0) { 1049 closed = true; 1050 self.closeList(keepalive_timed_out); 1051 metrics.timeoutKeepalive(keepalive_count); 1052 } 1053 1054 if (request_timeout == null and keepalive_timeout == null) { 1055 return .{ closed, null }; 1056 } 1057 1058 const next = @min(request_timeout orelse MAX_TIMEOUT, keepalive_timeout orelse MAX_TIMEOUT); 1059 if (next < now) { 1060 // can happen if a socket was just about to timeout when prepareToWait 1061 // was called 1062 return .{ closed, 1 }; 1063 } 1064 1065 return .{ closed, @intCast(next - now) }; 1066 } 1067 1068 // lists are ordered from soonest to timeout to last. Detach and return 1069 // the timed-out prefix; callers own the returned nodes and must not try 1070 // to remove them from the original list again. 1071 fn collectTimedOut(list: *List(Conn(WSH)), now: u32) struct { List(Conn(WSH)), usize, ?u32 } { 1072 var count: usize = 0; 1073 var conn = list.head; 1074 1075 while (conn) |c| { 1076 const timeout = c.protocol.http.timeout; 1077 if (timeout > now) { 1078 return .{ detachPrefix(Conn(WSH), list, count), count, timeout }; 1079 } 1080 count += 1; 1081 conn = c.next; 1082 } 1083 return .{ detachPrefix(Conn(WSH), list, count), count, null }; 1084 } 1085 1086 fn closeList(self: *Self, list: List(Conn(WSH))) void { 1087 var conn = list.head; 1088 while (conn) |c| { 1089 conn = c.next; 1090 // Timeout reclamation follows the same ownership rule as a 1091 // handler-requested close: the reactor must synchronously 1092 // remove the still-open descriptor before either the fd or 1093 // its Conn storage can be reused. Relying on close(2)'s 1094 // implicit epoll/kqueue cleanup leaves a queued event carrying 1095 // this pointer racing pool reuse under keepalive expiry. 1096 self.loop.remove(c); 1097 c.close(); 1098 // request_list may contain a partially parsed request. Never 1099 // return that HTTPConn to the pool with its parser still 1100 // carrying the old method/path/body expectation: the next 1101 // accepted socket would otherwise be parsed as the remainder 1102 // of the timed-out request. 1103 c.protocol.http.requestDone(self.retain_allocated_bytes, false) catch unreachable; 1104 self.releaseDetached(c); 1105 } 1106 } 1107 1108 fn releaseDetached(self: *Self, conn: *Conn(WSH)) void { 1109 const http_conn = conn.protocol.http; 1110 self.len -= 1; 1111 self.http_conn_pool.release(http_conn); 1112 self.conn_mem_pool.destroy(conn); 1113 } 1114 1115 fn shutdownList(self: *Self, list: *List(Conn(WSH))) void { 1116 const allocator = self.allocator; 1117 var conn = list.head; 1118 while (conn) |c| { 1119 conn = c.next; 1120 const http_conn = c.protocol.http; 1121 posix.close(http_conn.stream.socket.handle); 1122 http_conn.deinit(allocator); 1123 } 1124 } 1125 1126 fn shutdownConcurrentList(self: *Self, list: *ConcurrentList(Conn(WSH))) void { 1127 const io = self.io; 1128 list.mut.lockUncancelable(io); 1129 defer list.mut.unlock(io); 1130 self.shutdownList(&list.inner); 1131 } 1132 1133 inline fn enableListener(self: *Self, listener: posix.fd_t) void { 1134 self.full = false; 1135 self.loop.monitorAccept(listener) catch |err| log.err("Failed to enable monitor to listening socket: {}", .{err}); 1136 } 1137 }; 1138} 1139 1140pub fn List(comptime T: type) type { 1141 return struct { 1142 head: ?*T = null, 1143 tail: ?*T = null, 1144 1145 const Self = @This(); 1146 1147 pub fn insert(self: *Self, node: *T) void { 1148 if (self.tail) |tail| { 1149 tail.next = node; 1150 node.prev = tail; 1151 self.tail = node; 1152 } else { 1153 node.prev = null; 1154 self.head = node; 1155 self.tail = node; 1156 } 1157 node.next = null; 1158 } 1159 1160 pub fn remove(self: *Self, node: *T) void { 1161 if (node.prev) |prev| { 1162 prev.next = node.next; 1163 } else { 1164 self.head = node.next; 1165 } 1166 1167 if (node.next) |next| { 1168 next.prev = node.prev; 1169 } else { 1170 self.tail = node.prev; 1171 } 1172 1173 node.prev = null; 1174 node.next = null; 1175 } 1176 }; 1177} 1178 1179pub fn ConcurrentList(comptime T: type) type { 1180 return struct { 1181 inner: List(T) = .{}, 1182 mut: Io.Mutex = .init, 1183 1184 const Self = @This(); 1185 1186 pub fn insert(self: *Self, io: Io, node: *T) void { 1187 self.mut.lockUncancelable(io); 1188 defer self.mut.unlock(io); 1189 self.inner.insert(node); 1190 } 1191 1192 pub fn remove(self: *Self, io: Io, node: *T) void { 1193 self.mut.lockUncancelable(io); 1194 defer self.mut.unlock(io); 1195 self.inner.remove(node); 1196 } 1197 }; 1198} 1199 1200fn detachPrefix(comptime T: type, list: *List(T), count: usize) List(T) { 1201 if (count == 0) { 1202 return .{}; 1203 } 1204 1205 const head = list.head.?; 1206 var tail = head; 1207 for (1..count) |_| { 1208 tail = tail.next.?; 1209 } 1210 1211 const next = tail.next; 1212 tail.next = null; 1213 head.prev = null; 1214 1215 if (next) |n| { 1216 n.prev = null; 1217 list.head = n; 1218 } else { 1219 list.head = null; 1220 list.tail = null; 1221 } 1222 1223 return .{ .head = head, .tail = tail }; 1224} 1225 1226fn KQueue(comptime WSH: type) type { 1227 return struct { 1228 fd: i32, 1229 change_count: usize, 1230 change_buffer: [32]Kevent, 1231 event_list: [EVENT_BATCH_CAPACITY]Kevent, 1232 1233 const Self = @This(); 1234 const Kevent = posix.Kevent; 1235 1236 fn init() !Self { 1237 return .{ 1238 .fd = try posix.kqueue(), 1239 .change_count = 0, 1240 .change_buffer = undefined, 1241 .event_list = undefined, 1242 }; 1243 } 1244 1245 fn deinit(self: *const Self) void { 1246 posix.close(self.fd); 1247 } 1248 1249 fn start(self: *Self) !void { 1250 try self.change(1, 1, posix.system.EVFILT.USER, posix.system.EV.ADD | posix.system.EV.CLEAR, posix.system.NOTE.FFNOP); 1251 return self.change(2, 2, posix.system.EVFILT.USER, posix.system.EV.ADD | posix.system.EV.ONESHOT, posix.system.NOTE.FFNOP); 1252 } 1253 1254 fn stop(self: *Self) void { 1255 // called from an arbitrary thread, can't use change 1256 _ = posix.kevent(self.fd, &.{ 1257 .{ 1258 .ident = 2, 1259 .filter = posix.system.EVFILT.USER, 1260 .flags = 0, 1261 .fflags = posix.system.NOTE.TRIGGER, 1262 .data = 0, 1263 .udata = 2, 1264 }, 1265 }, &.{}, null) catch |err| { 1266 log.err("Failed to send stop signal: {}", .{err}); 1267 }; 1268 } 1269 1270 fn signal(self: *Self) !void { 1271 // called from thread pool thread, cant queue these in self.changes 1272 _ = try posix.kevent(self.fd, &.{.{ 1273 .ident = 1, 1274 .filter = posix.system.EVFILT.USER, 1275 .flags = 0, 1276 .fflags = posix.system.NOTE.TRIGGER, 1277 .data = 0, 1278 .udata = 1, 1279 }}, &.{}, null); 1280 } 1281 1282 fn monitorAccept(self: *Self, fd: posix.fd_t) !void { 1283 try self.change(fd, 0, posix.system.EVFILT.READ, posix.system.EV.ENABLE | posix.system.EV.ADD, 0); 1284 } 1285 1286 fn pauseAccept(self: *Self, fd: posix.fd_t) !void { 1287 try self.change(fd, 0, posix.system.EVFILT.READ, posix.system.EV.DISABLE, 0); 1288 } 1289 1290 fn monitorRead(self: *Self, conn: *Conn(WSH)) !void { 1291 try self.change(conn.getSocket(), @intFromPtr(conn), posix.system.EVFILT.READ, posix.system.EV.ADD | posix.system.EV.ENABLE, 0); 1292 } 1293 1294 fn remove(self: *Self, conn: *Conn(WSH)) void { 1295 _ = posix.kevent(self.fd, &.{.{ 1296 .ident = @intCast(conn.getSocket()), 1297 .filter = posix.system.EVFILT.READ, 1298 .flags = posix.system.EV.DELETE, 1299 .fflags = 0, 1300 .data = 0, 1301 .udata = 0, 1302 }}, &.{}, null) catch {}; 1303 } 1304 1305 fn rearmRead(self: *Self, conn: *Conn(WSH)) !void { 1306 if (conn.isProcessing()) return error.ConnectionStillProcessing; 1307 // called from the worker thread, can't use change_buffer 1308 // must remove then re-add for macOS 1309 const socket = conn.getSocket(); 1310 _ = try posix.kevent(self.fd, &.{ .{ 1311 .ident = @intCast(socket), 1312 .filter = posix.system.EVFILT.READ, 1313 .flags = posix.system.EV.DELETE, 1314 .fflags = 0, 1315 .data = 0, 1316 .udata = 0, 1317 }, .{ 1318 .ident = @intCast(socket), 1319 .filter = posix.system.EVFILT.READ, 1320 .flags = posix.system.EV.ADD | posix.system.EV.DISPATCH, 1321 .fflags = 0, 1322 .data = 0, 1323 .udata = @intFromPtr(conn), 1324 } }, &.{}, null); 1325 } 1326 1327 fn switchToOneShot(self: *Self, conn: *Conn(WSH)) !void { 1328 // From the Kqueue docs, you'd think you can just re-add the socket with EV.DISPATCH to enable 1329 // dispatching. But that _does not_ work on MacOS. Removing and Re-adding does. 1330 const socket = conn.getSocket(); 1331 try self.change(socket, 0, posix.system.EVFILT.READ, posix.system.EV.DELETE, 0); 1332 try self.change(socket, @intFromPtr(conn), posix.system.EVFILT.READ, posix.system.EV.ADD | posix.system.EV.DISPATCH, 0); 1333 } 1334 1335 fn change(self: *Self, fd: posix.fd_t, data: usize, filter: i16, flags: u16, fflags: u16) !void { 1336 var change_count = self.change_count; 1337 var change_buffer = &self.change_buffer; 1338 1339 if (change_count == change_buffer.len) { 1340 // calling this with an empty event_list will return immediate 1341 _ = try posix.kevent(self.fd, change_buffer, &.{}, null); 1342 change_count = 0; 1343 } 1344 change_buffer[change_count] = .{ 1345 .ident = @intCast(fd), 1346 .filter = filter, 1347 .flags = flags, 1348 .fflags = fflags, 1349 .data = 0, 1350 .udata = data, 1351 }; 1352 self.change_count = change_count + 1; 1353 } 1354 1355 fn wait(self: *Self, timeout_sec: ?i32) !Iterator { 1356 const event_list = &self.event_list; 1357 const timeout: ?posix.timespec = if (timeout_sec) |ts| posix.timespec{ .sec = ts, .nsec = 0 } else null; 1358 const event_count = try posix.kevent(self.fd, self.change_buffer[0..self.change_count], event_list, if (timeout) |ts| &ts else null); 1359 self.change_count = 0; 1360 1361 return .{ 1362 .index = 0, 1363 .loop = self, 1364 .events = event_list[0..event_count], 1365 }; 1366 } 1367 1368 const Iterator = struct { 1369 loop: *Self, 1370 index: usize, 1371 events: []Kevent, 1372 1373 fn next(self: *Iterator) ?Event(WSH) { 1374 const index = self.index; 1375 const events = self.events; 1376 if (index == events.len) { 1377 return null; 1378 } 1379 1380 const event = &self.events[index]; 1381 self.index = index + 1; 1382 1383 switch (event.udata) { 1384 0 => return .{ .accept = {} }, 1385 1 => return .{ .signal = {} }, 1386 2 => return .{ .shutdown = {} }, 1387 else => |nptr| return .{ .recv = @ptrFromInt(nptr) }, 1388 } 1389 } 1390 }; 1391 }; 1392} 1393 1394fn EPoll(comptime WSH: type) type { 1395 return struct { 1396 fd: i32, 1397 close_fd: i32, 1398 event_fd: i32, 1399 event_list: [EVENT_BATCH_CAPACITY]EpollEvent, 1400 1401 const Self = @This(); 1402 const linux = std.os.linux; 1403 const EpollEvent = linux.epoll_event; 1404 1405 fn init() !Self { 1406 const close_fd = try posix.eventfd(0, std.os.linux.EFD.CLOEXEC | std.os.linux.EFD.NONBLOCK); 1407 errdefer posix.close(close_fd); 1408 1409 const event_fd = try posix.eventfd(0, std.os.linux.EFD.CLOEXEC | std.os.linux.EFD.NONBLOCK); 1410 errdefer posix.close(event_fd); 1411 1412 return .{ 1413 .close_fd = close_fd, 1414 .event_fd = event_fd, 1415 .event_list = undefined, 1416 .fd = try posix.epoll_create1(0), 1417 }; 1418 } 1419 1420 fn deinit(self: *const Self) void { 1421 posix.close(self.close_fd); 1422 posix.close(self.event_fd); 1423 posix.close(self.fd); 1424 } 1425 1426 fn start(self: *Self) !void { 1427 { 1428 var event = linux.epoll_event{ 1429 .data = .{ .ptr = 2 }, 1430 .events = linux.EPOLL.IN, 1431 }; 1432 try posix.epoll_ctl(self.fd, linux.EPOLL.CTL_ADD, self.close_fd, &event); 1433 } 1434 1435 { 1436 var event = linux.epoll_event{ 1437 .data = .{ .ptr = 1 }, 1438 .events = linux.EPOLL.IN | linux.EPOLL.ET, 1439 }; 1440 try posix.epoll_ctl(self.fd, linux.EPOLL.CTL_ADD, self.event_fd, &event); 1441 } 1442 } 1443 1444 fn stop(self: *Self) void { 1445 // eventfd requires exactly 8 bytes; use u64 so 32-bit (e.g. Linux ARM) does not get EINVAL. 1446 const increment: u64 = 1; 1447 _ = posix.write(self.close_fd, std.mem.asBytes(&increment)) catch |err| { 1448 log.err("Failed to write to closefd: {}", .{err}); 1449 }; 1450 } 1451 1452 fn signal(self: *const Self) !void { 1453 // eventfd requires exactly 8 bytes; use u64 so 32-bit (e.g. Linux ARM) does not get EINVAL. 1454 const increment: u64 = 1; 1455 _ = try posix.write(self.event_fd, std.mem.asBytes(&increment)); 1456 } 1457 1458 fn monitorAccept(self: *Self, fd: posix.fd_t) !void { 1459 var event = linux.epoll_event{ .events = linux.EPOLL.IN | linux.EPOLL.EXCLUSIVE, .data = .{ .ptr = 0 } }; 1460 return posix.epoll_ctl(self.fd, linux.EPOLL.CTL_ADD, fd, &event); 1461 } 1462 1463 fn pauseAccept(self: *Self, fd: posix.fd_t) !void { 1464 return posix.epoll_ctl(self.fd, linux.EPOLL.CTL_DEL, fd, null); 1465 } 1466 1467 fn monitorRead(self: *Self, conn: *Conn(WSH)) !void { 1468 var event = linux.epoll_event{ 1469 .data = .{ .ptr = @intFromPtr(conn) }, 1470 .events = linux.EPOLL.IN | linux.EPOLL.RDHUP, 1471 }; 1472 return posix.epoll_ctl(self.fd, linux.EPOLL.CTL_ADD, conn.getSocket(), &event); 1473 } 1474 1475 fn remove(self: *Self, conn: *Conn(WSH)) void { 1476 posix.epoll_ctl(self.fd, linux.EPOLL.CTL_DEL, conn.getSocket(), null) catch {}; 1477 } 1478 1479 fn rearmRead(self: *Self, conn: *Conn(WSH)) !void { 1480 if (conn.isProcessing()) return error.ConnectionStillProcessing; 1481 var event = linux.epoll_event{ 1482 .data = .{ .ptr = @intFromPtr(conn) }, 1483 .events = linux.EPOLL.IN | linux.EPOLL.RDHUP | linux.EPOLL.ONESHOT, 1484 }; 1485 return posix.epoll_ctl(self.fd, linux.EPOLL.CTL_MOD, conn.getSocket(), &event); 1486 } 1487 1488 fn switchToOneShot(self: *Self, conn: *Conn(WSH)) !void { 1489 return self.rearmRead(conn); 1490 } 1491 1492 fn wait(self: *Self, timeout_sec: ?i32) !Iterator { 1493 const event_list = &self.event_list; 1494 var timeout: i32 = -1; 1495 if (timeout_sec) |sec| { 1496 if (sec > 2147483) { 1497 // max supported timeout by epoll_wait. 1498 timeout = 2147483647; 1499 } else { 1500 timeout = sec * 1000; 1501 } 1502 } 1503 1504 const event_count = posix.epoll_wait(self.fd, event_list, timeout); 1505 return .{ 1506 .index = 0, 1507 .event_fd = self.event_fd, 1508 .events = event_list[0..event_count], 1509 }; 1510 } 1511 1512 const Iterator = struct { 1513 index: usize, 1514 event_fd: i32, 1515 events: []EpollEvent, 1516 1517 fn next(self: *Iterator) ?Event(WSH) { 1518 const index = self.index; 1519 const events = self.events; 1520 if (index == events.len) { 1521 return null; 1522 } 1523 self.index = index + 1; 1524 1525 switch (events[index].data.ptr) { 1526 0 => return .{ .accept = {} }, 1527 1 => return .{ .signal = {} }, 1528 2 => return .{ .shutdown = {} }, 1529 else => |nptr| return .{ .recv = @ptrFromInt(nptr) }, 1530 } 1531 } 1532 }; 1533 }; 1534} 1535 1536fn Event(comptime WSH: type) type { 1537 return union(enum) { 1538 accept: void, 1539 signal: void, 1540 shutdown: void, 1541 recv: *Conn(WSH), 1542 }; 1543} 1544 1545// There's some shared logic between the NonBlocking and Blocking workers. 1546// Whatever we can de-duplicate, goes here. 1547const HTTPConnPool = struct { 1548 io: Io, 1549 mut: Io.Mutex, 1550 conns: []*HTTPConn, 1551 available: usize, 1552 allocator: Allocator, 1553 config: *const Config, 1554 buffer_pool: *BufferPool, 1555 retain_allocated_bytes: usize, 1556 http_mem_pool_mut: Io.Mutex, 1557 http_mem_pool: std.heap.MemoryPool(HTTPConn), 1558 1559 // we erase the type because we don't want Conn, and therefore Request and 1560 // Response to have to carry this type around. This is all about making the 1561 // API cleaner. 1562 websocket: *anyopaque, 1563 1564 // Unfortunately, in some special cases, we need to interact with the loop 1565 // directly from a worker thread. Rather than store a *Loop, which could be 1566 // misused (not all of the methods are safe to call from the non-worker 1567 // thread), we store the loop's FD which is more opaque. 1568 loop: i32, 1569 1570 fn init(io: Io, allocator: Allocator, buffer_pool: *BufferPool, websocket: *anyopaque, loop: i32, config: *const Config) !HTTPConnPool { 1571 const min = config.workers.min_conn orelse @min(config.workers.max_conn orelse 64, 64); 1572 1573 var conns = try allocator.alloc(*HTTPConn, min); 1574 errdefer allocator.free(conns); 1575 1576 var http_mem_pool: std.heap.MemoryPool(HTTPConn) = .empty; 1577 errdefer http_mem_pool.deinit(allocator); 1578 1579 var initialized: usize = 0; 1580 errdefer { 1581 for (0..initialized) |i| { 1582 conns[i].deinit(allocator); 1583 } 1584 } 1585 1586 for (0..min) |i| { 1587 const conn = try http_mem_pool.create(allocator); 1588 conn.* = try HTTPConn.init(io, allocator, buffer_pool, websocket, loop, config); 1589 1590 conns[i] = conn; 1591 initialized += 1; 1592 } 1593 1594 return .{ 1595 .io = io, 1596 .mut = .init, 1597 .loop = loop, 1598 .conns = conns, 1599 .config = config, 1600 .available = min, 1601 .websocket = websocket, 1602 .allocator = allocator, 1603 .buffer_pool = buffer_pool, 1604 .http_mem_pool = http_mem_pool, 1605 .http_mem_pool_mut = .init, 1606 .retain_allocated_bytes = config.workers.retain_allocated_bytes orelse 4096, 1607 }; 1608 } 1609 1610 fn deinit(self: *HTTPConnPool) void { 1611 const allocator = self.allocator; 1612 // the rest of the conns are "checked out" and owned by the Manager 1613 // whichi will free them. 1614 for (self.conns[0..self.available]) |conn| { 1615 conn.deinit(allocator); 1616 } 1617 allocator.free(self.conns); 1618 self.http_mem_pool.deinit(allocator); 1619 } 1620 1621 fn acquire(self: *HTTPConnPool) !*HTTPConn { 1622 const io = self.io; 1623 const conns = self.conns; 1624 1625 self.lock(); 1626 const available = self.available; 1627 if (available == 0) { 1628 self.unlock(); 1629 1630 try self.http_mem_pool_mut.lock(io); 1631 const conn = try self.http_mem_pool.create(self.allocator); 1632 self.http_mem_pool_mut.unlock(io); 1633 errdefer { 1634 self.http_mem_pool_mut.lockUncancelable(io); 1635 self.http_mem_pool.destroy(conn); 1636 self.http_mem_pool_mut.unlock(io); 1637 } 1638 1639 conn.* = try HTTPConn.init(io, self.allocator, self.buffer_pool, self.websocket, self.loop, self.config); 1640 return conn; 1641 } 1642 1643 const index = available - 1; 1644 const conn = conns[index]; 1645 self.available = index; 1646 self.unlock(); 1647 return conn; 1648 } 1649 1650 fn release(self: *HTTPConnPool, conn: *HTTPConn) void { 1651 const conns = self.conns; 1652 self.lock(); 1653 const available = self.available; 1654 if (available == conns.len) { 1655 self.unlock(); 1656 conn.deinit(self.allocator); 1657 1658 const io = self.io; 1659 self.http_mem_pool_mut.lockUncancelable(io); 1660 self.http_mem_pool.destroy(conn); 1661 self.http_mem_pool_mut.unlock(io); 1662 return; 1663 } 1664 1665 conns[available] = conn; 1666 self.available = available + 1; 1667 self.unlock(); 1668 } 1669 1670 // don't need thread safety in nonblocking 1671 fn lock(self: *HTTPConnPool) void { 1672 if (comptime httpz.blockingMode()) { 1673 self.mut.lockUncancelable(self.io); 1674 } 1675 } 1676 1677 // don't need thread safety in nonblocking 1678 fn unlock(self: *HTTPConnPool) void { 1679 if (comptime httpz.blockingMode()) { 1680 self.mut.unlock(self.io); 1681 } 1682 } 1683}; 1684 1685pub fn Conn(comptime WSH: type) type { 1686 return struct { 1687 protocol: union(enum) { 1688 http: *HTTPConn, 1689 websocket: *ws.HandlerConn(WSH), 1690 retired: void, 1691 }, 1692 1693 // Node in a List(WSH). List is [obviously] intrusive. 1694 next: ?*Conn(WSH), 1695 prev: ?*Conn(WSH), 1696 1697 // Used exclusively in the websocket path. In the HTTP Path, the 1698 // HTTPConn's state is used to prevent 2 requests on the same socket 1699 // from being processed at the same time. This is 100% necessary because 1700 // our event loop will keep emitting events for new data. For HTTP, this 1701 // is OK, because a "health" flow is REQ->RES - there shouldn't be 1702 // multiple inflight requests. NOT using ONESHOT/EV_DISPATCH is more 1703 // efficient because it avoids a bunch of system calls. 1704 // But for WebSockets, it's normal to have multiple incoming messages. 1705 // So we switch the event notifier to be ONESHOT/DISPATCH. And, that 1706 // SHOULD be enough to stop 2 concurrent messages from being processed 1707 // at the same time. And on Linux, it seems to work fine. But on MacOS, 1708 // there's a window where we might get 2 messages across 2 different 1709 // wait calls as we're switching to DISPATCH. So this guard is there just 1710 // for thar narrow window. 1711 processing: bool = false, 1712 1713 const Self = @This(); 1714 1715 fn close(self: *Self) void { 1716 switch (self.protocol) { 1717 .http => |http_conn| posix.close(http_conn.stream.socket.handle), 1718 .websocket => |hc| hc.conn.close(.{}) catch {}, 1719 .retired => {}, 1720 } 1721 } 1722 1723 pub fn getSocket(self: Self) posix.fd_t { 1724 return switch (self.protocol) { 1725 .http => |hc| hc.stream.socket.handle, 1726 .websocket => |hc| hc.socket, 1727 .retired => unreachable, 1728 }; 1729 } 1730 1731 pub fn acquireProcessing(self: *Self) bool { 1732 // returns true if it was previously false 1733 return @atomicRmw(bool, &self.processing, .Xchg, true, .monotonic) == false; 1734 } 1735 1736 pub fn releaseProcessing(self: *Self) void { 1737 return @atomicStore(bool, &self.processing, false, .release); 1738 } 1739 1740 pub fn isProcessing(self: *const Self) bool { 1741 return @atomicLoad(bool, &self.processing, .acquire); 1742 } 1743 }; 1744} 1745 1746// Wraps a socket with a application-specific details, such as information needed 1747// to manage the connection's lifecycle (e.g. timeouts). Conns are placed in a 1748// linked list, hence the next/prev. 1749// 1750// The Conn can be re-used (as parf of a pool), either for keepalive, or for 1751// completely different tcp connections. From the Conn's point of view, there's 1752// no difference, just need to `reset` between each request. 1753// 1754// The Conn contains request and response state information necessary to operate 1755// in nonblocking mode. A pointer to the conn is the userdata passed to epoll/kqueue. 1756// Should only be created through the worker's HTTPConnPool 1757pub const HTTPConn = struct { 1758 // A connection can be in one of four states. This mostly corresponds to which 1759 // of the worker's lists it is in. 1760 // - active: There's a thread in the thread pool serving a request 1761 // 1762 // - handover: The threadpool is done with the request and wants to transfer 1763 // control back to the worker. 1764 // 1765 // - request: We have some data, but not enough to service the request. i.e. 1766 // we don't have a complete header or missing [part of] the body. 1767 // Connections here are subject to the request timeout. 1768 // 1769 // - keepalive: Conenction is between requests. We're waiting for the start 1770 // of the new request. connections here are suject to the keepalive timeout. 1771 const State = enum { 1772 active, 1773 request, 1774 handover, 1775 keepalive, 1776 }; 1777 1778 pub const Handover = union(enum) { 1779 disown, 1780 close, 1781 unknown, 1782 keepalive, 1783 websocket: *anyopaque, 1784 }; 1785 1786 pub const IOMode = enum { 1787 blocking, 1788 nonblocking, 1789 }; 1790 1791 io: Io, 1792 1793 // can be concurrently accessed, use getState 1794 _state: State, 1795 1796 _mut: Io.Mutex, 1797 1798 _io_mode: IOMode, 1799 1800 handover: Handover, 1801 1802 // unix timestamp (seconds) where this connection should timeout 1803 timeout: u32, 1804 1805 // number of requests made on this connection (within a keepalive session) 1806 request_count: u64, 1807 1808 stream: Io.net.Stream, 1809 address: Io.net.IpAddress, 1810 socket_flags: usize, 1811 1812 // Data needed to parse a request. This contains pre-allocated memory, e.g. 1813 // as a read buffer and to store parsed headers. It also contains the state 1814 // necessary to parse the request over successive nonblocking read calls. 1815 req_state: Request.State, 1816 1817 // Data needed to create the response. This contains pre-allocate memory, .e. 1818 // header buffer to write the buffer. It also contains the state necessary 1819 // to write the response over successive nonblocking write calls. 1820 res_state: Response.State, 1821 1822 // Memory that is needed for the lifetime of a request, specifically from the 1823 // point where the request is parsed to after the response is sent, can be 1824 // allocated in this arena. An allocator for this arena is available to the 1825 // application as req.arena and res.arena. 1826 req_arena: std.heap.ArenaAllocator, 1827 1828 // Memory that is needed for the lifetime of the connection. In most cases 1829 // the connection outlives the request for two reasons: keepalive and the 1830 // fact that we keepa pool of connections. 1831 conn_arena: *std.heap.ArenaAllocator, 1832 1833 // This is our ws.Worker(WSH) but the type is erased so that Conn isn't 1834 // a generic. We don't want Conn to be a generic, because we don't want Response 1835 // to be generics since that would make using the library unecessarily complex 1836 // (especially since not everyone cares about websockets). 1837 ws_worker: *anyopaque, 1838 1839 // Unfortunately, in some special cases, we need to interact with the loop 1840 // directly from a worker thread. Rather than store a *Loop, which could be 1841 // misused (not all of the methods are safe to call from the non-worker 1842 // thread), we store the loop's FD which is more opaque. 1843 loop: i32, 1844 1845 fn init(io: Io, allocator: Allocator, buffer_pool: *BufferPool, ws_worker: *anyopaque, loop: i32, config: *const Config) !HTTPConn { 1846 const conn_arena = try allocator.create(std.heap.ArenaAllocator); 1847 errdefer allocator.destroy(conn_arena); 1848 1849 conn_arena.* = std.heap.ArenaAllocator.init(allocator); 1850 errdefer conn_arena.deinit(); 1851 1852 const req_state = try Request.State.init(conn_arena.allocator(), buffer_pool, &config.request); 1853 const res_state = try Response.State.init(conn_arena.allocator(), &config.response); 1854 1855 var req_arena = std.heap.ArenaAllocator.init(allocator); 1856 errdefer req_arena.deinit(); 1857 1858 return .{ 1859 .io = io, 1860 .timeout = 0, 1861 ._mut = .init, 1862 ._state = .request, 1863 .handover = .unknown, 1864 .stream = undefined, 1865 .address = undefined, 1866 .loop = loop, 1867 .request_count = 0, 1868 .socket_flags = 0, 1869 .ws_worker = ws_worker, 1870 .req_state = req_state, 1871 .res_state = res_state, 1872 .req_arena = req_arena, 1873 .conn_arena = conn_arena, 1874 ._io_mode = if (comptime httpz.blockingMode()) .blocking else .nonblocking, 1875 }; 1876 } 1877 1878 pub fn getState(self: *const HTTPConn) State { 1879 return @atomicLoad(State, &self._state, .acquire); 1880 } 1881 1882 pub fn setState(self: *HTTPConn, state: State) void { 1883 return @atomicStore(State, &self._state, state, .release); 1884 } 1885 1886 pub fn deinit(self: *HTTPConn, allocator: Allocator) void { 1887 self.req_state.deinit(); 1888 self.req_arena.deinit(); 1889 self.conn_arena.deinit(); 1890 allocator.destroy(self.conn_arena); 1891 } 1892 1893 // This method is being called from a worker thread. Be careful what you 1894 // do here. 1895 // When a connection is disowned, we need to remove it from the 1896 // loop, since we aren't responsible for it anymore. However, we 1897 // can't wait until the normal signal flow to do that, because, for 1898 // all we know, the new owner of the socket will have closed the 1899 // connection by then. We need to remove the socket from the loop 1900 // synchronously when we're asked to disown it (as part of 1901 // res.disown()). 1902 pub fn disown(self: *HTTPConn) !void { 1903 if (comptime httpz.blockingMode()) { 1904 self.handover = .disown; 1905 return; 1906 } 1907 1908 const loop = self.loop; 1909 const socket = self.stream.socket.handle; 1910 switch (comptime loopType()) { 1911 .kqueue => { 1912 _ = try posix.kevent(loop, &.{ 1913 .{ 1914 .ident = @intCast(socket), 1915 .filter = posix.system.EVFILT.READ, 1916 .flags = posix.system.EV.DELETE, 1917 .fflags = 0, 1918 .data = 0, 1919 .udata = 0, 1920 }, 1921 }, &.{}, null); 1922 }, 1923 .epoll => { 1924 try posix.epoll_ctl(loop, std.os.linux.EPOLL.CTL_DEL, socket, null); 1925 }, 1926 } 1927 1928 // Transfer ownership only after the event loop has definitely stopped 1929 // retaining the Conn pointer. If deregistration fails, leaving the 1930 // handover unchanged makes the normal reactor close path own cleanup. 1931 self.handover = .disown; 1932 } 1933 1934 pub fn requestDone(self: *HTTPConn, retain_allocated_bytes: usize, revert_blocking: bool) !void { 1935 self.req_state.reset(); 1936 self.res_state.reset(); 1937 _ = self.req_arena.reset(.{ .retain_with_limit = retain_allocated_bytes }); 1938 if (revert_blocking) { 1939 try self.nonblockingMode(); 1940 } 1941 } 1942 1943 pub fn writeAll(self: *HTTPConn, data: []const u8) !void { 1944 var i: usize = 0; 1945 var blocking = false; 1946 1947 const socket = self.stream.socket.handle; 1948 1949 while (i < data.len) { 1950 const n = posix.write(socket, data[i..]) catch |err| switch (err) { 1951 error.WouldBlock => { 1952 try self.blockingMode(); 1953 blocking = true; 1954 continue; 1955 }, 1956 else => return err, 1957 }; 1958 1959 // shouldn't be posssible on a correct posix implementation 1960 // but let's assert to make sure 1961 std.debug.assert(n != 0); 1962 i += n; 1963 } 1964 } 1965 1966 pub fn writeAllIOVec(self: *HTTPConn, vec: []posix.iovec_const) !void { 1967 const socket = self.stream.socket.handle; 1968 1969 var i: usize = 0; 1970 while (true) { 1971 var n = posix.writev(socket, vec[i..]) catch |err| switch (err) { 1972 error.WouldBlock => { 1973 try self.blockingMode(); 1974 continue; 1975 }, 1976 else => return err, 1977 }; 1978 1979 while (n >= vec[i].len) { 1980 n -= vec[i].len; 1981 i += 1; 1982 if (i >= vec.len) { 1983 return; 1984 } 1985 } 1986 vec[i].base += n; 1987 vec[i].len -= n; 1988 } 1989 } 1990 1991 pub fn blockingMode(self: *HTTPConn) !void { 1992 if (comptime httpz.blockingMode() == true) { 1993 // When httpz is in blocking mode, than we always keep the socket in 1994 // blocking mode 1995 return; 1996 } 1997 if (self._io_mode == .blocking) { 1998 return; 1999 } 2000 _ = try posix.fcntl(self.stream.socket.handle, posix.F.SETFL, self.socket_flags & ~@as(u32, @bitCast(posix.O{ .NONBLOCK = true }))); 2001 self._io_mode = .blocking; 2002 } 2003 2004 pub fn nonblockingMode(self: *HTTPConn) !void { 2005 if (comptime httpz.blockingMode() == true) { 2006 // When httpz is in blocking mode, than we always keep the socket in 2007 // blocking mode 2008 return; 2009 } 2010 if (self._io_mode == .nonblocking) { 2011 return; 2012 } 2013 _ = try posix.fcntl(self.stream.socket.handle, posix.F.SETFL, self.socket_flags); 2014 self._io_mode = .nonblocking; 2015 } 2016}; 2017 2018pub fn timestamp(io: Io) u32 { 2019 return @intCast(Io.Timestamp.now(io, .awake).toSeconds()); 2020} 2021 2022fn initializeBufferPool(io: Io, allocator: Allocator, config: *const Config) !*BufferPool { 2023 const large_buffer_count = config.workers.large_buffer_count orelse blk: { 2024 if (comptime httpz.blockingMode()) { 2025 break :blk config.threadPoolCount(); 2026 } else { 2027 break :blk 16; 2028 } 2029 }; 2030 const large_buffer_size = config.workers.large_buffer_size orelse config.request.max_body_size orelse 65536; 2031 2032 const buffer_pool = try allocator.create(BufferPool); 2033 errdefer allocator.destroy(buffer_pool); 2034 2035 buffer_pool.* = try BufferPool.init(io, allocator, large_buffer_count, large_buffer_size); 2036 return buffer_pool; 2037} 2038 2039// Handles parsing errors. If this returns true, it means Conn has a body ready 2040// to write. In this case the worker (blocking or nonblocking) will want to send 2041// the response. If it returns false, the worker probably wants to close the connection. 2042// This function ensures that both Blocking and NonBlocking workers handle these 2043// errors with the same response 2044fn requestError(conn: *HTTPConn, err: anyerror) !void { 2045 const handle = conn.stream.socket.handle; 2046 switch (err) { 2047 error.HeaderTooBig => { 2048 metrics.invalidRequest(); 2049 return writeError(handle, 431, "Request header is too big"); 2050 }, 2051 error.UnknownMethod, error.InvalidRequestTarget, error.UnknownProtocol, error.UnsupportedProtocol, error.InvalidHeaderLine, error.InvalidHost, error.InvalidContentLength, error.InvalidTransferEncoding, error.InvalidChunkSize, error.InvalidChunkLine => { 2052 metrics.invalidRequest(); 2053 return writeError(handle, 400, "Invalid Request"); 2054 }, 2055 error.BodyTooBig => { 2056 metrics.bodyTooBig(); 2057 return writeError(handle, 413, "Request body is too big"); 2058 }, 2059 error.BrokenPipe, error.ConnectionClosed, error.ConnectionResetByPeer, error.EndOfStream => return, 2060 else => { 2061 log.err("server error: {}", .{err}); 2062 metrics.internalError(); 2063 return writeError(handle, 500, "Internal Server Error"); 2064 }, 2065 } 2066 return err; 2067} 2068 2069fn writeError(socket: posix.socket_t, comptime status: u16, comptime msg: []const u8) !void { 2070 const response = std.fmt.comptimePrint("HTTP/1.1 {d} \r\nConnection: Close\r\nContent-Length: {d}\r\n\r\n{s}", .{ status, msg.len, msg }); 2071 2072 var i: usize = 0; 2073 while (i < msg.len) { 2074 i += try posix.write(socket, response[i..]); 2075 } 2076} 2077 2078const LoopType = enum { 2079 kqueue, 2080 epoll, 2081}; 2082 2083fn loopType() LoopType { 2084 return switch (builtin.os.tag) { 2085 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => .kqueue, 2086 .linux => .epoll, 2087 else => unreachable, 2088 }; 2089} 2090 2091const t = @import("t.zig"); 2092test "HTTPConnPool" { 2093 var bp = try BufferPool.init(t.io, t.allocator, 2, 64); 2094 defer bp.deinit(); 2095 2096 var p = try HTTPConnPool.init(t.io, t.allocator, &bp, undefined, 0, &.{ 2097 .workers = .{ .min_conn = 2 }, 2098 .request = .{ .buffer_size = 64 }, 2099 }); 2100 defer p.deinit(); 2101 2102 const s1 = try p.acquire(); 2103 const s2 = try p.acquire(); 2104 const s3 = try p.acquire(); 2105 2106 try t.expectEqual(true, s1.req_state.buf.ptr != s2.req_state.buf.ptr); 2107 try t.expectEqual(true, s1.req_state.buf.ptr != s3.req_state.buf.ptr); 2108 try t.expectEqual(true, s2.req_state.buf.ptr != s3.req_state.buf.ptr); 2109 2110 p.release(s2); 2111 const s4 = try p.acquire(); 2112 try t.expectEqual(true, s4.req_state.buf.ptr == s2.req_state.buf.ptr); 2113 2114 p.release(s1); 2115 p.release(s3); 2116 p.release(s4); 2117} 2118 2119test "HTTPConn.disown preserves reactor ownership when deregistration fails" { 2120 if (httpz.blockingMode()) return; 2121 2122 const loop = switch (comptime loopType()) { 2123 .kqueue => try posix.kqueue(), 2124 .epoll => try posix.epoll_create1(0), 2125 }; 2126 defer posix.close(loop); 2127 2128 var bp = try BufferPool.init(t.io, t.allocator, 1, 64); 2129 defer bp.deinit(); 2130 2131 var p = try HTTPConnPool.init(t.io, t.allocator, &bp, undefined, loop, &.{ 2132 .workers = .{ .min_conn = 1 }, 2133 .request = .{ .buffer_size = 64 }, 2134 }); 2135 defer p.deinit(); 2136 2137 const conn = try p.acquire(); 2138 defer p.release(conn); 2139 conn.handover = .unknown; 2140 // This fresh event loop has no registration for stdin, so deleting it 2141 // deterministically exercises the deregistration failure path. 2142 conn.stream.socket.handle = 0; 2143 2144 if (conn.disown()) { 2145 return error.ExpectedDeregistrationFailure; 2146 } else |_| {} 2147 2148 switch (conn.handover) { 2149 .unknown => {}, 2150 else => return error.OwnershipTransferredAfterFailedDeregistration, 2151 } 2152} 2153 2154test "List: insert & remove" { 2155 var list = List(TestNode){}; 2156 try expectList(&.{}, list); 2157 2158 var n1 = TestNode{ .id = 1 }; 2159 list.insert(&n1); 2160 try expectList(&.{1}, list); 2161 2162 list.remove(&n1); 2163 try expectList(&.{}, list); 2164 2165 var n2 = TestNode{ .id = 2 }; 2166 list.insert(&n2); 2167 list.insert(&n1); 2168 try expectList(&.{ 2, 1 }, list); 2169 2170 var n3 = TestNode{ .id = 3 }; 2171 list.insert(&n3); 2172 try expectList(&.{ 2, 1, 3 }, list); 2173 2174 list.remove(&n1); 2175 try expectList(&.{ 2, 3 }, list); 2176 2177 list.insert(&n1); 2178 try expectList(&.{ 2, 3, 1 }, list); 2179 2180 list.remove(&n2); 2181 try expectList(&.{ 3, 1 }, list); 2182 2183 list.remove(&n1); 2184 try expectList(&.{3}, list); 2185 2186 list.remove(&n3); 2187 try expectList(&.{}, list); 2188} 2189 2190test "List: detach prefix clears intrusive links" { 2191 var list = List(TestNode){}; 2192 var n1 = TestNode{ .id = 1 }; 2193 var n2 = TestNode{ .id = 2 }; 2194 var n3 = TestNode{ .id = 3 }; 2195 var n4 = TestNode{ .id = 4 }; 2196 list.insert(&n1); 2197 list.insert(&n2); 2198 list.insert(&n3); 2199 list.insert(&n4); 2200 2201 const empty = detachPrefix(TestNode, &list, 0); 2202 try expectList(&.{}, empty); 2203 try expectList(&.{ 1, 2, 3, 4 }, list); 2204 2205 const prefix = detachPrefix(TestNode, &list, 2); 2206 try expectList(&.{ 1, 2 }, prefix); 2207 try expectList(&.{ 3, 4 }, list); 2208 try t.expectEqual(null, prefix.tail.?.next); 2209 try t.expectEqual(null, list.head.?.prev); 2210 2211 const rest = detachPrefix(TestNode, &list, 2); 2212 try expectList(&.{ 3, 4 }, rest); 2213 try expectList(&.{}, list); 2214} 2215 2216const TestNode = struct { 2217 id: i32, 2218 next: ?*TestNode = null, 2219 prev: ?*TestNode = null, 2220 2221 fn alloc(id: i32) *TestNode { 2222 const tn = t.allocator.create(TestNode) catch unreachable; 2223 tn.* = .{ .id = id }; 2224 return tn; 2225 } 2226}; 2227 2228fn expectList(expected: []const i32, list: List(TestNode)) !void { 2229 if (expected.len == 0) { 2230 try t.expectEqual(null, list.head); 2231 try t.expectEqual(null, list.tail); 2232 return; 2233 } 2234 2235 var i: usize = 0; 2236 var next = list.head; 2237 while (next) |node| { 2238 try t.expectEqual(expected[i], node.id); 2239 i += 1; 2240 next = node.next; 2241 } 2242 try t.expectEqual(expected.len, i); 2243 2244 i = expected.len; 2245 var prev = list.tail; 2246 while (prev) |node| { 2247 i -= 1; 2248 try t.expectEqual(expected[i], node.id); 2249 prev = node.prev; 2250 } 2251 try t.expectEqual(0, i); 2252}