1

I have a value that looks like this

   lazy var authHeaders: [String: String] = {
        if shouldTokenBeRefreshed() {
            let semaphore = DispatchSemaphore(value: 0)
            refreshTokens {
                semaphore.signal()
            }
            semaphore.wait()
        }
        return ["Authorization": "Bearer \(module.client.credential.oauthToken)"]
    }()

The idea is, when requesting my auth headers, if my token has expired I will refresh it and then return the new value instead.

func refreshTokens(completion: @escaping () -> Void) {
    guard let token = keychain.get("refresh_token") else { return }

    module.renewAccessToken(
        withRefreshToken: token,
        success: { [weak self] credential, response, parameters in
            guard let self = self else { return }
            self.storeTokens([
                "access_token": credential.oauthToken,
                "refresh_token": credential.oauthRefreshToken
            ])
            completion()
        },
        failure: { error in
            print(error.description)
        })
}

As this is an async operation, I have tried to pause the flow using a Semaphore so I can cause it to continue once the completion block is triggered.

The call is not resolving however and I am unsure why.

Bhavesh Nayi
  • 3,626
  • 1
  • 27
  • 42
Tim J
  • 1,211
  • 1
  • 14
  • 31
  • Hey blukku, I'm not quite sure what your problem is. Which call is not being resolved? The one to the closure? – Carlos M. Apr 26 '19 at 16:15
  • Sorry, it's as if `semaphore.signal()` is never called. It just hangs even thought I can tell from printing that `renewAccessToken` has finished – Tim J Apr 26 '19 at 16:17
  • Does the model.renewAccessToken returns success? Or does it go to the failure closure? Maybe it's hitting any of those other return instructions and not reaching the call to the completion – Carlos M. Apr 26 '19 at 16:20
  • 2
    Your completion block runs on the main-thread.. your semaphore blocks the main thread.. therefore your completion block CANNOT run since the main thread is locked by the semaphore.. in other words.. `DEADLOCK`. – Brandon Apr 26 '19 at 18:06

2 Answers2

1

I'm not sure what you mean by

The call is not resolving

but there are a couple things to note in your example.

  • Be sure that authHeaders are not being initialized on the main thread since the semaphore would block your UI.
  • The only way the semaphore would be signaled to stop waiting is if the completion closure is executed. There are various paths in your code where the completion closure would not execute. In refreshTokens failing the first guard or hitting the failure block would not execute the completion closure, thus the semaphore would not stop waiting.
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
serg.io
  • 11
  • 2
1

This is not how you should use DispatchSemaphore.

Please do not force async code to be synchronous.

You will need to refactor your code to better handle the async nature of what you are trying to achieve.

A completion handler is the simpler, more efficient way to go. If you are trying to avoid this for some reason, take a look at PromiseKit or something other async helper library.

An alternative suggestion would be rather than updating your tokens pre flight, update them on a 401 and then replay your original, updated request.

nodediggity
  • 2,458
  • 1
  • 9
  • 12
  • 2
    I disagree. Sometimes using DispatchSemaphore, to make some code synchronous is applicable, as long as it is used with care. – Said Al Souti Sep 13 '20 at 15:05