Toldy
Toldy

Reputation: 1251

Prevent from executing tasks in same time

I need advices about how to handle the oauth2 token refreshing in my app.

I'm using GRPC to make my http requests to my API which I'm connected with an oauth2 token (it expires after 1 hour).

Before launching every request, I check the token:

Everything seems to work well but in some cases, my client "lose" the token.

The problem is that for 2 requests A & B launched in the exact same time, if the token is outdated, they will both refresh it. My server will generate a newTokenA, return it, generate newTokenB (remove newTokenA), return it. If the response newTokenA arrives to the client after newTokenB, the client token token will not be the good one.

I used a Semaphore to ensure that one call the refreshToken is made at the same time.

But when my Semaphore is waiting, I don't receive any response from my server.

let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)

func authenticate(completion: (GRPCProtoCall) -> (Void)) -> GRPCProtoCall {

    // Wait here if authenticate already called
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

     // If token up to date
    if isOAuth2TokenValid() {
        dispatch_semaphore_signal(semaphore) // Release semaphore
        completion(self.withAuthorization())
        return self
    }

    // Refresh the outdated token
    APIClient.shared.oAuth2AccessTokenRefresh { (response) -> (Void) in
        dispatch_semaphore_signal(semaphore)  // Release semaphore
        completion(self.withAuthorization())
    }

    return self
}

Upvotes: 3

Views: 373

Answers (3)

vadian
vadian

Reputation: 285220

Why not making the method entirely asynchronous? (self seems to be known anyway)

func authenticate(completion: (GRPCProtoCall) -> ()) {

    // If token up to date
    if isOAuth2TokenValid() {
        completion(self.withAuthorization())
    } else {
    // Refresh the outdated token
        APIClient.shared.oAuth2AccessTokenRefresh { response in
           completion(self.withAuthorization())
        }
    }
}

Upvotes: 0

Terence
Terence

Reputation: 443

I think your dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) holding your thread, you can try it with timeout incase last request does not response, and put it before return self

while semaphore.wait(timeout: DispatchTime.now() + Double(5000000000) / Double(NSEC_PER_SEC)) == DispatchTimeoutResult.success {//time out set to 5 seconds
    print("wait")
}

Upvotes: 1

Andreas Oetjen
Andreas Oetjen

Reputation: 10209

First, I would suggest to create the semaphore for 1 resource, not for 0 (which is used for reading purposes):

let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)

Secondly, I think the problem is that you first release the semaphore, and then call the completion block: Do something like this:

// Refresh the outdated token
APIClient.shared.oAuth2AccessTokenRefresh { (response) -> (Void) in
    completion(self.withAuthorization())
    dispatch_semaphore_signal(semaphore)  // Release semaphore at very end
}

Upvotes: 0

Related Questions