Skip to content

Commit ca0f193

Browse files
committed
[runloop] Implement runloop abstraction for BSD.
1 parent 386e107 commit ca0f193

File tree

2 files changed

+296
-5
lines changed

2 files changed

+296
-5
lines changed

CoreFoundation/RunLoop.subproj/CFRunLoop.c

+294-3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ typedef mach_port_t dispatch_runloop_handle_t;
7272
typedef int dispatch_runloop_handle_t;
7373
#elif TARGET_OS_WIN32
7474
typedef HANDLE dispatch_runloop_handle_t;
75+
#else
76+
typedef uint64_t dispatch_runloop_handle_t;
7577
#endif
7678

7779
#if TARGET_OS_MAC
@@ -113,9 +115,12 @@ DISPATCH_EXPORT void _dispatch_main_queue_callback_4CF(void * _Null_unspecified)
113115
dispatch_runloop_handle_t _dispatch_get_main_queue_port_4CF(void);
114116
extern void _dispatch_main_queue_callback_4CF(void *_Null_unspecified msg);
115117

118+
#else
119+
dispatch_runloop_handle_t _dispatch_get_main_queue_port_4CF(void);
120+
extern void _dispatch_main_queue_callback_4CF(void *_Null_unspecified msg);
116121
#endif
117122

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

8641135
ret = __CFPortSetInsert(queuePort, rlm->_portSet);
8651136
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
866-
8671137
#endif
1138+
rlm->_timerPort = CFPORT_NULL;
1139+
#if TARGET_OS_BSD
1140+
rlm->_timerPort = mk_timer_create(rlm->_portSet);
1141+
#else
8681142
rlm->_timerPort = mk_timer_create();
1143+
#endif
8691144
if (rlm->_timerPort == CFPORT_NULL) {
8701145
CRASH("*** Unable to create timer Port (%d) ***", rlm->_timerPort);
8711146
}
@@ -2720,6 +2995,8 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
27202995
Boolean windowsMessageReceived = false;
27212996
#elif TARGET_OS_LINUX
27222997
int livePort = -1;
2998+
#else
2999+
__CFPort livePort = CFPORT_NULL;
27233000
#endif
27243001
__CFPortSet waitSet = rlm->_portSet;
27253002

@@ -2757,6 +3034,12 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
27573034
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
27583035
goto handle_msg;
27593036
}
3037+
#elif TARGET_OS_BSD
3038+
if (__CFRunLoopServiceFileDescriptors(CFPORTSET_NULL, dispatchPort, 0, &livePort)) {
3039+
goto handle_msg;
3040+
}
3041+
#else
3042+
#error "invoking the port select implementation is required"
27603043
#endif
27613044
}
27623045
#endif
@@ -2812,6 +3095,10 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
28123095
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
28133096
#elif TARGET_OS_LINUX
28143097
__CFRunLoopServiceFileDescriptors(waitSet, CFPORT_NULL, poll ? 0 : TIMEOUT_INFINITY, &livePort);
3098+
#elif TARGET_OS_BSD
3099+
__CFRunLoopServiceFileDescriptors(waitSet, CFPORT_NULL, poll ? 0 : TIMEOUT_INFINITY, &livePort);
3100+
#else
3101+
#error "invoking the port set select implementation is required"
28153102
#endif
28163103

28173104
__CFRunLoopLock(rl);
@@ -2923,7 +3210,7 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
29233210
__CFRunLoopUnlock(rl);
29243211
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
29253212

2926-
#if TARGET_OS_WIN32 || TARGET_OS_LINUX
3213+
#if TARGET_OS_WIN32 || TARGET_OS_LINUX || TARGET_OS_BSD
29273214
void *msg = 0;
29283215
#endif
29293216
CFRUNLOOP_ARP_BEGIN(NULL)
@@ -3119,6 +3406,10 @@ void CFRunLoopWakeUp(CFRunLoopRef rl) {
31193406
CFAssert1(0 == ret, __kCFLogAssertion, "%s(): Unable to send wake message to eventfd", __PRETTY_FUNCTION__);
31203407
#elif TARGET_OS_WIN32
31213408
SetEvent(rl->_wakeUpPort);
3409+
#elif TARGET_OS_BSD
3410+
__CFPortTrigger(rl->_wakeUpPort);
3411+
#else
3412+
#error "required"
31223413
#endif
31233414

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

Sources/Foundation/RunLoop.swift

+2-2
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)