たのしい人生

OSAllocatedUnfairLockでOptionalなclosureを保護するとreabstraction thunk helperによってclosureの再帰呼び出しが無限ループになる問題

Sendable適合のためにclosureをOSAllocatedUnfairLockで保護することを考える。大体用途的にhandlerということでoptionalにしたい。

final class Po: Sendable {
    let handler: OSAllocatedUnfairLock<(@Sendable (Po.Result) -> ())?> = .init(initialState: nil)

    func setHandler(_ handler: (@Sendable (Po.Result) -> ())?) {
        self.handler.withLock { $0 = handler }
    }

    func call(with value: String) {
        let handler = handler.withLock { return $0 }
        handler?(.init(value: value))
    }

    struct Result {
        let value: String
    }
}

これは簡単すぎるコードで再現確認もとってないけど、だいたいこういう状況でhandlerを呼んだらstack overflow(EXC_BAD_ACCESS code=2)した。なぜ?

call stackの状況をプリントしてみる。

for symbol in Thread.callStackSymbols {
    print(symbol)
}

結果がこう。

1   Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
2   Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
3   Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
4   Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
5   Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
6   Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
7   Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
8   Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
9   Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
10  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
11  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
12  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
13  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
14  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
15  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
16  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
17  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
18  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
19  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
20  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
21  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
22  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
23  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
24  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
25  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
26  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
27  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
28  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
29  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
30  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
31  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
32  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
33  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
34  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
35  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
36  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
37  Example1Event.debug.dylib           0x0000000103f80ec0 $s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR + 24
38  Example1Event.debug.dylib           0x0000000103f80e98 $s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR + 36
39  Example1Event.debug.dylib           0x0000000103f82878 $s12Example1Core13CameraCaptureC13captureOutput_03didF04fromySo09AVCaptureF0C_So17CMSampleBufferRefaSo0I10ConnectionCtF + 636
40  Example1Event.debug.dylib           0x0000000103f83018 $s12Example1Core13CameraCaptureC13captureOutput_03didF04fromySo09AVCaptureF0C_So17CMSampleBufferRefaSo0I10ConnectionCtFTo + 116
41  AVFCapture                          0x00000001a9e96fac 31134C66-3B30-3B0E-B11A-00CEFB451A52 + 163756
42  AVFCapture                          0x00000001a9ebad48 31134C66-3B30-3B0E-B11A-00CEFB451A52 + 310600
43  CMCapture                           0x00000001ad70b3a0 E68AFBBE-7145-38E8-B796-F5399CE474DB + 844704
44  CMCapture                           0x00000001ad70b010 E68AFBBE-7145-38E8-B796-F5399CE474DB + 843792
45  libdispatch.dylib                   0x0000000102f6e7bc _dispatch_client_callout + 20
46  libdispatch.dylib                   0x0000000102f718e0 _dispatch_continuation_pop + 676
47  libdispatch.dylib                   0x0000000102f88cc8 _dispatch_source_latch_and_call + 480
48  libdispatch.dylib                   0x0000000102f87718 _dispatch_source_invoke + 860
49  libdispatch.dylib                   0x0000000102f764a4 _dispatch_lane_serial_drain + 376
50  libdispatch.dylib                   0x0000000102f77408 _dispatch_lane_invoke + 408
51  libdispatch.dylib                   0x0000000102f84404 _dispatch_root_queue_drain_deferred_wlh + 328
52  libdispatch.dylib                   0x0000000102f83a38 _dispatch_workloop_worker_thread + 444
53  libsystem_pthread.dylib             0x00000001e9804934 _pthread_wqthread + 288
54  libsystem_pthread.dylib             0x00000001e98010cc start_wqthread + 8

終わってるcall stack。2つの再帰してる呼び出しをdemangleしてみる。

~ swift demangle '$s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR'
$s12Example1Core10VideoFrameCytIeghnr_ACIeghg_TR ---> reabstraction thunk helper from @escaping @callee_guaranteed @Sendable (@in_guaranteed Example1Core.VideoFrame) -> (@out ()) to @escaping @callee_guaranteed @Sendable (@guaranteed Example1Core.VideoFrame) -> ()
~ swift demangle '$s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR'
$s12Example1Core10VideoFrameCIeghg_ACytIeghnr_TR ---> reabstraction thunk helper from @escaping @callee_guaranteed @Sendable (@guaranteed Example1Core.VideoFrame) -> () to @escaping @callee_guaranteed @Sendable (@in_guaranteed Example1Core.VideoFrame) -> (@out ())

取り出して差分強調

  • reabstraction thunk helper from @escaping @callee_guaranteed @Sendable (@in_guaranteed Example1Core.VideoFrame) -> (@out ()) to @escaping @callee_guaranteed @Sendable (@guaranteed Example1Core.VideoFrame) -> ()
  • reabstraction thunk helper from @escaping @callee_guaranteed @Sendable (@guaranteed Example1Core.VideoFrame) -> () to @escaping @callee_guaranteed @Sendable (@in_guaranteed Example1Core.VideoFrame) -> (@out ())

なんか知らない reabstraction thunk helper なるものが作られて呼ばれている上に、それが無限に相互に変換し合っている。なんでこんな事が起こっているか厳密には理解していないので言及は避けるが、OSAllocatedUnfairLockに型パラメータとして渡る型、setterで受け取る型、利用側で取り出す型それぞれについてシグネチャは同じだがコンパイラから見ると差分があるらしくこの変換が自動生成されるっぽい。

取り急ぎ対策としてはOSAllocatedUnfairLockにわたす際に一段型を挟んで型のブレを防ぐ。

struct HandlerBox<Handler: Sendable>: Sendable {
    let handler: Handler

    public init(handler: Handler) {
        self.handler = handler
    }
}

全く意味のなさそうな型だが

final class Po: Sendable {
    let handler: OSAllocatedUnfairLock<HandlerBox<(@Sendable (Po.Result) -> ())>?> = .init(initialState: nil)

    func setHandler(_ handler: (@Sendable (Po.Result) -> ())?) {
        self.handler.withLock { $0 = handler.map { .init(handler: $0) }}
    }

    func call(with value: String) {
        let box = handler.withLock { return $0 }
        box?.handler(.init(value: value))
    }

    struct Result {
        let value: String
    }
}

にすると上記のstack traceの再帰呼び出しは解消された。ふわっとした記事だけどハマる人(自分)のために念の為。

Amazon.co.jpアソシエイト