Skip to content

Commit 0721f0e

Browse files
committed
[runloop] Implement runloop abstraction for BSD.
1 parent 52b21fb commit 0721f0e

File tree

2 files changed

+296
-5
lines changed

2 files changed

+296
-5
lines changed

CoreFoundation/RunLoop.subproj/CFRunLoop.c

Lines changed: 294 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ typedef mach_port_t dispatch_runloop_handle_t;
6969
typedef int dispatch_runloop_handle_t;
7070
#elif TARGET_OS_WIN32
7171
typedef HANDLE dispatch_runloop_handle_t;
72+
#else
73+
typedef uint64_t dispatch_runloop_handle_t;
7274
#endif
7375

7476
#if TARGET_OS_MAC
@@ -110,9 +112,12 @@ DISPATCH_EXPORT void _dispatch_main_queue_callback_4CF(void * _Null_unspecified)
110112
dispatch_runloop_handle_t _dispatch_get_main_queue_port_4CF(void);
111113
extern void _dispatch_main_queue_callback_4CF(void *_Null_unspecified msg);
112114

115+
#else
116+
dispatch_runloop_handle_t _dispatch_get_main_queue_port_4CF(void);
117+
extern void _dispatch_main_queue_callback_4CF(void *_Null_unspecified msg);
113118
#endif
114119

115-
#if TARGET_OS_WIN32 || TARGET_OS_LINUX
120+
#if TARGET_OS_WIN32 || TARGET_OS_LINUX || TARGET_OS_BSD
116121
CF_EXPORT _CFThreadRef _CF_pthread_main_thread_np(void);
117122
#define pthread_main_thread_np() _CF_pthread_main_thread_np()
118123
#endif
@@ -443,6 +448,267 @@ CF_INLINE kern_return_t __CFPortSetRemove(__CFPort port, __CFPortSet portSet) {
443448
CF_INLINE void __CFPortSetFree(__CFPortSet portSet) {
444449
close(portSet);
445450
}
451+
#elif TARGET_OS_BSD
452+
453+
#include <sys/types.h>
454+
#include <sys/event.h>
455+
#include <sys/time.h>
456+
#include <poll.h>
457+
458+
typedef uint64_t __CFPort;
459+
#define CFPORT_NULL ((__CFPort)-1)
460+
461+
// _dispatch_get_main_queue_port_4CF is a uint64_t, i.e., a __CFPort.
462+
// That is, we can't use one type for the queue handle in Dispatch and a
463+
// different type for __CFPort in CF.
464+
#define __CFPORT_PACK(rfd, wfd) (((uint64_t)(rfd) << 32) | ((uint32_t)(wfd)))
465+
#define __CFPORT_UNPACK_W(port) ((uint32_t)((port) & 0xffffffff))
466+
#define __CFPORT_UNPACK_R(port) ((uint32_t)((port) >> 32))
467+
468+
typedef struct ___CFPortSet {
469+
int kq;
470+
} *__CFPortSet;
471+
#define CFPORTSET_NULL NULL
472+
473+
#define TIMEOUT_INFINITY UINT64_MAX
474+
475+
// Timers are not pipes; they are kevents on a parent kqueue.
476+
// We must flag these to differentiate them from pipes, but we have
477+
// to pack the (kqueue, timer ident) pair like a __CFPort.
478+
#define __CFPORT_TIMER_PACK(ident, kq) \
479+
((1ULL << 63) | ((uint64_t)(ident) << 32) | ((uint32_t)(kq)))
480+
#define __CFPORT_IS_TIMER(port) ((port) & (1ULL << 63))
481+
482+
static __CFPort __CFPortAllocate(__unused uintptr_t guard) {
483+
__CFPort port;
484+
int fds[2];
485+
int r = pipe2(fds, O_CLOEXEC | O_NONBLOCK);
486+
if (r == -1) {
487+
return CFPORT_NULL;
488+
}
489+
490+
uint32_t rfd = (uint32_t)fds[0], wfd = (uint32_t)fds[1];
491+
port = __CFPORT_PACK(rfd, wfd);
492+
493+
if (__CFPORT_IS_TIMER(port)) {
494+
// This port is not distinguishable from a flagged packed timer.
495+
close((int)(__CFPORT_UNPACK_W(port)));
496+
close((int)(__CFPORT_UNPACK_R(port)));
497+
return CFPORT_NULL;
498+
}
499+
500+
return port;
501+
}
502+
503+
static void __CFPortTrigger(__CFPort port) {
504+
int wfd = (int)__CFPORT_UNPACK_W(port);
505+
ssize_t result;
506+
do {
507+
result = write(wfd, "x", 1);
508+
} while (result == -1 && errno == EINTR);
509+
}
510+
511+
CF_INLINE void __CFPortFree(__CFPort port, __unused uintptr_t guard) {
512+
close((int)(__CFPORT_UNPACK_W(port)));
513+
close((int)(__CFPORT_UNPACK_R(port)));
514+
}
515+
516+
#define __CFPORT_TIMER_UNPACK_ID(port) (((port) >> 32) & 0x7fffffff)
517+
#define __CFPORT_TIMER_UNPACK_KQ(port) ((port) & 0xffffffff)
518+
#define MAX_TIMERS 16
519+
uintptr_t ident = 0;
520+
521+
static __CFPort mk_timer_create(__CFPortSet parent) {
522+
if (ident > MAX_TIMERS) return CFPORT_NULL;
523+
ident++;
524+
525+
int kq = parent->kq;
526+
__CFPort port = __CFPORT_TIMER_PACK(ident, kq);
527+
528+
return port;
529+
}
530+
531+
static kern_return_t mk_timer_arm(__CFPort timer, int64_t expire_tsr) {
532+
uint64_t now = mach_absolute_time();
533+
uint64_t expire_time = __CFTSRToNanoseconds(expire_tsr);
534+
int64_t duration = 0;
535+
if (now <= expire_time) {
536+
duration = __CFTSRToTimeInterval(expire_time - now) * 1000;
537+
}
538+
539+
int id = __CFPORT_TIMER_UNPACK_ID(timer);
540+
struct kevent tev;
541+
EV_SET(
542+
&tev,
543+
id,
544+
EVFILT_TIMER,
545+
EV_ADD | EV_ENABLE,
546+
0,
547+
duration,
548+
(void *)timer);
549+
550+
int kq = __CFPORT_TIMER_UNPACK_KQ(timer);
551+
int r = kevent(kq, &tev, 1, NULL, 0, NULL);
552+
553+
return KERN_SUCCESS;
554+
}
555+
556+
static kern_return_t mk_timer_cancel(__CFPort timer, const void *unused) {
557+
int id = __CFPORT_TIMER_UNPACK_ID(timer);
558+
struct kevent tev;
559+
EV_SET(
560+
&tev,
561+
id,
562+
EVFILT_TIMER,
563+
EV_DISABLE,
564+
0,
565+
0,
566+
(void *)timer);
567+
568+
int kq = __CFPORT_TIMER_UNPACK_KQ(timer);
569+
int r = kevent(kq, &tev, 1, NULL, 0, NULL);
570+
571+
return KERN_SUCCESS;
572+
}
573+
574+
static kern_return_t mk_timer_destroy(__CFPort timer) {
575+
int id = __CFPORT_TIMER_UNPACK_ID(timer);
576+
struct kevent tev;
577+
EV_SET(
578+
&tev,
579+
id,
580+
EVFILT_TIMER,
581+
EV_DELETE,
582+
0,
583+
0,
584+
(void *)timer);
585+
586+
int kq = __CFPORT_TIMER_UNPACK_KQ(timer);
587+
int r = kevent(kq, &tev, 1, NULL, 0, NULL);
588+
589+
ident--;
590+
return KERN_SUCCESS;
591+
}
592+
593+
CF_INLINE __CFPortSet __CFPortSetAllocate(void) {
594+
struct ___CFPortSet *set = malloc(sizeof(struct ___CFPortSet));
595+
set->kq = kqueue();
596+
return set;
597+
}
598+
599+
CF_INLINE kern_return_t __CFPortSetInsert(__CFPort port, __CFPortSet set) {
600+
if (__CFPORT_IS_TIMER(port)) {
601+
return 0;
602+
}
603+
604+
struct kevent change;
605+
EV_SET(&change,
606+
__CFPORT_UNPACK_R(port),
607+
EVFILT_READ,
608+
EV_ADD | EV_ENABLE | EV_CLEAR | EV_RECEIPT,
609+
0,
610+
0,
611+
(void *)port);
612+
struct timespec timeout = {0, 0};
613+
int r = kevent(set->kq, &change, 1, NULL, 0, &timeout);
614+
615+
return 0;
616+
}
617+
618+
CF_INLINE kern_return_t __CFPortSetRemove(__CFPort port, __CFPortSet set) {
619+
if (__CFPORT_IS_TIMER(port)) {
620+
return 0;
621+
}
622+
623+
struct kevent change;
624+
EV_SET(&change,
625+
__CFPORT_UNPACK_R(port),
626+
EVFILT_READ,
627+
EV_DELETE | EV_RECEIPT,
628+
0,
629+
0,
630+
(void *)port);
631+
struct timespec timeout = {0, 0};
632+
int r = kevent(set->kq, &change, 1, NULL, 0, &timeout);
633+
634+
return 0;
635+
}
636+
637+
CF_INLINE void __CFPortSetFree(__CFPortSet set) {
638+
close(set->kq);
639+
free(set);
640+
}
641+
642+
static int __CFPollFileDescriptors(struct pollfd *fds, nfds_t nfds, uint64_t timeout) {
643+
uint64_t elapsed = 0;
644+
uint64_t start = mach_absolute_time();
645+
int result = 0;
646+
while (1) {
647+
struct timespec ts = {0};
648+
struct timespec *tsPtr = &ts;
649+
if (timeout == TIMEOUT_INFINITY) {
650+
tsPtr = NULL;
651+
} else if (elapsed < timeout) {
652+
uint64_t delta = timeout - elapsed;
653+
ts.tv_sec = delta / 1000000000UL;
654+
ts.tv_nsec = delta % 1000000000UL;
655+
}
656+
657+
result = ppoll(fds, 1, tsPtr, NULL);
658+
659+
if (result == -1 && errno == EINTR) {
660+
uint64_t end = mach_absolute_time();
661+
elapsed += (end - start);
662+
start = end;
663+
} else {
664+
return result;
665+
}
666+
}
667+
}
668+
669+
static Boolean __CFRunLoopServiceFileDescriptors(__CFPortSet set, __CFPort port, uint64_t timeout, __CFPort *livePort) {
670+
__CFPort awokenPort = CFPORT_NULL;
671+
672+
if (port != CFPORT_NULL) {
673+
int rfd = __CFPORT_UNPACK_R(port);
674+
struct pollfd fdInfo = {
675+
.fd = rfd,
676+
.events = POLLIN,
677+
};
678+
679+
ssize_t result = __CFPollFileDescriptors(&fdInfo, 1, timeout);
680+
if (result == 0)
681+
return false;
682+
683+
awokenPort = port;
684+
} else {
685+
struct kevent awake;
686+
struct timespec timeout = {0, 0};
687+
688+
int r = kevent(set->kq, NULL, 0, &awake, 1, &timeout);
689+
690+
if (r == 0) {
691+
return false;
692+
}
693+
694+
if (awake.flags == EV_ERROR) {
695+
return false;
696+
}
697+
698+
if (awake.filter == EVFILT_READ) {
699+
char x;
700+
r = read(awake.ident, &x, 1);
701+
}
702+
703+
awokenPort = (__CFPort)awake.udata;
704+
}
705+
706+
if (livePort)
707+
*livePort = awokenPort;
708+
709+
return true;
710+
}
711+
446712
#else
447713
#error "CFPort* stubs for this platform must be implemented
448714
#endif
@@ -554,6 +820,11 @@ static kern_return_t mk_timer_cancel(HANDLE name, AbsoluteTime *result_time) {
554820
}
555821
return (int)res;
556822
}
823+
#elif TARGET_OS_BSD
824+
/*
825+
* This implementation of the mk_timer_* stubs is defined with the
826+
* implementation of the CFPort* stubs.
827+
*/
557828
#else
558829
#error "mk_timer_* stubs for this platform must be implemented"
559830
#endif
@@ -860,9 +1131,13 @@ static CFRunLoopModeRef __CFRunLoopCopyMode(CFRunLoopRef rl, CFStringRef modeNam
8601131

8611132
ret = __CFPortSetInsert(queuePort, rlm->_portSet);
8621133
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
863-
8641134
#endif
1135+
rlm->_timerPort = CFPORT_NULL;
1136+
#if TARGET_OS_BSD
1137+
rlm->_timerPort = mk_timer_create(rlm->_portSet);
1138+
#else
8651139
rlm->_timerPort = mk_timer_create();
1140+
#endif
8661141
if (rlm->_timerPort == CFPORT_NULL) {
8671142
CRASH("*** Unable to create timer Port (%d) ***", rlm->_timerPort);
8681143
}
@@ -2717,6 +2992,8 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
27172992
Boolean windowsMessageReceived = false;
27182993
#elif TARGET_OS_LINUX
27192994
int livePort = -1;
2995+
#else
2996+
__CFPort livePort = CFPORT_NULL;
27202997
#endif
27212998
__CFPortSet waitSet = rlm->_portSet;
27222999

@@ -2754,6 +3031,12 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
27543031
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
27553032
goto handle_msg;
27563033
}
3034+
#elif TARGET_OS_BSD
3035+
if (__CFRunLoopServiceFileDescriptors(CFPORTSET_NULL, dispatchPort, 0, &livePort)) {
3036+
goto handle_msg;
3037+
}
3038+
#else
3039+
#error "invoking the port select implementation is required"
27573040
#endif
27583041
}
27593042
#endif
@@ -2809,6 +3092,10 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
28093092
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
28103093
#elif TARGET_OS_LINUX
28113094
__CFRunLoopServiceFileDescriptors(waitSet, CFPORT_NULL, poll ? 0 : TIMEOUT_INFINITY, &livePort);
3095+
#elif TARGET_OS_BSD
3096+
__CFRunLoopServiceFileDescriptors(waitSet, CFPORT_NULL, poll ? 0 : TIMEOUT_INFINITY, &livePort);
3097+
#else
3098+
#error "invoking the port set select implementation is required"
28123099
#endif
28133100

28143101
__CFRunLoopLock(rl);
@@ -2920,7 +3207,7 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
29203207
__CFRunLoopUnlock(rl);
29213208
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
29223209

2923-
#if TARGET_OS_WIN32 || TARGET_OS_LINUX
3210+
#if TARGET_OS_WIN32 || TARGET_OS_LINUX || TARGET_OS_BSD
29243211
void *msg = 0;
29253212
#endif
29263213
CFRUNLOOP_ARP_BEGIN(NULL)
@@ -3116,6 +3403,10 @@ void CFRunLoopWakeUp(CFRunLoopRef rl) {
31163403
CFAssert1(0 == ret, __kCFLogAssertion, "%s(): Unable to send wake message to eventfd", __PRETTY_FUNCTION__);
31173404
#elif TARGET_OS_WIN32
31183405
SetEvent(rl->_wakeUpPort);
3406+
#elif TARGET_OS_BSD
3407+
__CFPortTrigger(rl->_wakeUpPort);
3408+
#else
3409+
#error "required"
31193410
#endif
31203411

31213412
cf_trace(KDEBUG_EVENT_CFRL_WAKEUP | DBG_FUNC_END, rl, 0, 0, 0);

Sources/Foundation/RunLoop.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

10-
#if os(Linux) || os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
10+
#if os(Linux) || os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(OpenBSD)
1111
import CoreFoundation
1212
#else
1313
@_implementationOnly import CoreFoundation
@@ -90,7 +90,7 @@ open class RunLoop: NSObject {
9090
// On platforms where it's available, getCFRunLoop() can be overridden and we use it below.
9191
// Make sure we honor the override -- var currentCFRunLoop will do so on platforms where overrides are available.
9292

93-
#if os(Linux) || os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
93+
#if os(Linux) || os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(OpenBSD)
9494
internal var currentCFRunLoop: CFRunLoop { getCFRunLoop() }
9595

9696
@available(*, deprecated, message: "Directly accessing the run loop may cause your code to not become portable in the future.")

0 commit comments

Comments
 (0)