Skip to content

Commit f97d23c

Browse files
committed
[runloop] Implement runloop abstraction for BSD.
1 parent f2552a2 commit f97d23c

File tree

2 files changed

+297
-6
lines changed

2 files changed

+297
-6
lines changed

CoreFoundation/RunLoop.subproj/CFRunLoop.c

Lines changed: 295 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ extern void objc_terminate(void);
3535

3636
#include "CFOverflow.h"
3737

38-
#if TARGET_OS_MAC || TARGET_OS_WIN32 || !DEPLOYMENT_RUNTIME_OBJC
38+
#if TARGET_OS_MAC || TARGET_OS_WIN32 || TARGET_OS_BSD || !DEPLOYMENT_RUNTIME_OBJC
3939
#define USE_DISPATCH_SOURCE_FOR_TIMERS __HAS_DISPATCH__
4040
#else
4141
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
@@ -65,6 +65,8 @@ typedef mach_port_t dispatch_runloop_handle_t;
6565
typedef int dispatch_runloop_handle_t;
6666
#elif TARGET_OS_WIN32
6767
typedef HANDLE dispatch_runloop_handle_t;
68+
#else
69+
typedef uint64_t dispatch_runloop_handle_t;
6870
#endif
6971

7072
#if TARGET_OS_MAC
@@ -106,9 +108,12 @@ DISPATCH_EXPORT void _dispatch_main_queue_callback_4CF(void * _Null_unspecified)
106108
dispatch_runloop_handle_t _dispatch_get_main_queue_port_4CF(void);
107109
extern void _dispatch_main_queue_callback_4CF(void *_Null_unspecified msg);
108110

111+
#else
112+
dispatch_runloop_handle_t _dispatch_get_main_queue_port_4CF(void);
113+
extern void _dispatch_main_queue_callback_4CF(void *_Null_unspecified msg);
109114
#endif
110115

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

8421113
ret = __CFPortSetInsert(queuePort, rlm->_portSet);
8431114
if (KERN_SUCCESS != ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
844-
8451115
#endif
8461116
#endif
1117+
rlm->_timerPort = CFPORT_NULL;
1118+
#if TARGET_OS_BSD
1119+
rlm->_timerPort = mk_timer_create(rlm->_portSet);
1120+
#else
8471121
rlm->_timerPort = mk_timer_create();
1122+
#endif
8481123
if (rlm->_timerPort == CFPORT_NULL) {
8491124
CRASH("*** Unable to create timer Port (%d) ***", rlm->_timerPort);
8501125
}
@@ -2670,6 +2945,8 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
26702945
Boolean windowsMessageReceived = false;
26712946
#elif TARGET_OS_LINUX
26722947
int livePort = -1;
2948+
#else
2949+
__CFPort livePort = CFPORT_NULL;
26732950
#endif
26742951
__CFPortSet waitSet = rlm->_portSet;
26752952

@@ -2707,6 +2984,12 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
27072984
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
27082985
goto handle_msg;
27092986
}
2987+
#elif TARGET_OS_BSD
2988+
if (__CFRunLoopServiceFileDescriptors(CFPORTSET_NULL, dispatchPort, 0, &livePort)) {
2989+
goto handle_msg;
2990+
}
2991+
#else
2992+
#error "invoking the port select implementation is required"
27102993
#endif
27112994
}
27122995
#endif
@@ -2762,6 +3045,10 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
27623045
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
27633046
#elif TARGET_OS_LINUX
27643047
__CFRunLoopServiceFileDescriptors(waitSet, CFPORT_NULL, poll ? 0 : TIMEOUT_INFINITY, &livePort);
3048+
#elif TARGET_OS_BSD
3049+
__CFRunLoopServiceFileDescriptors(waitSet, CFPORT_NULL, poll ? 0 : TIMEOUT_INFINITY, &livePort);
3050+
#else
3051+
#error "invoking the port set select implementation is required"
27653052
#endif
27663053

27673054
__CFRunLoopLock(rl);
@@ -2877,7 +3164,7 @@ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInter
28773164

28783165
CFRUNLOOP_ARP_BEGIN;
28793166

2880-
#if TARGET_OS_WIN32 || TARGET_OS_LINUX
3167+
#if TARGET_OS_WIN32 || TARGET_OS_LINUX || TARGET_OS_BSD
28813168
void *msg = 0;
28823169
#endif
28833170
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_DISPATCH | DBG_FUNC_START, rl, rlm, msg, livePort);
@@ -3060,6 +3347,10 @@ void CFRunLoopWakeUp(CFRunLoopRef rl) {
30603347
CFAssert1(0 == ret, __kCFLogAssertion, "%s(): Unable to send wake message to eventfd", __PRETTY_FUNCTION__);
30613348
#elif TARGET_OS_WIN32
30623349
SetEvent(rl->_wakeUpPort);
3350+
#elif TARGET_OS_BSD
3351+
__CFPortTrigger(rl->_wakeUpPort);
3352+
#else
3353+
#error "required"
30633354
#endif
30643355

30653356
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)