Skip to content

Commit b5741bc

Browse files
authored
Merge pull request #326 from SkyCryptWebsite/dev
Stop production container restart loop from unhandled promise rejections
2 parents 70087d2 + cfcedb2 commit b5741bc

6 files changed

Lines changed: 45 additions & 5 deletions

File tree

.changeset/brave-koalas-catch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"skycrypt-frontend": patch
3+
---
4+
5+
Stop the production container restart loop caused by unhandled promise rejections under Node 24. Two changes: (1) the `/stats/[ign]/[[profile]]/card` endpoint now uses `Promise.allSettled` rather than `Promise.all` for the parallel `getProfileStats` / `getNetworth` / `getCombined` calls — when one rejects first, the losing-side promises no longer become orphaned rejections that crash the process. (2) A `process.on("unhandledRejection")` safety net in `instrumentation.server.ts` logs + reports any future orphans to Sentry instead of exiting, since Node 24's default `--unhandled-rejections=throw` is fatal. Combined this ends the ~2-minute restart cycle visible in production logs.

.changeset/pre.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"mode": "pre",
3+
"tag": "beta",
4+
"initialVersions": {
5+
"skycrypt-frontend": "3.6.1"
6+
},
7+
"changesets": [
8+
"brave-koalas-catch"
9+
]
10+
}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 3.6.2-beta.0
4+
5+
### Patch Changes
6+
7+
- Stop the production container restart loop caused by unhandled promise rejections under Node 24. Two changes: (1) the `/stats/[ign]/[[profile]]/card` endpoint now uses `Promise.allSettled` rather than `Promise.all` for the parallel `getProfileStats` / `getNetworth` / `getCombined` calls — when one rejects first, the losing-side promises no longer become orphaned rejections that crash the process. (2) A `process.on("unhandledRejection")` safety net in `instrumentation.server.ts` logs + reports any future orphans to Sentry instead of exiting, since Node 24's default `--unhandled-rejections=throw` is fatal. Combined this ends the ~2-minute restart cycle visible in production logs. ([`9ffadbc`](https://github.com/SkyCryptWebsite/SkyCrypt-Frontend/commit/9ffadbcf6e4c5730756a06039dc23cf9a56eda98))
8+
39
## 3.6.1
410

511
### Patch Changes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "skycrypt-frontend",
3-
"version": "3.6.1",
3+
"version": "3.6.2-beta.0",
44
"private": true,
55
"type": "module",
66
"repository": {

src/instrumentation.server.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,11 @@ Sentry.init({
1818
enabled: !dev,
1919
environment: dev ? "development" : "production"
2020
});
21+
22+
// Node 24's default --unhandled-rejections=throw will kill the process on any
23+
// orphaned promise rejection (e.g. losing-side promises in Promise.all when
24+
// one rejects first). Log and report so we get visibility, but don't crash.
25+
process.on("unhandledRejection", (reason) => {
26+
console.error("Unhandled promise rejection:", reason);
27+
Sentry.captureException(reason);
28+
});

src/routes/stats/[ign]/[[profile]]/card/+server.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,29 @@ export const GET: RequestHandler = async ({ params, request, url }) => {
2828

2929
try {
3030
const user = (await getApiUuidUsername(ign)).data as ModelsPlayerResolve;
31+
// allSettled (not Promise.all) so a losing-side rejection is never orphaned:
32+
// Promise.all rejects on the first failure but leaves the others running, and
33+
// their later rejection becomes an unhandled rejection that crashes Node 24.
3134
const [
3235
// prettier-ignore
33-
profileData,
34-
networthData,
35-
combinedData
36-
] = await Promise.all([
36+
profileResult,
37+
networthResult,
38+
combinedResult
39+
] = await Promise.allSettled([
3740
// prettier-ignore
3841
getProfileStats({ uuid: user.uuid ?? ign, profileId: profile ?? "" }),
3942
getNetworth({ uuid: user.uuid ?? ign, profileId: profile ?? "" }),
4043
getCombined({ uuid: user.uuid ?? ign, profileId: profile ?? "" })
4144
]);
4245

46+
if (profileResult.status === "rejected") throw profileResult.reason;
47+
if (networthResult.status === "rejected") throw networthResult.reason;
48+
if (combinedResult.status === "rejected") throw combinedResult.reason;
49+
50+
const profileData = profileResult.value;
51+
const networthData = networthResult.value;
52+
const combinedData = combinedResult.value;
53+
4354
const { body: renderedHTML } = render(DefaultCard, {
4455
props: {
4556
profile: profileData,

0 commit comments

Comments
 (0)