Skip to content

fix(astro): Handle errors in middlewares better #16693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

mydea
Copy link
Member

@mydea mydea commented Jun 23, 2025

Hopefully fixes #16491

I could not get a reproducing test there for this 🤔 I figure this is "safe" to do, but not quite sure how/when this would happen. I would assume this was "introduced" by #15995, maybe @Fryuni has a clue how/when that could happen and if this is a reasonable change 🤔

@mydea mydea self-assigned this Jun 23, 2025
@Fryuni
Copy link
Contributor

Fryuni commented Jun 23, 2025

The error description indicates that calling enqueue on a closed controller fails, not close. close should be a no-op on an already closed controller.

The controller may be closed by downstream code for multiple reasons, like a canceled request or a closed connection.
In theory once the request is closed we should stop the iterations and close the original stream. But we could just consume it to the end discarding everything.

I think (would have to look at the docs) there is a .closed property we could check before calling enqueue

@mydea
Copy link
Member Author

mydea commented Jun 24, 2025

ahh I see, that makes sense. Should we maybe simply not send errors from this to sentry, then 🤔 is there a value in doing this? So just:

try {
                    for await (const chunk of originalBody) {
                      const html = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
                      const modifiedHtml = addMetaTagToHead(html);
                      controller.enqueue(new TextEncoder().encode(modifiedHtml));
                    }
                  } catch (e) {
                    controller.error(e);
                  } finally {
                    controller.close();
                  }

would lose some visibility, but probably ok...? We could just logger.error the error instead, I suppose... this does not feel actionable for a user, I'd say 🤔 cc @AbhiPrasad

@Fryuni
Copy link
Contributor

Fryuni commented Jun 24, 2025

A bit tricky 🤔

The iterator can fail with a user error from a component, but the body of the loop can error out of the user's control.

We'd be switching from reporting unactionable errors to not reporting actionable ones.

If we hand roll the iteration using the interface and a while loop we could have the try/catch on each part with different error handlers.

@Fryuni
Copy link
Contributor

Fryuni commented Jun 24, 2025

Here is one way to avoid that:

// Assign to a new variable to avoid TS losing the narrower type checked above.
const body = originalBody;

async function* bodyReporter(): AsyncGenerator<string | Buffer> {
  try {
    for await (const chunk of body) {
      yield chunk;
    }
  } catch (e) {
    // Report stream errors coming from user code or Astro rendering.
    sendErrorToSentry(e);
    throw e;
  }
}

const reportedBody = bodyReporter();

try {
  for await (const chunk of reportedBody) {
    const html = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
    const modifiedHtml = addMetaTagToHead(html);
    controller.enqueue(new TextEncoder().encode(modifiedHtml));
  }
} catch (e) {
  controller.error(e);
} finally {
  controller.close();
}

@mydea
Copy link
Member Author

mydea commented Jun 25, 2025

nice, thank you, that makes sense to me! is there a good way to test that this actually works? 😅

@mydea mydea force-pushed the fn/astro-controller branch from 98aeae9 to 32e798f Compare June 25, 2025 11:26
@mydea mydea changed the title fix(astro): Try-catch controller.close() usage fix(astro): Handle errors in middlewares better Jun 25, 2025
@mydea mydea force-pushed the fn/astro-controller branch from 32e798f to bd0f08c Compare June 27, 2025 08:42
@Fryuni
Copy link
Contributor

Fryuni commented Jun 27, 2025

Hum... A little tricky because this is deeply inside of Astro.

You can make a component with await timers.setTimeout(5000) in the frontmatter and then close the connection while waiting for it. It should cause the enqueue on a closed controller. On a unit test you just need to close the response body and produce a new chunk from the inner response after that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

TypeError: Invalid state: Controller is already closed from Sentry Astro middleware
4 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy