| ... | @@ -1254,11 +1254,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) | ... | @@ -1254,11 +1254,7 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) |
| 1254 | | 1254 | |
| 1255 | /// This API only works when `std.io.mode` is `std.io.Mode.evented`. | 1255 | /// This API only works when `std.io.mode` is `std.io.Mode.evented`. |
| 1256 | /// This struct is immovable after calling `listen`. | 1256 | /// This struct is immovable after calling `listen`. |
| 1257 | pub const Server = struct { | 1257 | pub const TcpServer = struct { |
| 1258 | /// This field is meant to be accessed directly. | | |
| 1259 | /// Call `connections.get` to accept a connection. | | |
| 1260 | connections: *ConnectionChannel, | | |
| 1261 | | | |
| 1262 | /// Copied from `Options` on `init`. | 1258 | /// Copied from `Options` on `init`. |
| 1263 | kernel_backlog: u32, | 1259 | kernel_backlog: u32, |
| 1264 | | 1260 | |
| ... | @@ -1266,63 +1262,33 @@ pub const Server = struct { | ... | @@ -1266,63 +1262,33 @@ pub const Server = struct { |
| 1266 | listen_address: Address, | 1262 | listen_address: Address, |
| 1267 | | 1263 | |
| 1268 | sockfd: ?os.fd_t, | 1264 | sockfd: ?os.fd_t, |
| 1269 | accept_frame: @Frame(acceptConnections), | | |
| 1270 | | | |
| 1271 | pub const ConnectionChannel = std.event.Channel(AcceptError!fs.File); | | |
| 1272 | | | |
| 1273 | pub const AcceptError = error{ | | |
| 1274 | ConnectionAborted, | | |
| 1275 | | | |
| 1276 | /// The per-process limit on the number of open file descriptors has been reached. | | |
| 1277 | ProcessFdQuotaExceeded, | | |
| 1278 | | | |
| 1279 | /// The system-wide limit on the total number of open files has been reached. | | |
| 1280 | SystemFdQuotaExceeded, | | |
| 1281 | | | |
| 1282 | /// Not enough free memory. This often means that the memory allocation is limited | | |
| 1283 | /// by the socket buffer limits, not by the system memory. | | |
| 1284 | SystemResources, | | |
| 1285 | | | |
| 1286 | ProtocolFailure, | | |
| 1287 | | | |
| 1288 | /// Firewall rules forbid connection. | | |
| 1289 | BlockedByFirewall, | | |
| 1290 | } || os.UnexpectedError; | | |
| 1291 | | 1265 | |
| 1292 | pub const Options = struct { | 1266 | pub const Options = struct { |
| 1293 | /// How many connections the kernel will accept on the application's behalf. | 1267 | /// How many connections the kernel will accept on the application's behalf. |
| 1294 | /// If more than this many connections pool in the kernel, clients will start | 1268 | /// If more than this many connections pool in the kernel, clients will start |
| 1295 | /// seeing "Connection refused". | 1269 | /// seeing "Connection refused". |
| 1296 | kernel_backlog: u32 = 128, | 1270 | kernel_backlog: u32 = 128, |
| 1297 | | | |
| 1298 | /// How many connections this `Server` will accept from the kernel even before | | |
| 1299 | /// they are requested from the `connections` channel. | | |
| 1300 | eager_connections: usize = 16, | | |
| 1301 | }; | 1271 | }; |
| 1302 | | 1272 | |
| 1303 | /// After this call succeeds, resources have been acquired and must | 1273 | /// After this call succeeds, resources have been acquired and must |
| 1304 | /// be released with `deinit`. | 1274 | /// be released with `deinit`. |
| 1305 | pub fn init(options: Options) !Server { | 1275 | pub fn init(options: Options) TcpServer { |
| 1306 | const loop = std.event.Loop.instance orelse | 1276 | return TcpServer{ |
| 1307 | @compileError("std.net.Server only works in evented I/O mode"); | | |
| 1308 | return Server{ | | |
| 1309 | .connections = try ConnectionChannel.create(loop, options.eager_connections), | | |
| 1310 | .sockfd = null, | 1277 | .sockfd = null, |
| 1311 | .kernel_backlog = options.kernel_backlog, | 1278 | .kernel_backlog = options.kernel_backlog, |
| 1312 | .listen_address = undefined, | 1279 | .listen_address = undefined, |
| 1313 | .accept_frame = undefined, | | |
| 1314 | }; | 1280 | }; |
| 1315 | } | 1281 | } |
| 1316 | | 1282 | |
| 1317 | /// After calling this function, one must call `init` to do anything else with this `Server`. | 1283 | /// Release all resources. The `TcpServer` memory becomes `undefined`. |
| 1318 | pub fn deinit(self: *Server) void { | 1284 | pub fn deinit(self: *TcpServer) void { |
| 1319 | self.close(); | 1285 | self.close(); |
| 1320 | self.connections.destroy(); | | |
| 1321 | self.* = undefined; | 1286 | self.* = undefined; |
| 1322 | } | 1287 | } |
| 1323 | | 1288 | |
| 1324 | pub fn listen(self: *Server, address: Address) !void { | 1289 | pub fn listen(self: *TcpServer, address: Address) !void { |
| 1325 | const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK; | 1290 | const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; |
| | 1291 | const sock_flags = os.SOCK_STREAM | os.SOCK_CLOEXEC | nonblock; |
| 1326 | const sockfd = try os.socket(os.AF_INET, sock_flags, os.PROTO_tcp); | 1292 | const sockfd = try os.socket(os.AF_INET, sock_flags, os.PROTO_tcp); |
| 1327 | self.sockfd = sockfd; | 1293 | self.sockfd = sockfd; |
| 1328 | errdefer { | 1294 | errdefer { |
| ... | @@ -1334,38 +1300,51 @@ pub const Server = struct { | ... | @@ -1334,38 +1300,51 @@ pub const Server = struct { |
| 1334 | try os.bind(sockfd, &address.os_addr, socklen); | 1300 | try os.bind(sockfd, &address.os_addr, socklen); |
| 1335 | try os.listen(sockfd, self.kernel_backlog); | 1301 | try os.listen(sockfd, self.kernel_backlog); |
| 1336 | try os.getsockname(sockfd, &self.listen_address.os_addr, &socklen); | 1302 | try os.getsockname(sockfd, &self.listen_address.os_addr, &socklen); |
| 1337 | | | |
| 1338 | // acceptConnections loops, calling os.accept(). | | |
| 1339 | self.accept_frame = async self.acceptConnections(); | | |
| 1340 | errdefer await self.accept_frame; | | |
| 1341 | } | 1303 | } |
| 1342 | | 1304 | |
| 1343 | /// Stop listening. It is still necessary to call `deinit` after stopping listening. | 1305 | /// Stop listening. It is still necessary to call `deinit` after stopping listening. |
| 1344 | /// Calling `deinit` will automatically call `close`. It is safe to call `close` when | 1306 | /// Calling `deinit` will automatically call `close`. It is safe to call `close` when |
| 1345 | /// not listening. | 1307 | /// not listening. |
| 1346 | pub fn close(self: *Server) void { | 1308 | pub fn close(self: *TcpServer) void { |
| 1347 | if (self.sockfd) |fd| { | 1309 | if (self.sockfd) |fd| { |
| 1348 | os.close(fd); | 1310 | os.close(fd); |
| 1349 | self.sockfd = null; | 1311 | self.sockfd = null; |
| 1350 | await self.accept_frame; | | |
| 1351 | self.accept_frame = undefined; | | |
| 1352 | self.listen_address = undefined; | 1312 | self.listen_address = undefined; |
| 1353 | } | 1313 | } |
| 1354 | } | 1314 | } |
| 1355 | | 1315 | |
| 1356 | fn acceptConnections(self: *Server) void { | 1316 | pub const AcceptError = error{ |
| 1357 | const sockfd = self.sockfd.?; | 1317 | ConnectionAborted, |
| 1358 | const accept_flags = os.SOCK_NONBLOCK | os.SOCK_CLOEXEC; | 1318 | |
| 1359 | while (true) { | 1319 | /// The per-process limit on the number of open file descriptors has been reached. |
| 1360 | var accepted_addr: Address = undefined; | 1320 | ProcessFdQuotaExceeded, |
| 1361 | var addr_len: os.socklen_t = @sizeOf(os.sockaddr); | 1321 | |
| 1362 | const conn = if (os.accept4(sockfd, &accepted_addr.os_addr, &addr_len, accept_flags)) |fd| | 1322 | /// The system-wide limit on the total number of open files has been reached. |
| 1363 | fs.File.openHandle(fd) | 1323 | SystemFdQuotaExceeded, |
| 1364 | else |err| switch (err) { | 1324 | |
| 1365 | error.WouldBlock => unreachable, // we asserted earlier about non-blocking I/O mode | 1325 | /// Not enough free memory. This often means that the memory allocation is limited |
| 1366 | else => |e| e, | 1326 | /// by the socket buffer limits, not by the system memory. |
| 1367 | }; | 1327 | SystemResources, |
| 1368 | self.connections.put(conn); | 1328 | |
| | 1329 | ProtocolFailure, |
| | 1330 | |
| | 1331 | /// Firewall rules forbid connection. |
| | 1332 | BlockedByFirewall, |
| | 1333 | } || os.UnexpectedError; |
| | 1334 | |
| | 1335 | /// If this function succeeds, the returned `fs.File` is a caller-managed resource. |
| | 1336 | pub fn accept(self: *TcpServer) AcceptError!fs.File { |
| | 1337 | const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0; |
| | 1338 | const accept_flags = nonblock | os.SOCK_CLOEXEC; |
| | 1339 | var accepted_addr: Address = undefined; |
| | 1340 | var adr_len: os.socklen_t = @sizeOf(os.sockaddr); |
| | 1341 | if (os.accept4(self.sockfd.?, &accepted_addr.os_addr, &adr_len, accept_flags)) |fd| { |
| | 1342 | return fs.File.openHandle(fd); |
| | 1343 | } else |err| switch (err) { |
| | 1344 | // We only give SOCK_NONBLOCK when I/O mode is async, in which case this error |
| | 1345 | // is handled by os.accept4. |
| | 1346 | error.WouldBlock => unreachable, |
| | 1347 | else => |e| return e, |
| 1369 | } | 1348 | } |
| 1370 | } | 1349 | } |
| 1371 | }; | 1350 | }; |