Skip to content

modern concurrency#93

Draft
KishanBagaria wants to merge 41 commits into
mainfrom
kb/modern-concurrency
Draft

modern concurrency#93
KishanBagaria wants to merge 41 commits into
mainfrom
kb/modern-concurrency

Conversation

@KishanBagaria
Copy link
Copy Markdown
Member

@KishanBagaria KishanBagaria commented May 31, 2026

What

Moves the entire Messages.app automation path off the legacy DispatchQueue + UnfairLock + semaphore/Thread.sleep model and onto Swift structured concurrency (actors + async/await), with real cancellation, bounded retries, and unit-testable seams. Net: +1,786 / −1,201 across 34 files.

The old model serialized automation by hopping onto a PassivelyAwareDispatchQueue and blocking; sleeps and waits pinned threads, and there was no way to cancel an in-flight operation. The new model funnels every operation through one serial async lane, propagates cancellation cooperatively, and bounds the retry/invalidation loops so a stuck controller can't spin forever.

Key changes

MessagesControllerAutomationLane (new actor) replaces PassivelyAwareDispatchQueue. A single serial async lane that all MessagesController automation funnels through (PlatformAPI.runOnMessagesControllerLane), so only one operation touches Messages.app at a time. It handles:

  • Ordering — each action queues behind the tail of previously-submitted work.
  • Cancellation — caller cancellation propagates to the enqueued task.
  • Passive idle callbacks — epoch-guarded, fire after idleDelay and keep firing while the lane stays quiet; new active work cancels a stale idle cycle.
  • Re-entrancy safety — a @TaskLocal flag turns re-entry into a loud precondition crash rather than a silent deadlock. (Documented why an "inline fallback" is unsafe: the task-local leaks into spawned Task {} children.)

PlatformAPI / MessagesControllerCoordinator — controller actions are now async throws; onMessagesControllerQueuerunOnMessagesControllerLane. The coordinator's invalidation loop is now bounded (max 30) with Task.checkCancellation() checks, and disposal runs inside a cancellation-immune unstructured Task so cancelling a caller mid-construction can no longer leak a half-launched Messages.app.

MessagesController — nearly all automation methods converted from sync throws to async throws (send, react, edit, undo-send, mute, delete, typing, mark-read, deep-link open, etc.). prepareForAutomation/finishedAutomation replaced by a scoped withAutomation { … } / automationDidComplete. Dead paths removed (markAsReadWithMenu, revealReplyTranscriptViaMenu).

Window coordinatorsmakeAutomatable/reset are now async throws, with explicit @MainActor annotations and MainActor.run hops where AppKit work happens. The coordinator protocol is intentionally not @MainActor (implementations do blocking Accessibility I/O that would freeze the UI on the main actor; they hop to @MainActor only for the AppKit bits) — documented inline.

New async primitives:

  • NSWorkspace+AsyncOpen — bridges NSWorkspace.open(…completionHandler:) to async with a single-completion-point continuation, timeout, and cancellation handling. Used for deep-link opens.
  • retry(withTimeout:) — cancellation-aware async retry (re-throws CancellationError, swallows/logs onError failures).
  • Result(catching:) async initializer.

KeyPresser — refactored for testability with injectable key-post and keymap closures (defaults preserve real behavior). Key presses now default to onMainThread: false since the lane is no longer the main thread.

Extensions / IMessageHostwaitForLaunch and related waits converted from Thread.sleep to Task.sleep.

Tests

  • New: MessagesControllerAutomationLaneTests (232 lines — ordering, idle callbacks, re-entrancy, cancellation), NSWorkspaceAsyncOpenTests, RetryTests, KeyPresserTests, and an Eventually async test helper.
  • Removed: PassivelyAwareDispatchQueueTests (along with its subject).

Follow-ups (tracked in todos.md)

  • Adopt -strict-concurrency=targeted and clear the ~26 concurrency warnings with real Sendable/isolation annotations (not blanket @unchecked). Surfaced by plan-eng-review; deferred so it can land as a focused pass with its own QA.
  • Add a factory seam to MessagesControllerCoordinator so the dedup/invalidate/dispose logic is unit-testable without Accessibility + Messages.app.
  • Make withAutomation cleanup fire when makeAutomatable partially succeeds (pre-existing edge: a throw after unhide/resize can leave Messages.app visible with stale windowFramePreEclipse).

@indent
Copy link
Copy Markdown
Contributor

indent Bot commented May 31, 2026

PR Summary

Migrates MessagesController and its window-coordination code from a custom PassivelyAwareDispatchQueue + UnfairLock design to modern Swift Concurrency. Automation serialization is moved into a new MessagesControllerAutomationLane actor with a tail-chained FIFO, AppKit-touching code is isolated to @MainActor, and all public automation methods (sendMessage, setReaction, editMessage, toggleThreadRead, etc.) plus MessagesController.init become async. The custom queue type, its tests, and a small CLI subcommand are deleted, netting a sizable code reduction (-827/+508).

  • Deletes PassivelyAwareDispatchQueue.swift and its tests; removes the test-idle-aware subcommand from IMDatabaseTestBench.
  • Adds MessagesControllerAutomationLane actor in PlatformAPI+MessagesController.swift with run(action) (FIFO via tail chain), setIdleCallback, and idle-after-quiescence scheduling using an idleEpoch counter.
  • Replaces PlatformAPI.onMessagesControllerQueue/messagesControllerQueue with runOnMessagesControllerLane / setMessagesControllerIdleCallback; updates MessagesControllerCoordinator accordingly.
  • Collapses scattered prepareForAutomation()/finishedAutomation() pairs (plus activityLock) into one withAutomation { … } async helper inside MessagesController; converts all automation methods to async throws.
  • Reworks cancelReplyTranscriptViewTask from a DispatchWorkItem into a structured Task that enters the lane after a 1.5s sleep.
  • Rewires activateMessages/deactivateMessages to run on the new lane (was previously inline on the lifecycle thread).
  • Renames idleCallback(...) to makeIdleActivityObserver(...) and changes its return type to () async throws -> Void.
  • Adds AnyObject and @MainActor to the WindowCoordinator protocol; annotates EclipsingWindowCoordinator, SpacesWindowCoordinator, EclipsingDebugger, NSApplication.largestElectronWindow, NSRect.flippedBetweenCocoaAndScreenSpace, and the eclipsing AnchorWindow builders @MainActor.
  • Marks OnboardingManager and SystemSettingsOnboarding @MainActor; reworks polling timer/createWindow/closeWindow/deinit to suit MainActor isolation.
  • Removes the unused revealReplyTranscriptViaMenu method.
  • Drops the "kill PassivelyAwareDispatchQueue" item from todos.md.

Issues

All clear! No issues remaining. 🎉

7 issues already resolved
  • SystemSettingsOnboarding.start()/stop() are now @MainActor, but call sites in IMessageCLI.swift:121-122 (non-isolated async request(), including a defer { SystemSettingsOnboarding.stop() } that cannot await) and IMessageNodeExports.swift:60, 63 (NodeFunction closures) were not updated to await/hop isolation, which will at minimum produce concurrency warnings and become errors under stricter checking. (fixed by commit fb8607d)
  • Lifecycle activate/deactivate now serialized through the automation lane: user-initiated focus/blur of Messages.app blocks behind any in-flight send/edit/react (these can take several seconds, e.g. editMessage's 6s retry), so the eclipse will not unhide responsively and lifecycle AX events stack up in the for await loop until the lane drains. (fixed by commit 39d5074)
  • In the new toggleThreadRead, if the first try await triggerThreadCellAction(threadID:, action: .pin) throws the function returns without calling restorePinsIfNecessary(). The old code's outer defer ran the restore in this case too; this is a small regression for the edge case where the pin action throws mid-way but the pin count nevertheless changed. (fixed by commit 339e1c5)
  • OnboardingManager.createWindow() no longer initializes the window synchronously: the timer body now dispatches a Task { @MainActor … }, so the immediate pollingTimer?.fire() only schedules work asynchronously and onboardingWindow is nil for one runloop turn after the call returns. (fixed by commit a8f6134)
  • Latent deadlock: any caller already running on the automation lane that re-enters runOnMessagesControllerLane will hang permanently (the tail-chained enqueue makes the new task await the still-running ancestor task). Safe today because withAutomation and the idle observer never re-enter, but nothing in code or comments warns against this, so a future contributor adding a withMessagesController call inside an automation body or idle callback will silently freeze the lane. (fixed by commit ea4da68)
  • MessagesController.openDeepLink now imposes a 5s default timeout on the LaunchServices wait (previously the DispatchSemaphore waited indefinitely), so a cold Messages.app launch from MessagesController.init that takes longer than 5s — first run after reboot, slow disks, system under load — will fail init with "Timed out opening URL via LaunchServices after 5.0s" where it would have succeeded before. (fixed by commit 39d1e43)
  • MessagesAppElements's default openDeepLink closure now calls fire-and-forget MessagesController.requestDeepLinkOpen($0) instead of the previous semaphore-blocked open. Inside _mainWindowReally's onError recovery, attempt 0 issues the compose deep link and immediately returns, so the retry loop's 0.2s polling can iterate (and even exit) before LaunchServices has actually processed the open — losing the implicit "deep link has been requested and processed" synchronization the old code provided. (fixed by commit 7d149e8)

CI Checks

Waiting for CI checks...

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR migrates automation from a DispatchQueue-based idle queue to async/await: it adds a MessagesControllerAutomationLane actor, converts MessagesController and PlatformAPI to async/await with new automation boundaries, applies @MainActor to UI/window components, removes PassivelyAwareDispatchQueue and its tests, and adds supporting async utilities and tests.

Changes

Concurrency Infrastructure and MessagesController Async Migration

Layer / File(s) Summary
Automation lane and PlatformAPI wiring
src/IMessage/Sources/IMessage/PlatformAPI+MessagesController.swift, src/IMessage/Sources/IMessage/PlatformAPI.swift
Add MessagesControllerAutomationLane actor; expose PlatformAPI.runOnMessagesControllerLane and setMessagesControllerIdleCallback; move controller creation/disposal and PlatformAPI callsites to use async lane-run closures and register idle callbacks via the lane.
MessagesController async/await conversion and withAutomation
src/IMessage/Sources/IMessage/Messages/MessagesController.swift, src/IMessage/Sources/IMessage/Messages/MessagesController+PlatformOperations.swift
Convert initializer and many user-interaction methods to async/throws; introduce withAutomation/withActivation helpers; convert deep-link open and thread-opening flows to async; update platform-operation wrappers to async/throws.
Remove old automation infra, tests, and TestBench hooks
src/IMessage/Sources/IMDatabaseTestBench/TestBench.swift, todos.md, src/IMessage/Sources/IMessageCore/PassivelyAwareDispatchQueue.swift, src/IMessage/Sources/IMessageTests/PassivelyAwareDispatchQueueTests.swift
Remove TestIdleAware CLI command, delete PassivelyAwareDispatchQueue implementation and its tests, and remove the related TODO entry.
@MainActor isolation: window coordination and UI managers
src/IMessage/Sources/IMessage/WindowCoordination/..., src/IMessage/Sources/IMessage/Extensions.swift, src/IMessage/Sources/IMessage/OnboardingManager.swift, src/IMessage/Sources/IMessage/SystemSettingsOnboarding.swift, src/IMessage/Sources/IMessageCLI/IMessageCLI.swift
Apply @MainActor to WindowCoordinator protocol and implementations (Eclipsing, Spaces), annotate UI helpers and managers (NSRect extension, OnboardingManager, SystemSettingsOnboarding, AuthorizationRequirement.request) and remove DispatchQueue.main.sync/async bridges.
MessagesControllerAutomationLane tests
src/IMessage/Sources/IMessageTests/MessagesControllerAutomationLaneTests.swift
Replace prior idle-queue tests with async tests for the new lane: serialization, ordering, idle callback semantics, cancellation, error isolation, and return-value behavior.
Async helpers, Node exports, and CLI
src/IMessage/Sources/IMessage/Utilities/NSWorkspace+AsyncOpen.swift, src/IMessage/Sources/IMessageCore/Result+Async.swift, src/IMessage/Sources/IMessageNode/IMessageNodeExports.swift, src/IMessage/Sources/IMessage/Messages/MessagesAppElements.swift
Add NSWorkspace.open async wrapper with timeout, add Result.init(catching:) async initializer, export SystemSettingsOnboarding start/stop as async NodeFunctions dispatching to MainActor.run, and change MessagesAppElements default deep-link opener to requestDeepLinkOpen.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

  • beeper/platform-imessage#92: Overlaps on coordinate-space handling and window coordination changes (NSRect flipping and EclipsingWindowCoordinator adjustments).
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether it relates to the changeset. Add a pull request description explaining the concurrency modernization effort, including key changes like PassivelyAwareDispatchQueue removal, async/await conversion, and @MainActor annotations.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'modern concurrency' accurately describes the main objective of the changeset, which involves converting synchronous APIs to async/await and applying @MainActor annotations throughout the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kb/modern-concurrency

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread src/IMessage/Sources/IMessage/Messages/MessagesController.swift
Comment thread src/IMessage/Sources/IMessage/PlatformAPI+MessagesController.swift Outdated
Comment thread src/IMessage/Sources/IMessage/OnboardingManager.swift
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/IMessage/Sources/IMessage/Messages/MessagesController.swift (1)

1317-1349: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Manual activation/deactivation now wait behind unrelated automation.

These handlers are awaited from the AX event loop, but both now queue behind the same serial lane as long-running sends/edits/reactions. A busy automation run can therefore keep Messages hidden after the user manually activates it, then replay stale activate/deactivate transitions once the lane drains. The user-facing hide/unhide path needs to stay responsive; either keep the coordinator-facing part off-lane or coalesce pending lifecycle state before enqueueing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/IMessage/Sources/IMessage/Messages/MessagesController.swift` around lines
1317 - 1349, The activateMessages() and deactivateMessages() handlers are
getting queued behind long-running work because they call windowCoordinator and
window operations inside PlatformAPI.runOnMessagesControllerLane; fix by keeping
the coordinator-facing/window operations off that serial lane or by coalescing
state before enqueuing: move the user-facing state updates (lastActivate,
messagesIsManuallyActivated, log.debug) inside
PlatformAPI.runOnMessagesControllerLane but perform
windowCoordinator.userManuallyActivated(app), userManuallyDeactivated(app),
reset(window), closeAllNonMainWindows(), and resetWindow() outside the lane
(e.g., dispatch them asynchronously on a different queue or call a PlatformAPI
method that runs off the messages controller lane), or alternatively capture and
check the latest messagesIsManuallyActivated value inside the
runOnMessagesControllerLane closure before calling the coordinator to avoid
replaying stale transitions; update activateMessages() and deactivateMessages()
to use one of these approaches referencing activateMessages(),
deactivateMessages(), PlatformAPI.runOnMessagesControllerLane,
windowCoordinator.userManuallyActivated/userManuallyDeactivated, reset(window),
closeAllNonMainWindows(), and resetWindow().
src/IMessage/Sources/IMessage/PlatformAPI+MessagesController.swift (1)

18-21: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

@TaskLocal re-entry state leaks into spawned tasks.

TaskLocal values propagate into child Task {}s, so any delayed follow-up created inside a lane action inherits isExecutingOnLane = true and will hit this assertion even after the original lane work has finished. MessagesController.scheduleCancelReplyTranscriptView() now does exactly that, so normal automation will trap in debug; in release, true nested re-entry still hangs because assert disappears. This guard needs a non-inherited/token-based check, or a separate non-inheriting path for legitimate follow-up tasks.

Also applies to: 35-39, 63-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/IMessage/Sources/IMessage/PlatformAPI`+MessagesController.swift around
lines 18 - 21, The TaskLocal flag isExecutingOnLane wrongly propagates into
child Tasks and causes false positives in run and callers like
MessagesController.scheduleCancelReplyTranscriptView; fix by replacing the
propagation-prone guard with a non-inheriting mechanism or by ensuring follow-up
tasks don’t inherit TaskLocal state. Concretely: either (A) replace `@TaskLocal`
private static var isExecutingOnLane with a simple actor-based tracker/guard
(e.g., LaneExecutionTracker actor with enter()/exit()/isExecuting() used by run)
so spawned tasks don’t see a propagated boolean, or (B) keep the TaskLocal but
change callers that spawn delayed follow-ups (notably
MessagesController.scheduleCancelReplyTranscriptView) to create those follow-ups
with Task.detached { ... } (or otherwise clear the TaskLocal) so they won’t
inherit isExecutingOnLane; update the assertion/check in run to use the new
actor guard or the non-inheriting path accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/IMessage/Sources/IMessage/Messages/MessagesController.swift`:
- Around line 1317-1349: The activateMessages() and deactivateMessages()
handlers are getting queued behind long-running work because they call
windowCoordinator and window operations inside
PlatformAPI.runOnMessagesControllerLane; fix by keeping the
coordinator-facing/window operations off that serial lane or by coalescing state
before enqueuing: move the user-facing state updates (lastActivate,
messagesIsManuallyActivated, log.debug) inside
PlatformAPI.runOnMessagesControllerLane but perform
windowCoordinator.userManuallyActivated(app), userManuallyDeactivated(app),
reset(window), closeAllNonMainWindows(), and resetWindow() outside the lane
(e.g., dispatch them asynchronously on a different queue or call a PlatformAPI
method that runs off the messages controller lane), or alternatively capture and
check the latest messagesIsManuallyActivated value inside the
runOnMessagesControllerLane closure before calling the coordinator to avoid
replaying stale transitions; update activateMessages() and deactivateMessages()
to use one of these approaches referencing activateMessages(),
deactivateMessages(), PlatformAPI.runOnMessagesControllerLane,
windowCoordinator.userManuallyActivated/userManuallyDeactivated, reset(window),
closeAllNonMainWindows(), and resetWindow().

In `@src/IMessage/Sources/IMessage/PlatformAPI`+MessagesController.swift:
- Around line 18-21: The TaskLocal flag isExecutingOnLane wrongly propagates
into child Tasks and causes false positives in run and callers like
MessagesController.scheduleCancelReplyTranscriptView; fix by replacing the
propagation-prone guard with a non-inheriting mechanism or by ensuring follow-up
tasks don’t inherit TaskLocal state. Concretely: either (A) replace `@TaskLocal`
private static var isExecutingOnLane with a simple actor-based tracker/guard
(e.g., LaneExecutionTracker actor with enter()/exit()/isExecuting() used by run)
so spawned tasks don’t see a propagated boolean, or (B) keep the TaskLocal but
change callers that spawn delayed follow-ups (notably
MessagesController.scheduleCancelReplyTranscriptView) to create those follow-ups
with Task.detached { ... } (or otherwise clear the TaskLocal) so they won’t
inherit isExecutingOnLane; update the assertion/check in run to use the new
actor guard or the non-inheriting path accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 89a3f762-fe9a-44ab-ad85-ee9bfc32d990

📥 Commits

Reviewing files that changed from the base of the PR and between 46e6f05 and ea4da68.

📒 Files selected for processing (6)
  • src/IMessage/Sources/IMessage/Messages/MessagesController.swift
  • src/IMessage/Sources/IMessage/PlatformAPI+MessagesController.swift
  • src/IMessage/Sources/IMessage/WindowCoordination/WindowCoordinators/EclipsingWindowCoordinator.swift
  • src/IMessage/Sources/IMessage/WindowCoordination/WindowCoordinators/SpacesWindowCoordinator.swift
  • src/IMessage/Sources/IMessage/WindowCoordination/WindowCoordinators/WindowCoordinator.swift
  • src/IMessage/Sources/IMessageTests/MessagesControllerAutomationLaneTests.swift
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-05-03T17:00:19.662Z
Learnt from: KishanBagaria
Repo: beeper/platform-imessage PR: 69
File: src/IMessage/Sources/IMessage/EventWatcher/EventWatcher+Updates.swift:89-93
Timestamp: 2026-05-03T17:00:19.662Z
Learning: In the beeper/platform-imessage Swift codebase, keep message IDs (`PlatformSDK.MessageID`) as raw GUIDs. When mapping from DB/event rows to `message.id`, set the ID directly from `msgRow.guid` (no GUID→public-ID hashing or transformation). For multi-part messages, append the part index as `_<part.index>` to the GUID-derived ID. During code review, if changes touch message ID creation/mapping, ensure this raw GUID + optional `_<part.index>` suffix behavior is preserved.

Applied to files:

  • src/IMessage/Sources/IMessage/WindowCoordination/WindowCoordinators/WindowCoordinator.swift
  • src/IMessage/Sources/IMessage/WindowCoordination/WindowCoordinators/SpacesWindowCoordinator.swift
  • src/IMessage/Sources/IMessageTests/MessagesControllerAutomationLaneTests.swift
  • src/IMessage/Sources/IMessage/WindowCoordination/WindowCoordinators/EclipsingWindowCoordinator.swift
  • src/IMessage/Sources/IMessage/PlatformAPI+MessagesController.swift
  • src/IMessage/Sources/IMessage/Messages/MessagesController.swift
🪛 SwiftLint (0.63.2)
src/IMessage/Sources/IMessageTests/MessagesControllerAutomationLaneTests.swift

[Warning] 25-25: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 51-51: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 62-62: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 72-72: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 82-82: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 100-100: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 116-116: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 142-142: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 154-154: Attributes should be on their own lines in functions and types, but on the same line as variables and imports

(attributes)


[Warning] 20-20: Magic numbers should be replaced by named constants

(no_magic_numbers)

Comment thread src/IMessage/Sources/IMessage/Messages/MessagesController.swift Outdated
Comment thread src/IMessage/Sources/IMessage/Messages/MessagesAppElements.swift Outdated
Comment thread src/IMessage/Sources/IMessage/Messages/MessagesController.swift
indent Bot and others added 8 commits May 31, 2026 11:22
…n throws too, so a partial pin state can still be cleaned up.

Generated with [Indent](https://indent.com)
Co-Authored-By: KishanBagaria <KishanBagaria@users.noreply.github.com>
…st so the _mainWindowReally retry path doesn't iterate before LaunchServices has processed the open.

Generated with [Indent](https://indent.com)
Co-Authored-By: KishanBagaria <KishanBagaria@users.noreply.github.com>
func idleCallback(observingThreadID threadID: String, statusSender: @escaping (ThreadActivityObservation) -> Void) throws -> ((Quiescence) throws -> Void) {
/// Returns an observer that checks one thread while controller work is idle.
/// The platform-level idle observer calls it repeatedly after active automation drains.
func makeIdleActivityObserver(observingThreadID threadID: String, statusSender: @escaping (ThreadActivityObservation) -> Void) throws -> (() async throws -> Void) {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review finding (P2) — idle observer can send stale activity after disposal

Flagged independently by the correctness and adversarial reviewers during a /ce-code-review pass on this PR.

What's wrong: The idle activity observer (makeIdleActivityObserver) lost the old activityLock.tryLock() skip-if-busy behavior and re-checks isValid only at lane entry — not after each await. After a disposal or an automation burst, the observer can still run its multi-await body and send stale activity (reporting on a controller that's since been torn down, or state that changed mid-flight). The deleted serial queue's tryLock skip used to prevent this.

Suggested fix: Re-check guard isValid else { return } after the withAutomation block (before observing/sending activity), and ideally at the top of the withAutomation closure — reinstating "bail if no longer valid" semantics in a form suited to the actor model.

Location: src/IMessage/Sources/IMessage/Messages/MessagesController.swift (around the makeIdleActivityObserver body, ~L1559)

Deferred from interactive code review (finding #10). Severity P2, confidence 75, reviewers: correctness + adversarial.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant