voscausa
voscausa

Reputation: 11706

Cloud Run Readablestream takes 5 minutes to cancel enqueue

I created an adapter-node Sveltekit API endpoint, which streams quotes using a readable stream. When I quit the client route The streaming has to stop. This works fine in development using Sveltekit "npm run dev" (vite dev) or using a windows desktop container (node build).

onDestroy(async () => {
  await reader.cancel();   // stop streaming
  controller.abort();      // signal fetch abort
});

But when I build and deploy the node container on Google Cloud Run the streaming works fine. Except when I quit the client route: the API endpoint keeps on streaming. The log shows: enqueus for 5 more minutes followed by a delayed Readablestream cancel() on the API server.

Why this 5 minutes between the client cancel / abort and the cancel on the server?

The API +server.js

import { YahooFinanceTicker } from "yahoo-finance-ticker";

/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
  const { logging, symbols } = await request.json();

  const controller = new AbortController();
  const ticker = new YahooFinanceTicker();
  ticker.setLogging(logging);

  if (logging) console.log("api ticker", symbols);
  const stream = new ReadableStream({
    start(controller) {
      (async () => {
        const tickerListener = await ticker.subscribe(symbols);
        tickerListener.on("ticker", (quote) => {
          if (logging) console.log("api", JSON.stringify(quote, ["id", "price", "changePercent"]));
          controller.enqueue(JSON.stringify(quote, ["id", "price", "changePercent"]));
        });
      })().catch(err => console.error(`api listen exeption: ${err}`));
    },
    cancel() {   // arrives after 5 minutes !!!
      console.log("api", "cancel: unsubscribe ticker and abort");
      ticker.unsubscribe();
      controller.abort();
    },
  });

  return new Response(stream, {
    headers: {
      'content-type': 'text/event-stream',
    }
  });
}

Route +page.svelte

const controller = new AbortController();
let reader = null;
const signal = controller.signal;

async function streaming(params) {
  try {
    const response = await fetch("/api/yahoo-finance-ticker", {
      method: "POST",
      body: JSON.stringify(params),
      headers: {
        "content-type": "application/json",
      },
      signal: signal,
    });

    const stream = response.body.pipeThrough(new TextDecoderStream("utf-8"));
    reader = stream.getReader();

    while (true) {
      const { value, done } = await reader.read();
      if (logging) console.log("resp", done, value);
      if (done) break;
      ... and more to get the quotes

    }
  } catch (err) {
    if (!["AbortError"].includes(err.name)) throw err;
  }
}
...

Upvotes: 0

Views: 352

Answers (1)

Roopa M
Roopa M

Reputation: 3029

The behavior you are observing is expected, Cloud Run does not support client-side disconnects yet.

It is mentioned in this article, that

Cloud Run (fully managed) currently only supports server-side streaming. Having only "server-side streaming" basically means when the "client" disconnects, "server" will not know about it and will carry on with the request. This happens because "server" is not connected directly to the "client" and the request from the "client" is buffered (in its entirety) and then sent to the "server".

You can also check this similar thread

It is a known issue, there is already a public issue exists for the same. You can follow that issue for future updates and also add your concerns there.

Upvotes: 1

Related Questions