Lock() and do-block syntax

I recently stumbled upon a combination of lock and do - similar to open and do.

lock(x.lock) do
        # do something unsafe here
end

It is unclear that the do-block syntax automatically calls unlock after the work inside the block is done (at least, I cannot derive that from the documentation - and it doesn’t feel right to extrapolate the open/close behavior to this). The lock and unlock matching requirement is pretty clear from the documentation.

Context: I am reading lots of code these days, and I encountered the behavior above in the LRUCache package.

Am I missing something? Maybe some more general principle about the do-block?

To remove any ambiguity, can I safely use lock + do-block syntax in my code?

there is nothing particularly special about do notation, it is simply a syntax trick to create an anonymous function and pass it as the first argument of the preceding call (for any call, not just lock). you can read the docs for lock(f::Function, lock) here:

lock(f::Function, lock)

  Acquire the lock, execute f with the lock held, and release the lock when f
  returns. If the lock is already locked by a different task/thread, wait for
  it to become available.

  When this function returns, the lock has been released, so the caller should
  not attempt to unlock it.

1 Like

I would disagree: The do-syntax is quite special. It creates an anonymous function and passes it as the first argument to the preceding function.

https://docs.julialang.org/en/v1/manual/functions/#man-anonymous-functions

Thanks for the instant answer - you are right - I was looking at the wrong signature (I was biased - maybe because in the same codebase, it also uses lock/unlock matching when do-block is not used).

Just to explain that, I believe manual try-finally blocks are used in that codebase when we want to return from inside that block, e.g. in https://github.com/JuliaCollections/LRUCache.jl/blob/618677182498ceeaae16dcdd4f49aad29b5c4c0b/src/LRUCache.jl#L81-L88. We can’t do that bit with the do-block form, since that would just define the return for the anonymous function, not the outer function.

Later versions of Julia have a macro form Base.@lock (which is exported on Julia 1.9). This is nice because it does not need to define an anonymous function (such closures can cause performance issues in some cases, xref #15276), and one can return from it.

1 Like

Thanks - I can see here the return from the anonymous function and also the dedicated return from the outer one.

However, this was a lame omission on my part: I wasn’t paying enough attention to the different signatures. The more frequent syntax is f(x) do y - however, because the anonymous function has no argument, it seems that f(x) do is also legitimate (I don’t think I encountered that before).

Thanks for the clarifications.

1 Like