Interesting Infrequent Polling Use Case!

Hello! I have an interesting infrequent polling use case (~once an hour) that I’d like your advise on.

I’ll preface this with saying that I’ve read some other community threads and the recommended approach on infrequent polling implementation in the Typescript samples repository. That being said, I’m not sure the strategy of “implement polling within an activity, and let that activity fail benignly, retry, etc.” fits my use case. I could be wrong though.

So: our use case.

  1. We have a timer that waits X amount of time. If the time passes before an isCondition is true, we want to execute some business logic (a reminder) and continue waiting for isCondition to become true.

  2. isCondition can become true in two different ways:

  • Listening to a signal from a client, receiving that signal with an update handler, and, along with some other business logic, setting isCondition to true. This is implemented without any issue.

  • Polling an external service to see if a condition is true. If the results from this polling action returns true, we also want to set isCondition to true. This is where I’m not sure what the best pattern is to implement this business logic. Currently, I have:

    • An activity that pings the external service and sees if a condition is true
    • An async function that is called within the workflow itself, runs in the background, and implements a polling loop (using Temporal’s sleep functionality). I (think) I needed to do this so that the polling loop could directly affect the await condition(() => isCondition condition within the workflow itself.

Would this work? Am I missing anything about why this wouldn’t work? Is this a huge anti pattern, and can anyone think of a better way to implement this business logic, as I know this isn’t the recommended infrequent polling architecture discussed in other threads

Thanks in advance! @antonio.perez @maxim

Just had another idea from a colleague:

The original (simpler) conditional logic without the polling looked like this:
await condition(() => isConditionTrueVariable, timeUntilReminder)

To integrate the polling logic into this control flow, could you just do this?

await condition(() => { 
   const pollingResult = await myPollingActivity()
   return pollingResult || isConditionTrueVariable
}, timeUntilReminder)

Where “myPollingActivity” is implemented as suggested in the infrequent polling sample repository?

My biggest concern with the above is not really understanding how/when that T/F callback is invoked, and whether or not calling a polling activity WITHIN that callback is going to work as intended + expected.

Failing activity with a benign exception is the most scalable approach for infrequent polls.

That makes sense, and I’d prefer to set up infrequent polls with an activity!

However, would the code in my follow up comment work? Would I get the behavior I’m expecting if I await for my polling activity within the true/false condition callback?

I think the blocking condition is not a good pattern. I would await the tasks from the condition and the polling activities separately.

Hmm I see what you mean. But if I await the polling activities separately, doesn’t that mean I wouldn’t be able to use the result from the polling activity to interrupt/stop the condition call? I’d like the result of the polling activity to “short-circuit” the condition call before the “timeUntilReminder” is hit.

You can update a variable that the condition waits on when the activity completes.

Ohhh I see!

But for the polling variable that the condition will check and wait for, you let that run in the background?

So something like this?

isPollingResponseTrue = pollingActivityHere()

await condition(() => isPollingResponseTrue || isOtherConditionVariableTrue, timeUntilReminder)

Yes, assuming that

isPollingResponseTrue = pollingActivityHere()

runs in parallel to the condition

1 Like

That makes sense, thanks so much!

Hi Maxim, follow up question. Ran into some issues when actually implementing this. As you can see in the screen shots below, leaving the call to the polling activity like isPollingResponseTrue = pollingActivityHere() (trying to get it to run in the background, parallel to the await condition call) causes the following typing issues:

I included some context to give you an idea of what the business logic I’m trying to implement.

Obviously, these typing issues could be resolved by changing the call to the polling activity to const isBillPlatformInvoiceStatusPaid = await pollInvoicePaymentStatus(billPlatformInvoiceId, invoiceNumber), but at that point, I don’t think the call to the polling activity runs in parallel to the condition? Unless, when the polling activity fails with a benign application failure, it still allows for the Temporal workflow to check the condition?

Thoughts?

Interestingly, awaiting for the polling activity seemed to work (at least in a Temporal time skipping testing environment)! I was running into OOM errors (deployed on Render) when trying to implement it with the background thread method.