Skip to content

Instantly share code, notes, and snippets.

@KaQuMiQ
Last active October 9, 2019 16:19
Show Gist options
  • Save KaQuMiQ/b04761bdee569591400a842e9e92e6b5 to your computer and use it in GitHub Desktop.
Save KaQuMiQ/b04761bdee569591400a842e9e92e6b5 to your computer and use it in GitHub Desktop.
import Darwin
internal enum AtomicFlag {
internal typealias Pointer = UnsafeMutablePointer<atomic_flag>
@inline(__always)
internal static func make() -> Pointer {
let pointer = Pointer.allocate(capacity: 1)
pointer.pointee = atomic_flag()
return pointer
}
@inline(__always)
internal static func destroy(_ pointer: Pointer) {
pointer.deinitialize(count: 1)
pointer.deallocate()
}
@discardableResult @inline(__always)
internal static func readAndSet(_ pointer: Pointer) -> Bool {
return atomic_flag_test_and_set(pointer)
}
@inline(__always)
internal static func clear(_ pointer: Pointer) {
atomic_flag_clear(pointer)
}
}
import Darwin.POSIX
internal let n_sec_in_m_sec: Int = 1_000_000 // nanoseconds in milisecond
internal let n_sec_in_sec: Int = 1_000 * n_sec_in_m_sec // nanoseconds in second
internal enum PollingError: Error {
case timeout
case canceled
}
CancelationToken
public func blockingPolling<T>(pollingSetup: PollingSetup, _ action: @autoclosure () throws -> T?) throws -> T {
assert(pollingSetup.timeout > 0)
assert(pollingSetup.interval >= 100) // interval should be at least 100ms
var timeoutTimeLeft = __darwin_time_t(pollingSetup.timeout) * n_sec_in_sec
var remained: timespec = .init()
var requested: timespec = timespecFrom(miliseconds: pollingSetup.interval)
while timeoutTimeLeft > 0 {
guard !pollingSetup.cancelationToken.isCanceled else {
throw PollingError.canceled
}
if let result = try action() {
return result
} else { /* continue waiting */ }
// we might do the last interval shorter when there is time left but shorteer than interval time
loop: while true {
switch nanosleep(&requested, &remained) {
case 0: // success
break loop
case EINTR: // interupt
requested.tv_nsec = remained.tv_nsec
case let errorCode: // unexpected
fatalError("nanosleep error: \(errorCode)")
}
}
timeoutTimeLeft -= (requested.tv_nsec - remained.tv_nsec)
timeoutTimeLeft -= (requested.tv_sec - remained.tv_sec) * n_sec_in_sec
}
throw PollingError.timeout
}
fileprivate func timespecFrom(miliseconds: UInt) -> timespec {
return .init(
tv_sec: __darwin_time_t(miliseconds / 1000),
tv_nsec: Int(miliseconds % 1000) * n_sec_in_m_sec
)
}
/// Token that can be used to cancel scheduled tasks.
/// CancelationToken cannot be reused, it is useles after task becomes canceled.
public final class CancelationToken {
private let flag: AtomicFlag.Pointer = AtomicFlag.make()
internal init() {
AtomicFlag.readAndSet(flag)
}
internal static let canceled: CancelationToken = {
let token: CancelationToken = .init()
token.cancel()
return token
}()
deinit {
AtomicFlag.destroy(flag)
}
/// Cancel associated task if able. Cancelation is never done automatically.
/// Does nothing if already canceled or task is already running and cannot be stopped.
public func cancel() {
AtomicFlag.clear(flag)
}
/// Check state without changing it
internal var isCanceled: Bool {
return !AtomicFlag.readAndSet(flag)
}
}
public final class PollingSetup {
/// timeout time in seconds
public let timeout: UInt
/// time between each action execution in miliseconds
public let interval: UInt
/// token which can be used to cancel polling
public let cancelationToken: CancelationToken = .init()
/// - parameter timeout: timeout time in seconds
/// - parameter interval: polling interval in milisecond, default is one second (1000ms)
public init(timeout: UInt, interval: UInt = 1000) {
precondition(timeout > 0)
precondition(interval >= 100, "interval should be at least 100ms")
self.timeout = timeout
self.interval = interval
}
}
public extension PollingSetup {
/// each time returns a new instance of PollingSetup with default
/// configuration for validating tickets via bluetooth
static var forTicketValidation: PollingSetup {
return .init(timeout: 10, interval: 500)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment