fix tick worker
This commit is contained in:
parent
f1cec36b6c
commit
1c1bec9d78
197
Tun/Punchnet/Concurrency/PeriodicWorker.swift
Normal file
197
Tun/Punchnet/Concurrency/PeriodicWorker.swift
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
//
|
||||||
|
// PeriodicWorker.swift
|
||||||
|
// Tun
|
||||||
|
//
|
||||||
|
// Created by Codex on 2026/5/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public actor PeriodicWorker {
|
||||||
|
public typealias Operation = @Sendable () async throws -> Void
|
||||||
|
public typealias ErrorHandler = @Sendable (Error) async -> Void
|
||||||
|
|
||||||
|
public enum Mode: Sendable {
|
||||||
|
case fixedDelay
|
||||||
|
case fixedRate
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ErrorPolicy: Sendable {
|
||||||
|
case keepRunning(delay: Duration?)
|
||||||
|
case stop
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Configuration: Sendable {
|
||||||
|
public var interval: Duration
|
||||||
|
public var tolerance: Duration?
|
||||||
|
public var runImmediately: Bool
|
||||||
|
public var mode: Mode
|
||||||
|
public var errorPolicy: ErrorPolicy
|
||||||
|
|
||||||
|
public init(
|
||||||
|
interval: Duration,
|
||||||
|
tolerance: Duration? = nil,
|
||||||
|
runImmediately: Bool = true,
|
||||||
|
mode: Mode = .fixedDelay,
|
||||||
|
errorPolicy: ErrorPolicy = .keepRunning(delay: .seconds(5))
|
||||||
|
) {
|
||||||
|
self.interval = interval
|
||||||
|
self.tolerance = tolerance
|
||||||
|
self.runImmediately = runImmediately
|
||||||
|
self.mode = mode
|
||||||
|
self.errorPolicy = errorPolicy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let configuration: Configuration
|
||||||
|
private let operation: Operation
|
||||||
|
private let onError: ErrorHandler
|
||||||
|
|
||||||
|
private var task: Task<Void, Never>?
|
||||||
|
private var generation: UInt64 = 0
|
||||||
|
|
||||||
|
public init(configuration: Configuration, operation: @escaping Operation, onError: @escaping ErrorHandler = { _ in }) {
|
||||||
|
self.configuration = configuration
|
||||||
|
self.operation = operation
|
||||||
|
self.onError = onError
|
||||||
|
}
|
||||||
|
|
||||||
|
public func start() {
|
||||||
|
guard self.task == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.generation &+= 1
|
||||||
|
let currentGeneration = self.generation
|
||||||
|
let configuration = self.configuration
|
||||||
|
let operation = self.operation
|
||||||
|
let onError = self.onError
|
||||||
|
|
||||||
|
self.task = Task {
|
||||||
|
switch configuration.mode {
|
||||||
|
case .fixedDelay:
|
||||||
|
await Self.runFixedDelay(
|
||||||
|
configuration: configuration,
|
||||||
|
operation: operation,
|
||||||
|
onError: onError
|
||||||
|
)
|
||||||
|
case .fixedRate:
|
||||||
|
await Self.runFixedRate(
|
||||||
|
configuration: configuration,
|
||||||
|
operation: operation,
|
||||||
|
onError: onError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
self.clearIfCurrent(generation: currentGeneration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func stop() async {
|
||||||
|
self.generation &+= 1
|
||||||
|
|
||||||
|
let task = self.task
|
||||||
|
self.task = nil
|
||||||
|
|
||||||
|
task?.cancel()
|
||||||
|
await task?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isRunning: Bool {
|
||||||
|
self.task != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func clearIfCurrent(generation: UInt64) {
|
||||||
|
guard self.generation == generation else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.task = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func runFixedDelay(configuration: Configuration, operation: @escaping Operation, onError: @escaping ErrorHandler) async {
|
||||||
|
if !configuration.runImmediately {
|
||||||
|
guard await sleep(interval: configuration.interval, tolerance: configuration.tolerance) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while !Task.isCancelled {
|
||||||
|
let shouldContinue = await runOperation(
|
||||||
|
operation: operation,
|
||||||
|
onError: onError,
|
||||||
|
errorPolicy: configuration.errorPolicy
|
||||||
|
)
|
||||||
|
guard shouldContinue else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard await sleep(interval: configuration.interval, tolerance: configuration.tolerance) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func runFixedRate(configuration: Configuration, operation: @escaping Operation, onError: @escaping ErrorHandler) async {
|
||||||
|
var nextRun = ContinuousClock.now
|
||||||
|
if !configuration.runImmediately {
|
||||||
|
nextRun = nextRun.advanced(by: configuration.interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
while !Task.isCancelled {
|
||||||
|
let now = ContinuousClock.now
|
||||||
|
if nextRun > now {
|
||||||
|
let delay = now.duration(to: nextRun)
|
||||||
|
guard await sleep(interval: delay, tolerance: configuration.tolerance) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let shouldContinue = await runOperation(
|
||||||
|
operation: operation,
|
||||||
|
onError: onError,
|
||||||
|
errorPolicy: configuration.errorPolicy
|
||||||
|
)
|
||||||
|
guard shouldContinue else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let afterRun = ContinuousClock.now
|
||||||
|
repeat {
|
||||||
|
nextRun = nextRun.advanced(by: configuration.interval)
|
||||||
|
} while nextRun <= afterRun
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func runOperation(operation: @escaping Operation, onError: @escaping ErrorHandler, errorPolicy: ErrorPolicy) async -> Bool {
|
||||||
|
do {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
try await operation()
|
||||||
|
return true
|
||||||
|
} catch is CancellationError {
|
||||||
|
return false
|
||||||
|
} catch {
|
||||||
|
await onError(error)
|
||||||
|
|
||||||
|
switch errorPolicy {
|
||||||
|
case .keepRunning(let delay):
|
||||||
|
if let delay {
|
||||||
|
return await sleep(interval: delay, tolerance: nil)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case .stop:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func sleep(interval: Duration, tolerance: Duration?) async -> Bool {
|
||||||
|
do {
|
||||||
|
try await Task.sleep(for: interval, tolerance: tolerance)
|
||||||
|
return true
|
||||||
|
} catch is CancellationError {
|
||||||
|
return false
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user