NSNetService not calling didAcceptConnectionWithInputStream on custom runloop

I have some existing working code which creates a peer-to-peer AWDL connection using NSNetService.

self.advertisingService = [[NSNetService alloc] initWithDomain:@"local." type:@"_http-alt._tcp" name:self.serviceName];
self.advertisingService.delegate = self;
self.advertisingService.includesPeerToPeer = true;
[self.advertisingService scheduleInRunLoop:self.runLoop forMode:NSDefaultRunLoopMode];
[self.advertisingService publishWithOptions:NSNetServiceListenForConnections];

Previously, self.runLoop was always [NSRunLoop mainRunLoop]. Now I am embedding this code in an application where the thread 1 does not offer a run loop. This means the above code no longer worked: I wouldn't even get the netServiceDidPublish: delegate callback.

So instead I spawn an NSThread, create a run loop manually and supply a reference to it here (self.runLoop). This mostly works: now I correctly receive the netServiceDidPublish: callback.

The problem is that I still never receive netService:didAcceptConnectionWithInputStream: when a connection occurs. Over on the connecting device, it is successfully obtaining the input and output streams and reaching the "Open" state for both. This new connection is never reflected here on the listening service.

It feels likely that I have to tell something else to use the custom runloop but I can't tell what. Does anyone have any ideas?

Accepted Reply

Unfortunately the behaviour is identical to my original message.

OK. When you set NSNetServiceListenForConnections the system has to fire up two different async subsystems, the NSNetService itself and an internal subsystem that listens for connections. I suspect what’s going on here is that the former is attaching to the expected run loop but the latter is not. That sounds like a bug to me, and you can file it as such if you want, but there’s probably not much point. That’s because…

It's not so much a solution as a workaround, but I can confirm that using nw_listener_t to set up the equivalent peer-to-peer TCP listener works.

I disagree that this is a workaround. Network framework is our long-term direction for low-level networking and, while NSNetService and CFSocketStream are not formally deprecated, they are well on their way to that fate. Thus, I would recommend that you make this transition even if you weren’t hitting this bug.

I just glad to hear that Network framework dosen’t have the same problem (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Replies

Right now you’re doing something I call cross run-loop scheduling, that is, adding and removing sources to a run loop not associated with the current thread. This is supposed to work but my experience is that you commonly encounter weird edge cases.

If you move all the service creation code to your dedicated run loop thread, what do you see? Oh, and don’t worry about the code looking nice, just do a quick hack to see whether it changes things.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • I tried that again just now: I wrapped the whole caboodle of initing and publishing inside [self.runLoop performBlock:^{ … }];, and scheduled the advertising service onto [NSRunLoop currentRunLoop] to drive home the point. Unfortunately the behaviour is identical to my original message. :( didPublish yes, didAccept no.

Add a Comment

It's not so much a solution as a workaround, but I can confirm that using nw_listener_t to set up the equivalent peer-to-peer TCP listener works. It and nw_connection_t have a configurable dispatch queue instead of a runloop. Provided I stay away from dispatch_get_main_queue() everything looks like it's working fine in this environment.

In this situation I can tolerate supporting only macOS 10.14+ so I'll get away with it.

Unfortunately the behaviour is identical to my original message.

OK. When you set NSNetServiceListenForConnections the system has to fire up two different async subsystems, the NSNetService itself and an internal subsystem that listens for connections. I suspect what’s going on here is that the former is attaching to the expected run loop but the latter is not. That sounds like a bug to me, and you can file it as such if you want, but there’s probably not much point. That’s because…

It's not so much a solution as a workaround, but I can confirm that using nw_listener_t to set up the equivalent peer-to-peer TCP listener works.

I disagree that this is a workaround. Network framework is our long-term direction for low-level networking and, while NSNetService and CFSocketStream are not formally deprecated, they are well on their way to that fate. Thus, I would recommend that you make this transition even if you weren’t hitting this bug.

I just glad to hear that Network framework dosen’t have the same problem (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"