refactor(connectivity): remove DispatchQueue from observer notifications#60
Conversation
Removes forced main queue dispatch from ConnectivityObserverManaging notification methods. Observers are now responsible for their own thread management. This eliminates the race condition causing watchOS test timeouts where: - ConnectivityManager (actor) → DispatchQueue.main.async → Observer (actor) - Created deadlock under load on watchOS simulators Breaking change: Observers expecting automatic main thread delivery must now use @mainactor annotation or dispatch explicitly. Benefits: - Fixes intermittent watchOS test failures - More flexible threading model - Better Swift 6 concurrency compliance - Faster observer notifications (no queue hopping) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
Important Review skippedMore than 25% of the files skipped due to max files limit. The review is being skipped to prevent a low-quality review. 70 files out of 270 files are above the max files limit of 100. Please upgrade to Pro plan to get higher limits. You can disable this status message by setting the ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 48-demo-applications-part-3 #60 +/- ##
============================================================
Coverage 45.33% 45.33%
============================================================
Files 30 30
Lines 514 514
============================================================
Hits 233 233
Misses 281 281
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
PR Review: Remove DispatchQueue from Observer NotificationsThis PR removes forced DispatchQueue.main.async dispatch from 6 notification methods, transferring thread management responsibility to observers. This is a breaking change appropriate for v2.0.0 that aligns with Swift 6 concurrency best practices. Strengths1. Root Cause Fix
2. Swift 6 Concurrency Alignment
3. Improved Architecture
4. Documentation Quality
Concerns & Recommendations1. Unstructured Task Usage in TestObserver (Medium) TestObserver still uses unstructured Task in notification handlers (TestObserver.swift:32-34, 41-43). The PR description states this compounded timing issues, but the fix only removes DispatchQueue hop, not the unstructured Task pattern. Recommendation: Consider making protocol methods async or using Task(priority: .high) to ensure deterministic completion for tests. 2. ObserverRegistry.notify() Fire-and-Forget (Low) ObserverRegistry.notify() spawns unstructured Task with no await (ObserverRegistry.swift:95-98). Consider adding notifyAndWait() variant for testing scenarios where completion matters. 3. Coverage Regression (Investigate) Coverage dropped from 85.25% to 45.13%. Appears related to dropping Swift 5.9/5.10/6.0 test jobs. Verify this is a reporting artifact. Files ReviewedCore Changes:
Test Changes:
RecommendationAPPROVE with minor suggestions This fixes the root cause of watchOS test failures and aligns with Swift 6 best practices. Before merge:
Great work! The shift to observer-controlled execution is the right direction for modern Swift concurrency. |
…xecute
The tests were failing due to a race condition caused by multiple layers
of unstructured concurrency:
1. ObserverRegistry.notify() creates Task { await notifyIsolated() }
2. TestObserver delegate methods create Task { await setLastState() }
This creates a situation where notifications complete immediately but
state updates happen asynchronously. Adding Task.yield() before checking
conditions gives pending tasks an opportunity to execute.
Related to #60
Code Review: Remove DispatchQueue from Observer NotificationsThis PR removes forced DispatchQueue.main.async from observer notifications, making observers responsible for their own thread management. Well-justified architectural improvement that fixes watchOS test failures and aligns with Swift 6. ✅ Strengths
Code Quality
Improvements
Issues: NoneNo bugs, leaks, security concerns. Follows Swift concurrency correctly. PerformancePositive: Eliminated queue hopping, reduced main thread contention Test Suggestions
✅ APPROVEHigh quality PR: fixes watchOS timeouts, improves Swift 6 compliance, well-tested. Before merge: CI passes all platforms, no new timeouts, CHANGELOG updated Great work! Excellent diagnosis, clean solution, comprehensive testing. Reviewed: Claude Code (Sonnet 4.5) | Focus: quality, concurrency, tests, perf, security |
Summary
Removes forced main queue dispatch from
ConnectivityObserverManagingnotification methods. Observers are now responsible for their own thread management.Problem
Intermittent test failures on watchOS simulators (particularly Xcode 16.3) where observer notification tests would timeout at 16-18 seconds:
Failed CI Run: https://github.com/brightdigit/SundialKit/actions/runs/19305857262/job/55213040666
Root Cause:
ConnectivityManager(actor) →DispatchQueue.main.async→TestObserver(actor)TestObserverused unstructuredTask {}which compounded the timing issueSolution
Removed
DispatchQueue.main.asyncfrom 6 notification methods:notifyActivationStateChanged(_:)notifyReachabilityChanged(_:)notifyCompanionAppInstalledChanged(_:)notifyPairedStatusChanged(_:)(iOS only)notifyMessageReceived(_:)notifyApplicationContextReceived(_:)Now: Direct actor-to-actor communication without queue hopping
Changes
Files Modified
Sources/SundialKitConnectivity/ConnectivityObserverManaging.swift- Removed DispatchQueue wrappersSources/SundialKitConnectivity/ConnectivityStateObserver.swift- Updated documentationBreaking Change
Before (automatic main thread):
After (explicit threading):
Benefits
✅ Fixes intermittent watchOS test failures
✅ More flexible threading model - observers choose their execution context
✅ Better Swift 6 concurrency compliance - no forced GCD usage
✅ Faster notifications - no queue hopping overhead
✅ Cleaner code - removed weak self capture and guard unwrapping
Migration
This is acceptable as a breaking change because:
Testing
swift buildRelated Issues
Addresses intermittent CI failures:
Previous attempt to fix with timeout increase was insufficient - this addresses the root architectural issue.
Perform an AI-assisted review on