2021/10/14 Workers Runtime Release Notes

Changes this week:

  • request.signal will always return an AbortSignal.
  • Cloudflare Workers’ integration with Chrome DevTools profiling now more accurately reports the line numbers and time elapsed. Previously, the line numbers were shown as one line later then the actual code, and the time shown would be proportional but much longer than the actual time used.
  • Upgrade to v8 9.5. See V8 release v9.5 · V8 for more details.
4 Likes

Hi,

Can you clarify this?

Do you mean that every Request() object will have a .signal property and calling .signal.abort() will abort the underlying Fetch() request?

What is the expected behavior when we call .signal.abort() on the Worker incoming request (which is also a Request())?

Thank you.

Hi @famzah,

Yes, but if the script didn’t provide the signal property during Request construction, then the signal property that is created is just a dummy object with no effect.

No, that’s not what AbortSignal.abort() does. AbortSignal.abort() is a static method which returns a new, immediately-aborted AbortSignal. It cannot be called on an actual AbortSignal object. In this way, it is conceptually similar to the Promise.reject() function, which creates new, immediately-rejected promises.

To use an AbortSignal to cancel a fetch(), first create an AbortController, pass its signal property to the Request constructor, and then you can call abort() on the AbortController to abort any fetch() using that Request.

Here’s an example: Cloudflare Workers

Harris

Thank you for the clarification and excuse my confusion about AbortSignal.abort().

Still I didn’t understand what’s request.signal useful for? Your example shows how to assign signal but not how to use it in the case we got it from the request.signal property.

Is its purpose that we subscribe to the abort event like shown in this example using
addEventListener() or .onabort? Are there other use-cases?

I think being able to subscribe to its abort event is probably the main purpose for exposing it. I’ve actually never used the AbortController/AbortSignal API myself, so I would defer to an expert.

2 Likes

@harris Is it really supposed to work like this?

await fetch(somehost, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(reqBody),
      signal: AbortSignal.timeout(milliseconds),
    })

Adding a few of these fetch promises to Promise.all - terminates ALL requests as soon as the abort signal is reached and does not trigger for the individual promise/fetch like expected. This happens on production workers, not in the quick edit.

Update: To make things even worse, if I make multiple requests to a worker that do the promise.all, then ALL of these will be cancelled too, if any of these fetches receive an abortSignal.

@thomas4 are you maybe just missing some .catch()es? It’s expected that Promise.all() is rejected by the first exception thrown: Promise.all() - JavaScript | MDN

I wrote the following worker to test the behavior: Cloudflare Workers

That worker adds three fetches to a Promise.all() promise. Each fetch is initiated at the same time to an endpoint which returns a response after 3 seconds. The first fetch has an AbortSignal.timeout(1000), the second .timeout(2000), and the third .timeout(4000).

The behavior I see, regardless of whether it’s in the fiddle or deployed to production, is that the first request to time out rejects the Promise.all() promise as expected, but the second one is not rejected until its later timeout fires. Meanwhile, the third receives a response after three seconds, because its timeout never fires.

If the behavior you’re reporting doesn’t match this, could you provide a reproduction?

1 Like

@harris Thanks for the quick reply, it would seem that if processing happens in the fetch.then() and it takes too long without a return, it would trigger the abortSignal as well. Could that be the case?

I think the AbortSignal also covers the time spent reading the response body, meaning if the worker doesn’t finish receiving the response body within the specified timeout, the response body ReadableStream will be errored. Could that explain the behavior you’re seeing?

2 Likes

Once I removed the body parsing from fetch.then and return it to it’s own function, then AbortSignal.timeout works just as expected, so it does count the whole fetch event.