getsentry / getsentry/sentry-javascript

Node: process never exits since 10.72 — beforeExit session send + client-report flush feed each other (skipOpenTelemetrySetup: true)

Đang mở
#24,262 2 bình luận 0 reaction 1 người được giao Được @logaretm nhận Xem trên GitHub
Bug Client Reports Node.js
Ngôn ngữ chính
TypeScript
Star
8.7k
Fork
1.8k
Merge trung bình
1 ngày 17 giờ
Pull request đã merge (30 ngày)
523

Mô tả

### Is there an existing issue for this?

- [x] I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- [x] I have reviewed the documentation https://docs.sentry.io/
- [x] I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases

### How do you use Sentry?

Sentry Saas (sentry.io)

### Which SDK are you using?

@sentry/node

### SDK Version

10.73.0 (also 10.72.0; 10.54.0 is fine)

### Framework Version

Node 22.22.2

### Link to Sentry event

_No response_

### Reproduction Example/SDK Setup

```js
// repro.mjs — run with a DSN pointing at any HTTP server that answers 200, e.g. a stub on localhost
import * as Sentry from "@sentry/node"
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: "1.0.0", // any release, so the process session is sendable
skipOpenTelemetrySetup: true, // we run our own OTel pipeline elsewhere
tracesSampleRate: undefined, // tracing off
})
console.log("done; letting the process exit naturally")
```

Stub receiver used for the counts below:

```js
import http from "node:http"
let n = 0
http.createServer((req, res) => { let b = ""; req.on("data", c => b += c); req.on("end", () => {
n++; console.log(n, req.url, [...b.matchAll(/"type":"([a-z_]+)"/g)].map(m => m[1]).join(","), b.slice(-160)); res.end("{}") }) }).listen(9999)
```

### Steps to Reproduce

1. `node stub.mjs`
2. `SENTRY_DSN=http://k@localhost:9999/1 node repro.mjs`

### Expected Result

The process sends the session (and at most one client report) and exits.

### Actual Result

The process never exits. The stub receives one `session` envelope and then an endless stream of `client_report` envelopes, each containing:

```json
{"discarded_events":[{"reason":"no_parent_span","category":"span","quantity":1}]}
```

CPU sits at 60–100% against a local stub; against a real DSN the loop is network-bound but the process is still immortal. We hit this in a Cloud Run job running database migrations, which hung until its 600 s task timeout on every deploy after upgrading from 10.54.

What appears to happen:

1. #23731 (10.72) makes `processSessionIntegration` end the session on `beforeExit` when its status is `ok`, so a healthy process now sends a `session` envelope at exit (previously the condition was inverted and healthy processes sent nothing).
2. That transport request is instrumented by the http integration. With no OTel setup and tracing off, its span is dropped and `recordDroppedEvent("no_parent_span", "span")` records an outcome.
3. The client-report `beforeExit` listener flushes the new outcome → another instrumented request → another `no_parent_span` outcome → `beforeExit` again → …

Confirmed by elimination:

| init options | result |
|---|---|
| `{ dsn, release }` | 1 session, exits |
| `{ dsn, release, skipOpenTelemetrySetup: true }` | 1 session + infinite client reports, never exits |
| `{ dsn, release, skipOpenTelemetrySetup: true, sendClientReports: false }` | 1 session, exits |
| same as row 2 on 10.54.0 | nothing sent, exits |

`await Sentry.close()` before exiting also avoids it, but the default behaviour of a process that simply finishes should not be to hang.

### Additional Context

Suggested fixes: don't record a `no_parent_span` outcome for the SDK's own transport requests, and/or don't re-arm the client-report flush from within a `beforeExit` flush (e.g. only flush outcomes that existed before the current `beforeExit` tick).

### Priority

Medium

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.