# Threading model

What runs on the main actor, what runs on a background queue, and how to keep your plugin race-free.

## The two contexts

Your plugin's code executes in exactly two places:

| Context | When | Annotated with |
|---------|------|----------------|
| **Main actor** | Plugin load (once at app launch), preferences UI, settings observation | `@MainActor` on the principal class |
| **Background** | Packet analysis: `init(data:packet:)`, `protocolNodes(_:)`, `value(_:data:)` | `Sendable` on the analyzer protocol |

The host calls analyzer methods from packet-processing queues. The same analyzer class may be instantiated concurrently for different packets, so your analyzer's per-instance state is fine, but **do not share mutable state across analyzer instances**.

## Analyzer Sendable conformance

`CPAPluginAnalyzerProtocol` requires `Sendable`. The pragmatic pattern, used by every in-tree analyzer, is `@unchecked Sendable` on the class:

```swift
final class MyAnalyzer: NSObject, CPAPluginAnalyzerProtocol, @unchecked Sendable {
    // …
}
```

This is safe as long as the analyzer's stored properties are either `let` constants set in `init?(data:packet:)` or only read after `init` returns. Avoid `var` properties that are mutated outside `init`.

## Notifications

Your plugin can ask the host to refresh the UI by posting one of the ``CPAProtocolKonstants`` notifications:

```swift
NotificationCenter.default.post(
    name: NSNotification.Name(CPAProtocolKonstants.kReloadPacketsView.rawValue),
    object: self
)
```

Posting from a background thread is safe; the host subscribes on the main thread and dispatches.

## Talking back to the host

For lookups that need MainActor-isolated state (e.g. "what's my host-app default port for this protocol?"), use the lock-free accessors on ``CPAPacket``:

- `pluginAnalyzerClass(forProtocol:)`
- `pluginHeaderSize(forProtocol:)`
- `pluginAttribute(forKey:)`
- `pluginAnalyzerClass(forKey:)`

These are nonisolated and safe to call from background analysis code.

**Do not** use `MainActor.assumeIsolated` from background threads — it will crash. Use `Thread.isMainThread` checks plus `DispatchQueue.main.sync` if you absolutely must reach actor-isolated state from a background queue.
