Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import CoreAIDiffusionPipeline
import Foundation

actor AppleDiffusionPipelineEngine: AppleDiffusionGenerating {
// Safe: all stored properties are immutable after init, and the class is
// only stored on this actor; deinit merely balances the security-scoped
// access started in init, so there is no shared mutable state to race on.
private final class ScopedResourceLease: @unchecked Sendable {
let url: URL
private let isAccessing: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ actor AppleObjectDetectorEngine: AppleObjectDetecting {
}
}

// ObjectDetector is a value wrapper around Core AI runtime handles, and its
// inference function supports concurrent calls. Keep that unchecked boundary
// local instead of adding a retroactive conformance to Apple's public type.
// Safe: ObjectDetector is a value wrapper around Core AI runtime handles, and
// its inference function supports concurrent calls; instances are additionally
// confined to AppleObjectDetectorEngine's actor-isolated methods. Keep that
// unchecked boundary local instead of adding a retroactive conformance to
// Apple's public type.
private struct SendableObjectDetector: @unchecked Sendable {
let value: ObjectDetector
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ actor AppleImageSegmenterEngine: AppleImageSegmenting {
}
}

// Safe: ImageSegmenter is not Sendable, but every instance is created and
// queried exclusively from AppleImageSegmenterEngine's actor-isolated methods,
// so the wrapped value never crosses an isolation boundary concurrently.
private struct SendableImageSegmenter: @unchecked Sendable {
let value: ImageSegmenter
}
61 changes: 43 additions & 18 deletions CoreAILabCore/Chatterbox/ChatterboxSampling.swift
Original file line number Diff line number Diff line change
@@ -1,37 +1,62 @@
import Foundation
import os

/// A deterministic SplitMix64-based generator whose mutable state is guarded by
/// an unfair lock, which is what makes the `@unchecked Sendable` conformance
/// sound. Draw order still determines the sequence, so callers that need
/// reproducibility must serialize their draws (the Chatterbox engine does, by
/// confining each generator to a single generation pass).
final class ChatterboxRandomGenerator: @unchecked Sendable, RandomNumberGenerator {
private var state: UInt64
private var spareNormal: Double?
private struct State {
var state: UInt64
var spareNormal: Double?
}

private let protectedState: OSAllocatedUnfairLock<State>

init(seed: UInt64) {
state = seed
protectedState = OSAllocatedUnfairLock(
initialState: State(state: seed, spareNormal: nil)
)
}

func next() -> UInt64 {
state &+= 0x9E3779B97F4A7C15
var value = state
value = (value ^ (value >> 30)) &* 0xBF58476D1CE4E5B9
value = (value ^ (value >> 27)) &* 0x94D049BB133111EB
return value ^ (value >> 31)
protectedState.withLock { Self.nextValue(&$0) }
}

func nextUnitDouble() -> Double {
Double(next() >> 11) * 0x1.0p-53
protectedState.withLock { Self.nextUnitDouble(&$0) }
}

func nextNormal() -> Double {
if let spareNormal {
self.spareNormal = nil
return spareNormal
protectedState.withLock { state in
if let spareNormal = state.spareNormal {
state.spareNormal = nil
return spareNormal
}

let first = max(
Self.nextUnitDouble(&state),
Double.leastNonzeroMagnitude
)
let second = Self.nextUnitDouble(&state)
let magnitude = sqrt(-2 * log(first))
let angle = 2 * Double.pi * second
state.spareNormal = magnitude * sin(angle)
return magnitude * cos(angle)
}
}

private static func nextValue(_ state: inout State) -> UInt64 {
state.state &+= 0x9E3779B97F4A7C15
var value = state.state
value = (value ^ (value >> 30)) &* 0xBF58476D1CE4E5B9
value = (value ^ (value >> 27)) &* 0x94D049BB133111EB
return value ^ (value >> 31)
}

let first = max(nextUnitDouble(), Double.leastNonzeroMagnitude)
let second = nextUnitDouble()
let magnitude = sqrt(-2 * log(first))
let angle = 2 * Double.pi * second
spareNormal = magnitude * sin(angle)
return magnitude * cos(angle)
private static func nextUnitDouble(_ state: inout State) -> Double {
Double(nextValue(&state) >> 11) * 0x1.0p-53
}
}

Expand Down
36 changes: 30 additions & 6 deletions CoreAILabCore/Conversion/CoreAIConversionProcessRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,38 @@ actor CoreAIConversionProcessRunner {

private func stop(_ process: Process) async {
guard process.isRunning else { return }

process.interrupt()
try? await Task.sleep(for: .seconds(1))
if process.isRunning {
process.terminate()
}
if process.isRunning {
process.waitUntilExit()
if await waitForExit(of: process, within: .seconds(1)) { return }

process.terminate()
if await waitForExit(of: process, within: .seconds(1)) { return }

kill(process.processIdentifier, SIGKILL)
_ = await waitForExit(of: process, within: .seconds(1))
}

/// Waits for the process to exit without blocking the actor. Polling is
/// used because `execute` already owns the process's `terminationHandler`,
/// so installing a second handler here would break the termination stream.
/// Returns `false` as soon as the grace period lapses (or the surrounding
/// task is cancelled) so the caller can escalate to the next signal.
private func waitForExit(
of process: Process,
within gracePeriod: Duration
) async -> Bool {
let clock = ContinuousClock()
let deadline = clock.now.advanced(by: gracePeriod)
while process.isRunning {
guard clock.now < deadline else { return false }
do {
try await Task.sleep(for: .milliseconds(50))
} catch {
// Cancelled: skip the remaining grace period and escalate.
return !process.isRunning
}
}
return true
}

private func childEnvironment(for executableURL: URL) -> [String: String] {
Expand Down
Loading