eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2
3// 🤖 The main-thread hang watchdog — ClickLab's stall ticker (LChatMac),
4// generalised so both apps report hangs into the perf ledger. A background
5// timer pings the main queue every 200ms; any ping serviced late means main
6// was wedged, and the lag ≈ how long. Cheap enough to run for a whole
7// session: one async block per 200ms, no stack sampling (MetricKit's hang
8// diagnostics carry the call trees; this catches the sub-hang stutters
9// that MetricKit's threshold ignores but fingers feel). The 250ms floor is
10// Apple's own responsiveness bar — the first field day showed felt hangs
11// living below the original 400ms floor, invisible; HitchRecorder covers
12// the band below this.
13final class HangWatchdog {
14 private var ticker: DispatchSourceTimer?
15 // A long hang queues several pings that all drain in one burst; the first
16 // serviced carries the full lag, the rest shrink toward zero. Report the
17 // first, swallow the burst. Main-thread state (pings land on main).
18 private var suppressUntil = DispatchTime.now()
19
20 func start() {
21 guard ticker == nil else { return }
22 let t = DispatchSource.makeTimerSource(queue: DispatchQueue(label: "lchat.perf.watchdog"))
23 t.schedule(deadline: .now() + 1, repeating: .milliseconds(200))
24 t.setEventHandler {
25 let sent = DispatchTime.now()
26 DispatchQueue.main.async { [weak self] in
27 guard let self else { return }
28 let now = DispatchTime.now()
29 let lagMs = Double(now.uptimeNanoseconds &- sent.uptimeNanoseconds) / 1e6
30 guard lagMs > 250, now > self.suppressUntil else { return }
31 self.suppressUntil = now + .seconds(1)
32 Perf.recordHang(ms: lagMs)
33 }
34 }
35 t.resume()
36 ticker = t
37 }
38
39 func stop() {
40 ticker?.cancel()
41 ticker = nil
42 }
43}
44