Do I somehow opt-out of using sockets or should they be not used by default?
When running this test app:
import SwiftUI
import Network
var activeConnections: [NWConnection] = [] {
didSet {
print("connection count: \(activeConnections.count)")
}
}
let site = "www.apple.com"
func openNewConnections(count: Int) {
if count <= 0 { return }
let c = NWConnection(host: NWEndpoint.Host(site), port: .https, using: .tls)
activeConnections.append(c)
c.stateUpdateHandler = { state in
switch state {
case .cancelled: fatalError("• cancelled")
case .failed(let error): fatalError("• Error: \(error)")
case .setup: break
case .waiting: break
case .preparing: break
case .ready:
c.send(content: "GET https://\(site)/index.html HTTP/1.0\n\n".data(using: .utf8), completion: NWConnection.SendCompletion.contentProcessed { error in
if let error {
fatalError("• send ended with Error: \(error)")
}
})
c.receive(minimumIncompleteLength: 1, maximumLength: 20) { data, contentContext, isComplete, error in
if let data {
if let s = String(data: data, encoding: .utf8) {
openNewConnections(count: count - 1)
} else {
openNewConnections(count: count - 1)
}
} else {
fatalError("• Error: \(String(describing: error))")
}
}
@unknown default:
fatalError("TODO")
}
}
c.start(queue: .main)
}
@main struct NetTestApp: App {
init() {
var r = rlimit()
let err = getrlimit(RLIMIT_NOFILE, &r)
precondition(err == 0)
print("max files:", r.rlim_cur)
openNewConnections(count: 20_000)
}
var body: some Scene {
WindowGroup { Text("Hello, World") }
}
}
I'm hitting the "too many open files" error around the number reported by getrlimit + RLIMIT_NOFILE (249'th connection out of 256 max files) and I can see the word "socket" in the log, in function names and "Failed to initialize socket" error message:
max files: 256
connection count: 1
...
connection count: 2
connection count: 246
connection count: 247
connection count: 248
nw_socket_initialize_socket <private> Failed to create socket(2,1) [24: Too many open files]
nw_socket_initialize_socket Failed to create socket(2,1) [24: Too many open files]
nw_socket_initialize_socket Failed to create socket(2,1) [24: Too many open files], dumping backtrace:
[arm64] libnetcore-3100.140.3
0 Network 0x00000001938e4564 __nw_create_backtrace_string + 192
1 Network 0x0000000193b0b164 _ZL27nw_socket_initialize_socketP11nw_protocol + 2008
2 Network 0x0000000193b2917c _ZL27nw_socket_add_input_handlerP11nw_protocolS0_ + 1416
3 Network 0x0000000193c901b4 nw_endpoint_flow_attach_socket_protocol + 380
4 Network 0x0000000193c800a0 nw_endpoint_flow_attach_protocols + 6492
5 Network 0x0000000193c7d304 nw_endpoint_flow_setup_protocols + 3664
6 Network 0x0000000193c989e8 -[NWConcrete_nw_endpoint_flow startWithHandler:] + 4092
7 Network 0x00000001937625ac nw_endpoint_handler_path_change + 9400
8 Network 0x000000<…>
nw_socket_add_input_handler [C248.1.1:2] Failed to initialize socket
I think the same would happen in the console app (where getrlimit + RLIMIT_NOFILE returns a higher number 7168 = 0x1C00) if I wait long enough (possibly make the test more robust first to handle the cancellation errors, etc).
I am on macOS 13.6.
Edit: It's also not obvious how to recover from that error as it is not reported back via normal Swift error mechanism, looks like it uses either C++ or Objective-C exception mechanism. The last NWConnection() call completes, setting stateUpdateHandler, and "start" calls complete, then the OS internals fires this error in the log and the update handler is not called two more times (with "preparing" and "waiting" – normally waiting is not called) without "ready" – thus the logic of handling response or restarting a new connection stops proceeding without having a chance of handling or printing out the relevant error!
More specifically if I add more log entries I see this at the very end:
state: preparing, connectionCount: 248
// here goes string of errors
state: waiting, connectionCount: 248
and nothing else afterwards. For the previous connections before the file limit is hit – "ready" callout happens after "preparing" and without "waiting" callout.