Will moving CFSocket to background prevent data loss?

I am using a UDP CFSocket to receive and process large amounts of data.


Processing of the data is fairly simple before it is dispalyed. Creating the display happens on a background thread, and I use GCD.


My problem is that I drop UDP data and I do not think that I should be doing this already. I am getting new data packets in every 100 ms, but they are large, so the overall throughput is around 140 Mbps.


My socket is added to the main run loop with:

   // Add to run loop:
    let rls = CFSocketCreateRunLoopSource(nil, cfSock, 0)
    if (rls == nil) {
      error("Could not get run loop source.")
      return
    }
    CFRunLoopAddSource(CFRunLoopGetMain(), rls, CFRunLoopMode.defaultMode);


This means my callback, passed when creating the socket is called on the main thread.


I would like to add the socket to a run loop in a dedicated processing thread but got stuck trying to do so. I am use to the CGD async calls. I've tried the following, but it does not work:

DispatchQueue.global(qos: .userInitiated).async {
  CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, CFRunLoopMode.defaultMode);
}


How do I move to a dedicated processing run loop, and can I expect it to make a difference? My hope is for data processing to always succeed, and drop the data when displaying, not processing it.

Accepted Reply

[I] used Wireshark to effectively probe point "E". I can see that my data is received.

OK. There’s two possibilities here:

  • There’s something wrong with the code that’s actually receiving the data (X).

  • The kernel is dropping the data because the receive buffer is full (Y).

I’d investigate both of these in turn. With regards X, for testing purposes I’d write a small tool that receives data synchronously using BSD Sockets and then records what got received. If that sees the data and your real app doesn’t, you can look at why.

If your test project also drops data, you can test for Y but tweaking the socket’s receive buffer size (using

SO_RCVBUF
).

ps Is your receiving code running on the Mac? Or iOS?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Networking in general is not that reliable. It is even worse with UDP. It is a connectionless protocol with no guarantee of delivery. It's nickname is "Unreliable Data Protocol".

Hi John,


Thanks, but my issue is not with UDP or the networking itself. I am testing on localhost with no data loss over the network. The issue is entierly in my app.


I've managed to move my socket receive callback to its own thread with:

let thread = Thread(block: {
      CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, CFRunLoopMode.defaultMode);
      let runLoop = RunLoop.current
      runLoop.run()
    })
    thread.start()


This did not solve my problem, but it did give me more insight.


My UDP data comes in bursts of big data blocks. I've slowed it down so that it comes not once every 100 ms, but once every second. I am therefore not busy at all but I am still dropping data. It looks like the UDP data is not not buffered on the network layer or something. Could this be, and if so, what can I do about it?

I am using a UDP

CFSocket
to receive and process large amounts of data.

Ooooh, someone who is using

CFSocket
for a valid reason. That’s rare (-:

Seriously though, while it’s still fine to use

CFSocket
for UDP, there are better choices:
  • BSD Sockets, using a Dispatch source for async support

  • Network framework

Processing of the data is fairly simple before it is dispalyed. Creating the display happens on a background thread, and I use GCD.

If you’re already ‘in bed’ with Dispatch, using an API integrated with that model (either of the ones I mentioned above) is generally a good idea.

My socket is added to the main run loop with:

Putting this sort of I/O work on the main thread is definitely not your best option. The problem is that the main thread is subject to substantial latency (for example, when the app has to handle a complex UI event), so it may not be able to pull data out of the network promptly. If the traffic is bursty, that means the data backs up in the socket buffer, which can result in it being dropped.

I would like to add the socket to a run loop in a dedicated processing thread but got stuck trying to do so. I am use to the CGD async calls.

Yeah, that’s not going to work. If you want to keep your

CFSocket
code, you’ll need to:
  1. Start a custom thread.

  2. From that thread, add the socket’s run loop source to the thread’s run loop.

  3. Have that thread then run the run loop indefinitely.

This is quite feasible, but using a Dispatch-compatible API would be better given that you’re already depending on Dispatch.

It looks like the UDP data is not not buffered on the network layer or something. Could this be, and if so, what can I do about it?

UDP has no flow control, so dealing with bursty traffic like this is tricky. At each step of the way the network has to decide whether to allocate space to buffer the burst or drop packets. It’s hard to predict how that’ll go.

I recommend that you take a look at my Investigating Network Latency Problems post. That explains how to analyse where the packets are going missing. That’s important because, if the packets are being dropped by the AP, no amount of clever code on the receiving STA is going to help.

Finally, do you have any control over the sender of this data?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks. I am busy looking in to the Network framework, but before I go in to the weeds:

I've also read your Investigating Network Latency Problems post and used Wireshark to effectively probe point "E". I can see that my data is received.

Yes, I have control of the sending app but this is only useful for testing purposes. When it runs as intended then it will generate data at a high speed (usually on the network, not on localhost) and I have to handle it.

I've changed the sending app to be 10x slower and send once every second instead of every 100 ms. I still have the same issue. My app always drops the last 38 packets regardless of how busy my app is.

If the kernel is not dropping data, and my app is not very busy, then what can be causing the data drop, and will using a different framework make a difference, or am I barking up the wrong tree?

[I] used Wireshark to effectively probe point "E". I can see that my data is received.

OK. There’s two possibilities here:

  • There’s something wrong with the code that’s actually receiving the data (X).

  • The kernel is dropping the data because the receive buffer is full (Y).

I’d investigate both of these in turn. With regards X, for testing purposes I’d write a small tool that receives data synchronously using BSD Sockets and then records what got received. If that sees the data and your real app doesn’t, you can look at why.

If your test project also drops data, you can test for Y but tweaking the socket’s receive buffer size (using

SO_RCVBUF
).

ps Is your receiving code running on the Mac? Or iOS?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Got it, thanks!


I wrote a simple C++ app using BSD sockets and it did not loose the data. But, when I added as much as a print statement for every packet received it also started losing data.


I read socket's the receive buffer and it was smaller than the number of bytes that would be sent with a burst (about 100 kBytes). So, my app was keeping up for the first couple of packets, but no matter what I did, it would start losing data.


Seeing that my data comes in as quick bursts, I increased the RX buffer with the following ocde (swift):

// Increase RX buffer size:
var rcvbuf: ULONG = 2*1024*1024;
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, socklen_t(MemoryLayout<ULONG>.size)) != 0)
{
    error("Could not set socket rx buffer size option (errno = \(errno)).");
}
// Verify if necessary:
// var len = socklen_t(MemoryLayout<ULONG>.size)
// getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len);


I am now running without losing any data.

I am now running without losing any data.

Excellent news. Your next step is to decide which API to use in your real app. In theory, your original

CFSocket
should still be viable at long as the main thread latency allows you to drain the socket before a second burst comes in. However, I’m still going to recommend a switch to something more Dispatch-friendly (either Network framework or BSD Sockets using a Dispatch event source).

What platform are you working on? On iOS, there are specific advantages of making the leap to the Network framework. Specifically, UDP performance is generally better with the Network framework because of the user-space networking stack.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

My application is for macOS.


Do you have specific examples that I could look at? I've looked at Network framework and found the ImplementingNetcatWithNetworkFramework. Is that a good example?


I have lots of experience in using raw BSD sockets but how do I link that up with a Dispatch event source? When I briefly looked at GCD one of the issues that I was was that I cannot simply dispatch using GCD since my processing needs to happen in sequence (re-assembling UDP data, and displaying in order). The simple (and very easy to use) GCD would farm out to as many threads as possible, so I need to do something to keep it in sequence.


Any points in the right direction is much appreciated.

Do you have specific examples that I could look at? I've looked at Network framework and found the ImplementingNetcatWithNetworkFramework. Is that a good example?

nwcat
is a fine example but it’s probably not the best starting place for you because it’s goals are different from your goals. Specifically, it has the goal of being transport independent, just like
nc
, but you are tightly bound to UDP. This matters because, for example,
nwcat
doesn’t care about message boundaries but you do.

A better place to start would be the code in this post. It makes an outgoing UDP ‘connection’. If you need to listen for incoming connections, you’ll need to combine this code with an

NWListener
.

I have lots of experience in using raw BSD sockets but how do I link that up with a Dispatch event source?

One of the main pain points with BSD Sockets is async I/O. A Dispatch source makes that easy. For example:

let sock: Int32 = -1
let flags = fcntl(sock, F_GETFL)
assert(flags != 0)
let success = fcntl(sock, F_SETFL, flags | O_NONBLOCK) >= 0
assert(success)
let source = DispatchSource.makeReadSource(fileDescriptor: sock, queue: .main)
source.setEventHandler {
    … here you can make a `recvmsg` code and process the message …
}
source.setCancelHandler {
    let success = close(sock) == 0
    assert(success)

    … put any other shutdown code you need here …
}
source.activate()

Note that I’m targeting the main queue on line 6. In a real app, you’d supply whatever serial queue makes sense in that context.

The simple (and very easy to use) GCD would farm out to as many threads as possible, so I need to do something to keep it in sequence.

Lots of folks think that Dispatch is about parallelism, and it works well for that, but I almost exclusively use it for asynchronicity. I generally stick with serial queues, and then use the queue target to avoid thread explosions. For more background on this, watch the following WWDC presentations:

Understanding Dispatch is an important skill even if you use Network framework for your I/O, which is generally what I’d recommend you do (as long as you can accept the deployment target requirements).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks again. I've watched the presentations and changed my app so that the socket sits on its own dispatch queue. The application is no longer dropping any data.


Note to others that may find this post: remember to ensure that your DispatchSource (source in the above example from eskimo) is retained. If it is in a method, then source will be released when it is no longer in scope, and the DispatchSource will be destoryed, meaning that your event handlers are never called.