From 6cc3a8cde769b8ecefb308ebaee4edd210d9bf58 Mon Sep 17 00:00:00 2001 From: jack Date: Sun, 4 Jan 2026 13:25:43 -1000 Subject: [PATCH] fix: add @MainActor to MessageDeduplicationService for thread safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added @MainActor annotation to both LRUDeduplicationCache and MessageDeduplicationService classes. This provides compile-time enforcement of thread safety since all callers (ChatViewModel, NostrRelayManager) are already on MainActor. Benefits: - Compile-time enforcement prevents future misuse - Simpler than internal locking mechanisms - Consistent with existing patterns in codebase Also adds: - @MainActor annotation to existing test suites - 5 new concurrency tests verifying thread safety behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../MessageDeduplicationService.swift | 4 + .../MessageDeduplicationServiceTests.swift | 134 ++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/bitchat/Services/MessageDeduplicationService.swift b/bitchat/Services/MessageDeduplicationService.swift index 88f249ed..b513bb87 100644 --- a/bitchat/Services/MessageDeduplicationService.swift +++ b/bitchat/Services/MessageDeduplicationService.swift @@ -12,6 +12,8 @@ import Foundation /// Generic LRU (Least Recently Used) cache for deduplication. /// Uses an efficient O(1) lookup with periodic compaction. +/// Thread-safe via @MainActor - all callers are already on main actor. +@MainActor final class LRUDeduplicationCache { private var map: [String: Value] = [:] private var order: [String] = [] @@ -157,6 +159,8 @@ enum ContentNormalizer { /// Service that manages message deduplication using LRU caches. /// Provides separate caches for content-based dedup and Nostr event ID dedup. +/// Thread-safe via @MainActor - all callers are already on main actor. +@MainActor final class MessageDeduplicationService { /// Cache for content-based near-duplicate detection diff --git a/bitchatTests/MessageDeduplicationServiceTests.swift b/bitchatTests/MessageDeduplicationServiceTests.swift index 8f265afb..251fe808 100644 --- a/bitchatTests/MessageDeduplicationServiceTests.swift +++ b/bitchatTests/MessageDeduplicationServiceTests.swift @@ -12,6 +12,8 @@ import Foundation // MARK: - LRU Deduplication Cache Tests +@Suite("LRU Deduplication Cache") +@MainActor struct LRUDeduplicationCacheTests { // MARK: - Basic Operations @@ -265,6 +267,8 @@ struct ContentNormalizerTests { // MARK: - Message Deduplication Service Tests +@Suite("Message Deduplication Service") +@MainActor struct MessageDeduplicationServiceTests { // MARK: - Content Deduplication @@ -467,4 +471,134 @@ struct MessageDeduplicationServiceTests { #expect(service.contentTimestamp(for: "hello world") == now) #expect(service.contentTimestamp(for: "Hello World") == now) } + + // MARK: - Thread Safety Tests (via @MainActor enforcement) + + @Test("Concurrent content recording is safe via MainActor") + func concurrentContentRecording() async { + let service = MessageDeduplicationService(contentCapacity: 1000, nostrEventCapacity: 1000) + let iterations = 100 + + // All operations run on MainActor due to @MainActor annotation + // This test verifies the pattern works correctly + await withTaskGroup(of: Void.self) { group in + for i in 0..(capacity: 500) + let iterations = 100 + + await withTaskGroup(of: Void.self) { group in + // Write tasks + for i in 0..(capacity: 10) + let iterations = 100 + + await withTaskGroup(of: Void.self) { group in + for i in 0..