Behaviour of dispatch group tasks with completions?

Hi,

I have pseudo code for a repeating task of work:
Code Block
api_call("/status") {
if response.statusCode == 418 {
api_call("/rotate-credentials") { ... }
}
else {
doSomeWork()
}
sleep(60)
api_call("/status")
}

I want to make a call to /status roughly every 60 seconds, but if the response is a 418 then I want to complete a call to /rotate-credentials before continuing with the next status request.

There are other api calls going on as well, triggered by user interaction. These other api calls should wait until the /status call is completed in full (including if a credential rotation is triggered).

This implies a serial queue? But that won't wait for the completions to finish, so I should be looking at a dispatch group enter(), leave() and wait() right?

I can't find/understand from the docs where completion handlers go, in terms of execution, relative to dispatch queues. So where is the completion handler execution of the subsequent call to /rotate-credentials ?

If I wrap the status call in a dispatch group, is the rotate-credentials completion handler execution covered by this group? So no further tasks will begin until that task is complete?

The credentials are used for digest signing of http requests, hence why I want to pause other api calls until the rotate-credentials is complete.

thanks,

Replies

So I implemented the control using a semaphore, and it seems to be working fine. Though not load tested. Semaphores seem easier to understand than dispatch groups, more explicit. But I'm not 100% which is idiomatic for Swift.

In the closure of the api_call to rotate-credentials I signal the semaphore like this :

Code Block
{
// rotate-credentials api call closure
defer {
self.semaphore.signal()
print("semaphore signal - apiCredential")
}
// main closure code
}

The debug output gives the logical flow:
Code Block
ident loaded: 7CC20A7BEAF242C8B853BAE960402EC7
secret loaded: A52189B0CA8D42ABAF22047439057B44
semaphore wait over - getnodestatus
Req http://127.0.0.1:8000/vendor/node/status, Resp 418
💥 credential trigger 💥
Triggered credential rotation for 7CC20A7BEAF242C8B853BAE960402EC7 with secret 0356FC2123804D4DB232379BD88CA06B, signed with A52189B0CA8D42ABAF22047439057B44.
Req http://127.0.0.1:8000/vendor/node/credential, Resp 201
KeychainManager: Successfully deleted data
KeychainManager: Item added successfully
Primary credential now: 0356FC2123804D4DB232379BD88CA06B
semaphore signal - apiCredential
semaphore wait over - getnodestatus
Req http://127.0.0.1:8000/vendor/node/status, Resp 204
semaphore wait over - getnodestatus
semaphore signal - getnodestatus
Req http://127.0.0.1:8000/vendor/node/status, Resp 204
semaphore signal - getnodestatus
semaphore wait over - getnodestatus
Req http://127.0.0.1:8000/vendor/node/status, Resp 204
semaphore signal - getnodestatus







OK, first things first, your call to sleep on line 8 of your first post is Not Good™. That call blocks whatever queue that the api_call subsystem is using for its completions. This is going to result in one of two problems:
  • If that subsystem uses a single serial queue for its completions, you block the subsystem entirely.

  • If it uses multiple serial queues — or, worse yet, a concurrent queue — you expose yourself to thread explosion.

A better approach is to use a timer for this delay.



Beyond that, I’m concerned about your overall model. IMO the best way to model complex state transitions like this is via a state machine. Trying to glom together in an ad hoc fashion is likely to leave states unhandled, and such bugs tend to show up in rare circumstances that are very hard to investigate.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"