Marcus Leon Marcus Leon - 3 months ago 29
Swift Question

NSLock.lock() executed while lock already held?

I'm reviewing some Alamofire sample Retrier code:

func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }

if let response = request.task.response as? HTTPURLResponse, response.statusCode == 401 {

if !isRefreshing {
refreshTokens { [weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }

strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }

} else {
completion(false, 0.0)

I don't follow how you can have
on the first line of the function and then also have that same line
within the closure passed to

If the first lock is not released until the end of the
method when the
unlock is executed then how does the the second
successfully execute while the first lock is held?

Rob Rob

The trailing closure of refreshTokens, where this second call to lock()/unlock() is called, runs asynchronously. This is because the closure is @escaping and is called from within a responseJSON inside the refreshTokens routine. So the should method will have performed its deferred unlock by the time the closure of refreshTokens is actually called.

Having said that, this isn't the most elegant code that I've seen, where the utility of the lock is unclear and the risk of deadlocking is so dependent upon the implementation details of other routines. It looks like it's OK here, but I don't blame you for raising an eyebrow at it.