iOS kqueue timer retriggered after delete issue

While I’m developing app using kqueue as timer purpose, I observed a wired/unexpected behavior which could be reproduced as following:

I create a single thread that operation multiple sockets, and using kqueue to handle the IO callback from FDs. To be more specific, I use EVFILT_TIMER to create some timers, code snippet as:

/

/

EV_SET(&event_set, timer_id, EVFILT_TIMER, EV_ADD |EV_ENABLE, 0, internal, timer_struct_ptr);

Kevent(kqueue_id, &event_set, 1, NULL, 0, NULL);

/

EV_SET64(&event_set, timer_id, EVFILT_TIMER,EV_ADD|EV_ENABLE, 0, internal, timer_struct_ptr, 0, 0);

Kevnet64(kqueue_id, &event_set, 1, NULL, 0, 0, NULL);


/

/

EV_SET(&event_set, timer_id, EVFILT_TIMER, EV_DELETE, 0, internal, timer_struct_ptr);

Kevent(kqueue_id, &event_set, 1, NULL, 0, NULL);

/

EV_SET64(&event_set, timer_id, EVFILT_TIMER, EV_DELETE, 0, internal, timer_struct_ptr, 0, 0);

Kevnet64(kqueue_id, &event_set, 1, NULL, 0, 0, NULL);


According to API description, my understanding for the parameter list here are:

“event_set” is the struct of kevent (64bits: kevent64_s);

timer_id is a random bit integer;

interval is the time interval triggers the timer;

timer_struct_ptr is the pointer for the timer object instance;

kqueue_id literally is the identifier id.


By using code described above, I observed some of my App user (rare but indeed could be reproduced in some level of lab load test) that when the timer is removed, the timer would still be triggered after the deletion action. I double check to make sure the timer cancelation happened early enough before the timer trigger time (make sure it’s not the timing edge issue). As part of timer cancelation, I have to de-construct the timer_struct_prt object, release the memory, etc. which would lead to crashes(some dump stack is attached).

Furthermore, I did some test by NOT doing de-construction for timer_struact_ptr while the timer is canceled. Of course this would cause memory leak. However, the result is quite interesting that crash is indeed eliminated (comparing with before).


Now, which bother me for a while whether this is my mis-understanding for kqueue usage or there’s any other alternative solution to achieve what I expected?


Typical trace stacks:


1:

Code Type: ARM-64

Parent Process: ??? [1]

Date/Time: 2018-01-12 02:50:45 +0000

OS Version: iPhone OS 10.3.3 (14G60)

Report Version: 104

Exception Type: SIGSEGV

Exception Codes: SEGV_MAPERR at 0x118

Crashed Thread: 0

Thread 0 Crashed:

0 CoreLocation 0x000000018cc65178 _CLClientInvalidate :904 (in CoreLocation)

1 CoreFoundation 0x000000018499d30c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ :20 (in CoreFoundation)

2 CoreFoundation 0x000000018499cb28 __CFRunLoopDoBlocks :288 (in CoreFoundation)

3 CoreFoundation 0x000000018499a9bc __CFRunLoopRun :764 (in CoreFoundation)

4 CoreFoundation 0x00000001848cada4 _CFRunLoopRunSpecific :424 (in CoreFoundation)

5 GraphicsServices 0x0000000186335074 _GSEventRunModal :100 (in GraphicsServices)

6 UIKit 0x000000018ab85c9c _UIApplicationMain :208 (in UIKit)

7 MYAPP 0x000000010007ce40 main main.m:84 (in MYAPP)

8 libdyld.dylib 0x00000001838d959c _start :4 (in libdyld.dylib)

Thread 1:

...


2:

Code Type: ARM-64

Parent Process: ??? [1]

Date/Time: 2018-01-12 02:47:05 +0000

OS Version: iPhone OS 11.1.2 (15B202)

Report Version: 104

Exception Type: SIGSEGV

Exception Codes: SEGV_MAPERR at 0x10

Crashed Thread: 0

Thread 0 Crashed:

0 libdispatch.dylib 0x0000000182bc1080 _dispatch_call_block_and_release :16 (in libdispatch.dylib)

1 libdispatch.dylib 0x0000000182bc1048 _dispatch_client_callout :16 (in libdispatch.dylib)

2 libdispatch.dylib 0x0000000182bcdb74 _dispatch_main_queue_callback_4CF$VARIANT$mp :1016 (in libdispatch.dylib)

3 CoreFoundation 0x00000001831e5eb0 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ :12 (in CoreFoundation)

4 CoreFoundation 0x00000001831e3a8c __CFRunLoopRun :2012 (in CoreFoundation)

5 CoreFoundation 0x0000000183103fb8 _CFRunLoopRunSpecific :436 (in CoreFoundation)

6 GraphicsServices 0x0000000184f9bf84 _GSEventRunModal :100 (in GraphicsServices)

7 UIKit 0x000000018c6d82e8 _UIApplicationMain :208 (in UIKit)

8 MYAPP 0x0000000104124e40 main main.m:84 (in MYAPP)

9 libdyld.dylib 0x0000000182c2656c _start :4 (in libdyld.dylib)

Thread 1:

...

Replies

In my experience kqueues is a much trickier API then it seems at first blush. So I have to start with the ‘obvious’ question: why are you using them? As opposed to, say, dispatch sources? Is this cross-platform code?

Share and Enjoy

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

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

The major reason of manipulate BSD Socket by our own is to get benefit from some specific TLS_1.3 draft features. Meanwhile we want to get full customization around TCP, TLS, Data timeout. The exist CFNetwork and NSURLSession could not fit in at first place. As a result, we choose kqueue as it already has to the “timer” logic that could be used for timeout control for Socket operation.

Is there any potential known issue by using kqueue timer as I described above on iOS?

Is there any potential known issue by using kqueue timer as I described above on iOS?

Honestly, I don’t know. As I mentioned above, my experience is that kqueues is a much trickier API than you might expert. I recommend that you look at dispatch event sources instead. Unless you have an overriding reason to use kqueues (like you’re working on cross-platform code), dispatch sources are better on many axes. Specifically, dispatch sources support are:

  • Well integrated with other dispatch technologies

  • Support BSD Sockets

  • Support timers

Share and Enjoy

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

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