Skip to content

Various tools needed for concurrency (locks, async stream helpers, ...)

License

Notifications You must be signed in to change notification settings

ordo-one/package-concurrency-helpers

Repository files navigation

Swift Linux build Swift macOS build codecov Swift lint Swift outdated dependencies Swift address sanitizerSwift thread sanitizer

Concurrency helpers

Various concurrency related tools, including Lock and async stream additions etc.

To add to your project:

dependencies: [
    .package(url: "https://github.com/ordo-one/package-concurrency-helpers", .upToNextMajor(from: "0.0.1")),
]

and then add the dependency to your target, e.g.:

.executableTarget(
  name: "MyExecutableTarget",
  dependencies: [
  .product(name: "SwiftConcurrencyHelpers", package: "package-concurrency-helpers")
]),

Helpers provided:

  • Safely call a blocking function from an async context:

    let result = await forBlockingFunc {
        // Call your blocking function here.
    }
  • Run async code in a non-async context:

    let result = runSync {
        await someFunction()
    }
  • Simple locks, including a variant that sleeps while waiting for the lock:

    let lock = Lock()
    
    let result = lock.withLock {
        
    }

    …and a variant that spins waiting for the lock:

    let lock = Spinlock()
    
    let result = lock.withLock {
         // Make sure whatever you do here is quick; other threads may be busy waiting.
    }
  • A simple thread-safe box for a value:

    let safeValue = Protected(myValue)
    
    safeValue.write {
        // Have exclusive access to the contents here.
        $0.someField = true
    }
    
    safeValue.read {
        // Have exclusive, read-only access to the contents here.
        if $0.someField {
            
        }
    }
  • An unsafe Sendable wrapper for value types:

    var someNonSendable = 
    
    let wrapper = UnsafeTransfer(someNonSendable)
    
    Task.detached {
        let value = wrapper.wrappedValue
        // Use `value` here, instead of the original name `someNonSendable`.
    }

    There is also an UnsafeMutableTransfer variant, for mutable values.

  • Yield a value to an AsyncStream with back-pressure support (waiting in a Concurrency-safe way until the stream is ready to accept the message):

    guard yieldWithBackPressure(message: , to: continuation) else {
        // Stream was closed or this task has been cancelled.
    }
  • A simple reference-typed box for value types:

    let a = Box(5)
    let b = a
    
    b.value += 1
    
    // 'b' and 'a' both now hold 6.

    It inherits the Equatable, Comparable, CustomStringConvertible, and CustomDebugStringConvertible conformances of the contained type.

  • Determine if a debugger is attached (a Swift version of Apple's C AmIBeingDebugged):

    if isBeingDebugged {
        // Running under, or attached by, a debugger of some kind.
    } else {
        // *Probably* not being debugged.
    }

    Note that this is not foolproof; debuggers can hide themselves from detection.

  • A backport of Apple's AsyncStream.makeStream(of:bufferingPolicy:), to make it available in Swift 5.8 and earlier as well.

  • Calculate the next highest power of two, above a given integer:

    let roundedUp = nearestPowerOf2(27)
    // 'roundedUp' is 32.
  • Track the rate of an event, and run a closure every N events:

    let monitor = ProcessingRate(interval: 10)
    
    while true {
        monitor.checkpoint {
            // Runs every 10 calls to `checkpoint`.
            print("Current rate: \(monitor.ratePerSecond) Hz")
        }
    }
  • Count events, then at designated checkpoints run a closure & reset the count if a set duration has passed since the last reset (or since the counter was created, if never before reset).

    let monitor = TimeIntervalCounter(clock: ContinuousClock(),
                                      timeInterval: .seconds(5))
    
    while true {
        if monitor.incremenet() {
            // It has been at least five seconds since the monitor was last reset.
        }
    
        monitor.checkpoint {
            // Runs at most once every five seconds.
            print("Current count: \(monitor.count)")
            // The count will automatically be reset to zero when this closure exits.
        }
    }