Using semaphores in actor code

Binary vs. counting is not the problem. The problem is that there's no such thing as acquiring a semaphore; the semaphore gets signaled by an arbitrary, unpredictable thread. This is not a problem with locks because the thread that acquired a lock is required to be the thread that releases the lock.

An API like this could be implemented in a way that avoids priority inversion:

struct AsyncLimiter: ~Copyable {
  init(limit: Int, inOrder: Bool = false)

  /// At most `limit` tasks will be performing the operation concurrently
  func withControl<R,E>(operation: () async throws(E) -> R) async throws(E) -> R
}

Note that this is just a sort of async mutex when limit == 1.

It would need to be implemented in the Swift concurrency library because of the transitive inheritance issues that @Alejandro points out (which is something we already handle with e.g. Task.get()), but it would be implementable. How many of your uses of semaphores would that eliminate?

11 Likes