mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 03:45:20 +00:00
Fix CI deadlock in vouch tests and live-refresh the fingerprint sheet on vouch acceptance
Two fixes for PR #1380 review findings: 1. CI "Run Swift Tests (app)" hang (exit 137): the new SecureIdentityStateManagerVouchTests suite was nonisolated, so Swift Testing ran its tests in parallel on the Swift Concurrency cooperative pool. Each test enqueues a queue.async(.barrier) write (setVerified) and immediately blocks in queue.sync / queue.sync(.barrier) (recordVouch / effectiveTrustLevel). On CI's few-core runners every cooperative-pool thread ended up parked behind a pending barrier that never got a dispatch worker, deadlocking the whole test process until the watchdog SIGKILLed it. The suite is now @MainActor, matching the production isolation of the vouch API (ChatVouchCoordinator is @MainActor) and keeping blocking syncs off the cooperative pool. 2. Codex P2: an open fingerprint sheet did not refresh its vouched badge when a vouch batch was accepted - VerificationModel.bind() never observed the trust-change signal. It now subscribes to the "peerStatusUpdated" notification that ChatVouchCoordinator.notifyPeerTrustChanged() posts (same source PeerListModel uses) and forwards it to objectWillChange. Added a regression test that pins VerificationModel's own subscription (verified to fail without the fix). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -150,6 +150,17 @@ final class VerificationModel: ObservableObject {
|
|||||||
self?.objectWillChange.send()
|
self?.objectWillChange.send()
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
// Vouch state changes (ChatVouchCoordinator.notifyPeerTrustChanged)
|
||||||
|
// are signalled via this notification rather than a published
|
||||||
|
// property, so an open fingerprint sheet refreshes its vouched badge
|
||||||
|
// live when a vouch batch is accepted.
|
||||||
|
NotificationCenter.default.publisher(for: Notification.Name("peerStatusUpdated"))
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.objectWillChange.send()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resolveDisplayName(for peerID: PeerID, statusPeerID: PeerID) -> String {
|
private func resolveDisplayName(for peerID: PeerID, statusPeerID: PeerID) -> String {
|
||||||
|
|||||||
@@ -591,6 +591,45 @@ struct AppArchitectureTests {
|
|||||||
#expect(!verificationModel.isVerified(peerID: peerID))
|
#expect(!verificationModel.isVerified(peerID: peerID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test("VerificationModel refreshes when peer trust changes (vouch accepted)")
|
||||||
|
@MainActor
|
||||||
|
func verificationModelRefreshesOnPeerTrustChange() async {
|
||||||
|
let viewModel = makeArchitectureViewModel()
|
||||||
|
var privateConversationModel: PrivateConversationModel? = PrivateConversationModel(
|
||||||
|
chatViewModel: viewModel,
|
||||||
|
conversations: viewModel.conversations,
|
||||||
|
locationChannelsModel: LocationChannelsModel(manager: makeArchitectureLocationManager())
|
||||||
|
)
|
||||||
|
let verificationModel = VerificationModel(
|
||||||
|
chatViewModel: viewModel,
|
||||||
|
privateConversationModel: privateConversationModel!
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivateConversationModel happens to observe the same notification
|
||||||
|
// and re-assign its published selection, which would ripple into
|
||||||
|
// VerificationModel; release it so this test pins VerificationModel's
|
||||||
|
// own subscription rather than that incidental chain.
|
||||||
|
privateConversationModel = nil
|
||||||
|
|
||||||
|
// The bound @Published sources replay their current values on
|
||||||
|
// subscription; let those initial main-queue emissions settle so the
|
||||||
|
// sink below observes only the trust-change signal.
|
||||||
|
try? await Task.sleep(nanoseconds: 100_000_000)
|
||||||
|
|
||||||
|
// ChatVouchCoordinator.notifyPeerTrustChanged() signals accepted
|
||||||
|
// vouches via "peerStatusUpdated"; an open fingerprint sheet must
|
||||||
|
// re-render its vouched badge from that signal alone.
|
||||||
|
var refreshed = false
|
||||||
|
let cancellable = verificationModel.objectWillChange.sink { _ in
|
||||||
|
refreshed = true
|
||||||
|
}
|
||||||
|
defer { cancellable.cancel() }
|
||||||
|
|
||||||
|
NotificationCenter.default.post(name: Notification.Name("peerStatusUpdated"), object: nil)
|
||||||
|
await waitUntil { refreshed }
|
||||||
|
#expect(refreshed)
|
||||||
|
}
|
||||||
|
|
||||||
@Test("PeerListModel publishes mesh and geohash directory state")
|
@Test("PeerListModel publishes mesh and geohash directory state")
|
||||||
@MainActor
|
@MainActor
|
||||||
func peerListModelPublishesDirectoryState() async {
|
func peerListModelPublishesDirectoryState() async {
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ import Testing
|
|||||||
/// Ordering note: mutations use barrier blocks on the manager's concurrent
|
/// Ordering note: mutations use barrier blocks on the manager's concurrent
|
||||||
/// queue and reads use `queue.sync`, so a read submitted after a mutation
|
/// queue and reads use `queue.sync`, so a read submitted after a mutation
|
||||||
/// always observes it — no polling needed.
|
/// always observes it — no polling needed.
|
||||||
|
///
|
||||||
|
/// `@MainActor` matches production (the manager's vouch API is driven by the
|
||||||
|
/// main-actor `ChatVouchCoordinator`) and keeps the blocking `queue.sync`
|
||||||
|
/// reads off the Swift Concurrency cooperative pool. Left nonisolated, Swift
|
||||||
|
/// Testing runs these tests in parallel on that pool, and on CI's few-core
|
||||||
|
/// runners every pool thread ended up parked in `queue.sync` behind a pending
|
||||||
|
/// `queue.async(.barrier)` write that never got a dispatch worker — a
|
||||||
|
/// process-wide deadlock (watchdog SIGKILL, exit 137).
|
||||||
|
@MainActor
|
||||||
struct SecureIdentityStateManagerVouchTests {
|
struct SecureIdentityStateManagerVouchTests {
|
||||||
private let voucher = String(repeating: "0a", count: 32)
|
private let voucher = String(repeating: "0a", count: 32)
|
||||||
private let vouchee = String(repeating: "0b", count: 32)
|
private let vouchee = String(repeating: "0b", count: 32)
|
||||||
|
|||||||
Reference in New Issue
Block a user