Skip to content

Commit cba44bf

Browse files
committed
Update Looper error handling
1 parent 6ab21a2 commit cba44bf

File tree

2 files changed

+25
-69
lines changed

2 files changed

+25
-69
lines changed

Sources/AndroidLooper/AndroidMainActor.swift

Lines changed: 8 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,14 @@ public extension AndroidMainActor {
5454
// the public API should always be retained.
5555
assert(looper.isRetained)
5656

57-
let executor: Looper.Executor
57+
// override the global executors to wake the main looper to drain the queue whenever something is scheduled
5858
do {
59-
executor = try Looper.Executor(looper: looper)
59+
let executor = try Looper.Executor(looper: looper)
60+
return try AndroidMainActor.installGlobalExecutor(executor)
6061
}
6162
catch {
62-
assertionFailure("Unable to initialize Looper.Executor: \(error)")
6363
return false
6464
}
65-
66-
// override the global executors to wake the main looper to drain the queue whenever something is scheduled
67-
return AndroidMainActor.installGlobalExecutor(executor)
6865
}
6966
}
7067

@@ -96,13 +93,13 @@ private extension AndroidMainActor {
9693
/// See also [a draft proposal for custom executors](https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#the-default-global-concurrent-executor)
9794
static func installGlobalExecutor(
9895
_ executor: Looper.Executor
99-
) -> Bool {
96+
) throws(AndroidLooperError) -> Bool {
10097
if didInstallGlobalExecutor {
10198
return false
10299
}
103100
didInstallGlobalExecutor = true
104101

105-
let looperCallback: Looper.Callback = { ft, event, data in
102+
let looperCallback: Looper.Handle.Callback = { ft, event, data in
106103
while true {
107104
switch CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true) {
108105
case CFRunLoopRunResult.handledSource:
@@ -123,66 +120,17 @@ private extension AndroidMainActor {
123120
let dispatchPort = _dispatch_get_main_queue_port_4CF()
124121
let fileDescriptor = FileDescriptor(rawValue: dispatchPort)
125122

126-
guard executor.looper.handle.add(
123+
try executor.looper.handle.add(
127124
fileDescriptor: fileDescriptor,
128125
id: 0,
129126
events: .input,
130127
callback: looperCallback,
131128
data: nil
132-
) else {
133-
return false
134-
}
129+
).get()
130+
135131
// install executor
136132
self.executor = executor
137133
_ = mainLoop
138134
return true
139135
}
140136
}
141-
142-
@available(macOS 13.0, *)
143-
internal extension Looper.Handle {
144-
145-
/// Waits for events to be available, with optional timeout in milliseconds.
146-
func pollOnce(duration: Duration? = nil) throws(Error) -> AndroidMainActor.PollResult? {
147-
var outFd: CInt = -1
148-
var outEvents: CInt = 0
149-
var outData: UnsafeMutableRawPointer?
150-
151-
let timeoutMillis: CInt
152-
if let duration {
153-
timeoutMillis = CInt(duration.milliseconds)
154-
} else {
155-
timeoutMillis = 0
156-
}
157-
158-
let err = ALooper_pollOnce(timeoutMillis, &outFd, &outEvents, &outData)
159-
switch Int(err) {
160-
case ALOOPER_POLL_WAKE:
161-
fallthrough
162-
case ALOOPER_POLL_CALLBACK:
163-
return nil
164-
case ALOOPER_POLL_TIMEOUT:
165-
throw AndroidMainActor.Error.pollTimeout
166-
case ALOOPER_POLL_ERROR:
167-
throw AndroidMainActor.Error.pollError
168-
default:
169-
return AndroidMainActor.PollResult(id: err, fd: .init(rawValue: outFd), events: Looper.Events(rawValue: Int(outEvents)), data: outData)
170-
}
171-
}
172-
}
173-
174-
@available(macOS 13.0, *)
175-
public extension AndroidMainActor {
176-
177-
enum Error: Swift.Error {
178-
case pollTimeout
179-
case pollError
180-
}
181-
182-
struct PollResult: Identifiable {
183-
public let id: CInt
184-
public let fd: FileDescriptor
185-
public let events: Looper.Events
186-
public let data: UnsafeRawPointer?
187-
}
188-
}

Sources/AndroidLooper/SerialExecutor.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,33 +25,30 @@ public extension Looper {
2525
let queue = LockedState(initialState: [UnownedJob]())
2626

2727
/// Initialize with Android Looper
28-
internal init(looper: consuming Looper) throws(Errno) {
28+
internal init(looper: consuming Looper) throws(AndroidLooperError) {
2929
let looperHandle = looper.handle
3030
// open fd
3131
let fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK) // TODO: Move to System / Socket package
3232
if fd < 0 {
33-
throw Errno(rawValue: errno)
33+
throw .bionic(Errno(rawValue: errno))
3434
}
3535

3636
// initialize
3737
let eventFd = FileDescriptor(rawValue: fd)
3838
self.eventFd = eventFd
3939
self.looper = looper
4040

41-
// add to looper
42-
guard looperHandle.add(fileDescriptor: eventFd, callback: drainAExecutor, data: Unmanaged.passUnretained(self).toOpaque()) else {
43-
try? eventFd.close()
44-
throw .invalidArgument
45-
}
41+
// Add fd to Looper
42+
try configureLooper()
4643
}
4744

4845
deinit {
4946
if eventFd.rawValue != -1 {
50-
_ = looper.handle.remove(fileDescriptor: eventFd)
47+
_ = try? looper.remove(fileDescriptor: eventFd)
5148
try? eventFd.close()
5249
}
5350
}
54-
51+
5552
/// Enqueue a single job
5653
public func enqueue(_ job: UnownedJob) {
5754
queue.withLock { queue in
@@ -69,6 +66,17 @@ public extension Looper {
6966
@available(macOS 13.0, iOS 13.0, *)
7067
internal extension Looper.Executor {
7168

69+
func configureLooper() throws(AndroidLooperError) {
70+
do {
71+
// add to looper
72+
try looper.handle.add(fileDescriptor: eventFd, callback: drainAExecutor, data: Unmanaged.passUnretained(self).toOpaque()).get()
73+
}
74+
catch {
75+
try? eventFd.close()
76+
throw error
77+
}
78+
}
79+
7280
/// Read number of remaining events from eventFd
7381
var eventsRemaining: UInt64 {
7482
get throws {

0 commit comments

Comments
 (0)