Skip to content

Instantly share code, notes, and snippets.

@omochi
Created May 23, 2024 00:31
Show Gist options
  • Save omochi/be343838cfdbf0760dee9e412ddd65a3 to your computer and use it in GitHub Desktop.
Save omochi/be343838cfdbf0760dee9e412ddd65a3 to your computer and use it in GitHub Desktop.
import Foundation
struct TimeoutError: Error {}
extension Result where Failure == any Error {
init(catching: () async throws -> Success) async {
do {
self = .success(try await catching())
} catch {
self = .failure(error)
}
}
}
final class SingleSink<T>: @unchecked Sendable {
init(con: CheckedContinuation<T, any Error>) {
self.con = con
}
let lock = NSLock()
let con: CheckedContinuation<T, any Error>
var isCompleted = false
func send(result: Result<T, any Error>) {
lock.withLock {
if isCompleted { return }
isCompleted = true
con.resume(with: result)
}
}
}
struct GuaranteedCheckedContinuation<T> {
var sink: SingleSink<T>
func resume(with result: Result<T, any Error>) {
sink.send(result: result)
}
}
func withGuaranteedCheckedContinuation<T>(
timeout: Duration,
_ body: (GuaranteedCheckedContinuation<T>) -> Void
) async throws -> T {
return try await withCheckedThrowingContinuation { (con) in
let sink = SingleSink(con: con)
let gcon = GuaranteedCheckedContinuation(sink: sink)
body(gcon)
Task {
try await Task.sleep(for: timeout)
gcon.resume(with: .failure(TimeoutError()))
}
}
}
// 自由な処理
func myAsyncProc(timeout: Duration) async throws {
try await withGuaranteedCheckedContinuation(timeout: timeout) { (con: GuaranteedCheckedContinuation<Void>) in
// しかしバグがあり、con.resumeが呼ばれることはない
}
}
func main() async throws {
// 自由な処理に、タイムアウトを設定して呼び出す
do {
try await myAsyncProc(timeout: .seconds(1))
print("proc completed")
} catch {
if error is TimeoutError {
// それでも1秒後にタイムアウトする
print("timeout")
} else {
fatalError("error: \(error)")
}
}
}
try await main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment