Base.Semaphore and @spawn: The Illusion of Control vs. Explicit Resource Management

All the best to everyone

The Problem
When using Base.Semaphore to limit the number of concurrently executing heavy tasks, there is a fundamental difference in the behavior of the two approaches:
Option A (Syntactic sugar do):

Base.acquire(sem) do
    data = take!(chan)
    @spawn handle(data)
end

Option B (Explicit acquire/release via closure) in asynchrony:

Base.acquire(sem)
data = take!(chan)
@spawn try 
    handle(data)
finally
    Base.release(sem)
end

Option A works.

Option B, at least in complex hybrid parallelization schemes, leads to erratic semaphore behavior and stalling.

And I can’t understand whether this is a bug or whether option A is ideologically and/or practically the only correct one?

In the latter example, the take! is outside the try/finally block. So, if take! errors, the semaphore is not released.
Otherwise the two should be identical.

Try moving the take! into the try-block. If that doesn’t fix it, can you come up with a minimal working example so we can investigate?

Also depending on the context data might be being captured in a closure in the spawn macro in a way you don’t want it to be.
Ref: Boxed Variables · OhMyThreads.jl
Due to Julia 's scoping rules without the full context it is hard to say if that is the issue here.