ax-concurrency-ref
npx machina-cli add skill Kasempiternal/axiom-v2/ax-concurrency-ref --openclawConcurrency Profiling Reference
Quick Patterns
Swift Concurrency Instruments Template
| Track | Information |
|---|---|
| Swift Tasks | Task lifetimes, parent-child relationships |
| Swift Actors | Actor access, contention visualization |
| Thread States | Blocked vs running vs suspended |
Color Coding:
- Blue: Task executing
- Red: Task waiting (contention)
- Gray: Task suspended (awaiting)
Statistics:
- Running Tasks: Currently executing
- Alive Tasks: Present at a point in time
- Total Tasks: Cumulative count created
Cooperative Thread Pool Model
| Aspect | GCD | Swift Concurrency |
|---|---|---|
| Threads | Grows unbounded | Fixed to core count |
| Blocking | Creates new threads | Suspends, frees thread |
| Dependencies | Hidden | Runtime-tracked |
| Context switch | Full kernel switch | Lightweight continuation |
Decision Tree
Performance issue with async code?
├─ UI freezing
│ └─ Workflow 1: Main Thread Blocking
│
├─ Parallel work running sequentially
│ └─ Workflow 2: Actor Contention
│
├─ Tasks queued but not executing
│ └─ Workflow 3: Thread Pool Exhaustion
│
├─ High-priority work delayed
│ └─ Workflow 4: Priority Inversion
│
└─ Not sure where the bottleneck is
└─ Start with Quick Checks, then profile
Deep Patterns
Workflow 1: Main Thread Blocking
Symptom: UI freezes, main thread timeline full
- Profile with Swift Concurrency template
- Look at main thread "Swift Tasks" lane
- Find long blue bars (task executing on main)
- Check if work could be offloaded
// WRONG: Heavy work on MainActor
@MainActor class ViewModel: ObservableObject {
func process() {
let result = heavyComputation() // Blocks UI
self.data = result
}
}
// FIX: Offload heavy work
@MainActor class ViewModel: ObservableObject {
func process() async {
let result = await Task.detached {
heavyComputation()
}.value
self.data = result
}
}
Workflow 2: Actor Contention
Symptom: Tasks serializing unexpectedly, parallel work running sequentially
- Enable "Swift Actors" instrument
- Look for serialized access patterns
- Red = waiting, Blue = executing
- High red:blue ratio = contention problem
// WRONG: All work serialized through actor
actor DataProcessor {
func process(_ data: Data) -> Result {
heavyProcessing(data) // All callers wait in line
}
}
// FIX: Mark heavy work as nonisolated
actor DataProcessor {
nonisolated func process(_ data: Data) -> Result {
heavyProcessing(data) // Runs in parallel
}
func storeResult(_ result: Result) {
// Only actor state access is serialized
}
}
Additional fixes for contention:
- Split single actor into multiple (domain separation)
- Use Mutex for hot-path reads (faster than actor hop)
- Reduce actor scope (fewer isolated properties)
Workflow 3: Thread Pool Exhaustion
Symptom: Tasks queued but not executing, gaps in task execution
Cause: Blocking calls exhaust the cooperative thread pool
- Look for gaps in task execution across all threads
- Check for blocking primitives
- Replace with async equivalents
// WRONG: Blocks cooperative thread
Task { semaphore.wait() } // NEVER
Task { let data = Data(contentsOf: fileURL) } // Blocks
Task { Thread.sleep(forTimeInterval: 1.0) } // Blocks
// FIX: Use async APIs
Task { await withCheckedContinuation { ... } } // Non-blocking
Task { let (data, _) = try await URLSession... } // Async
Task { try await Task.sleep(for: .seconds(1)) } // Cooperative
Debug flag: SWIFT_CONCURRENCY_COOPERATIVE_THREAD_BOUNDS=1 detects unsafe blocking in async context.
Workflow 4: Priority Inversion
Symptom: High-priority task waits for low-priority
- Inspect task priorities in Instruments
- Follow wait chains
- Ensure critical paths use appropriate priority
// Explicit priority for critical work
Task(priority: .userInitiated) {
await criticalUIUpdate()
}
// Task groups inherit priority
await withTaskGroup(of: Void.self) { group in
// Child tasks inherit parent priority
group.addTask { await processItem(item) }
}
Quick Checks Before Profiling
Run these before opening Instruments:
1. Is work actually async?
// Sync code in async function still blocks!
func fetchData() async -> Data {
return Data(contentsOf: url) // NOT async - blocks thread
}
2. Holding locks across await?
// DEADLOCK RISK
mutex.withLock {
await something() // Never!
}
3. Tasks in tight loops?
// WRONG: Task creation overhead
for item in items { Task { process(item) } }
// FIX: Structured concurrency
await withTaskGroup(of: Void.self) { group in
for item in items { group.addTask { process(item) } }
}
4. Blocking primitives in async context?
- DispatchSemaphore.wait() - always unsafe
- pthread_cond_wait - always unsafe
- Thread.sleep() - always unsafe
- Sync file/network I/O - always unsafe
Anti-Patterns
// ---- Using Task.detached unnecessarily ----
// WRONG: Loses structured concurrency benefits
Task.detached { await self.process() }
// FIX: Use regular Task (inherits priority, cancellation)
Task { await self.process() }
// Only use Task.detached when you explicitly need to escape actor context
// ---- Profiling without Instruments ----
// WRONG: Adding print timestamps manually
let start = Date()
await work()
print("Took \(Date().timeIntervalSince(start))s")
// FIX: Use Instruments Swift Concurrency template
// Shows actual thread utilization, contention, waits
// ---- Ignoring task group errors ----
// WRONG: Errors silently lost
await withTaskGroup(of: Void.self) { group in
group.addTask { try await riskyWork() } // Error lost!
}
// FIX: Use throwing task group
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { try await riskyWork() }
}
Diagnostics
Common Issues Summary
| Issue | Symptom in Instruments | Fix |
|---|---|---|
| MainActor overload | Long blue bars on main thread | @concurrent, Task.detached, nonisolated |
| Actor contention | High red:blue ratio in actor lane | Split actors, use nonisolated for pure work |
| Thread exhaustion | Gaps across all cooperative threads | Remove blocking calls, use async APIs |
| Priority inversion | High-pri task waits for low-pri | Check task priorities, follow wait chains |
| Too many tasks | Task creation overhead visible | Use task groups instead of individual Tasks |
| Unnecessary detached | Lost cancellation propagation | Use regular Task unless escaping actor needed |
Safe vs Unsafe Primitives
Safe with cooperative pool:
await, actors, task groupsos_unfair_lock,NSLock(short critical sections only)Mutex(iOS 18+)Atomic(iOS 18+)
Unsafe (violate forward progress):
DispatchSemaphore.wait()pthread_cond_waitpthread_mutex_lock(long hold)- Sync file/network I/O in Task
Thread.sleep()in TaskDispatchQueue.syncfrom async context
Debug Environment Variables
SWIFT_CONCURRENCY_COOPERATIVE_THREAD_BOUNDS=1
→ Detects unsafe blocking in async context
SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6
→ Strict executor checking
LIBDISPATCH_COOPERATIVE_POOL_STRICT=1
→ Assert on cooperative pool misuse
Related
ax-concurrency- Swift 6 concurrency patterns, actors, Sendable, Mutexaxiom-lldb- Interactive thread and task state inspection in debuggeraxiom-performance-profiling- General Instruments workflows
Source
git clone https://github.com/Kasempiternal/axiom-v2/blob/main/axiom-plugin/skills/ax-concurrency-ref/SKILL.mdView on GitHub Overview
ax-concurrency-ref offers Instruments-based profiling for Swift Concurrency, covering async/await workflows, actor contention, and thread-pool behavior. It helps diagnose UI blocking, serialized access, and priority inversion by visualizing tasks, actors, and thread states.
How This Skill Works
It uses the Swift Concurrency Instruments Template to surface lanes for Swift Tasks, Swift Actors, and Thread States, with color coding to indicate execution, waiting (contention), and suspension. It provides a Decision Tree and Deep Patterns that guide analysis of common workflows such as Main Thread Blocking, Actor Contention, and Thread Pool Exhaustion.
When to Use It
- UI freezing or main thread blocking
- Parallel work running sequentially due to actor contention
- Tasks queued but not executing because of thread pool exhaustion
- High-priority work delayed due to priority inversion
- Not sure where the bottleneck is — start with quick checks, then profile
Quick Start
- Step 1: Profile with the Swift Concurrency Instruments Template to surface Swift Tasks, Swift Actors, and Thread States
- Step 2: Inspect the Swift Tasks and Swift Actors lanes; look for long blue bars and high red-to-blue ratios
- Step 3: Apply fixes (offload heavy work with Task.detached, mark nonisolated where safe, replace blocking calls with async), then re-profile
Best Practices
- Profile with the Swift Concurrency Instruments Template to collect data on Swift Tasks, Swift Actors, and Thread States
- Monitor the main thread lane for long blue bars and offload work when needed
- Detect serialized access in actors and split work or mark heavy operations as nonisolated
- Replace blocking calls with async equivalents to avoid exhausting the cooperative thread pool
- Decompose large actors into smaller domains and minimize isolated properties to improve concurrency
Example Use Cases
- A UI freeze caused by heavy computation running on the MainActor; offload the work to a detached task and update the UI asynchronously
- Multiple coroutines contending for a single actor, resulting in serialized execution; refactor to nonisolated methods or multiple actors
- Blocking URLSession or file IO inside Tasks leading to thread pool starvation; replace with non-blocking async APIs
- High-priority tasks consistently delayed due to a priority inversion scenario; adjust task priorities and scopes accordingly
- Using the instrumentation to identify and fix bottlenecks in a Swift app's concurrency model