Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Local evaluation: unhandled promise rejection ends the process when the stream drops while the flags API is failing

Aperta Adatta ai principianti
#85 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
78/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
node.js, typescript
Ambito
backend

Direzione di ricerca

Inizia da dist/src/local/streamer.js, in FlagConfigStreamer.start(), e segui il gestore onError che chiama this.poller.start(onChange) senza gestirne la promise. Controlla come l’altro punto di chiamata gestisce gli errori di polling e ispeziona i test pertinenti dello streamer o del poller. Il lavoro è terminato quando un errore del polling di fallback non causa più un rifiuto non gestito, mentre gli errori attesi continuano a raggiungere il chiamante.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug

Expected Behavior

With streamUpdates: true, if the flag stream disconnects while /sdk/v2/flags is also returning errors, the fallback poll's failure is caught and logged. The client keeps serving the configs it has cached, and the stream retry interval recovers it once Amplitude is reachable again.

Current Behavior

The SDK raises an unhandled promise rejection (Error: Flags error response: status=503). On Node 15+ that ends the process by default (exit code 1). So an incident affecting both the stream and the flags API takes down every service using local evaluation, instead of leaving them serving their last configs.

Possible Solution

In dist/src/local/streamer.js, FlagConfigStreamer.start() sets an onError handler that falls back to polling without handling the result:

this.stream.onError = (e) => {
  // …
  this.poller.start(onChange); // not awaited, not caught
  this.startRetryStreamInterval();
};

FlagConfigPoller.start() rethrows when its first poll fails (after calling stop()), so this promise rejects with nothing to handle it. The other call site, in the catch of start(), awaits it, so that one is fine. Catching it would fix this:

this.poller.start(onChange).catch((e) => this.logger.debug('[Experiment] streamer - fallback poll failed', e));

Steps to Reproduce

  1. Save the script below as repro.cjs, next to an install of @amplitude/experiment-node-server. It runs a local server that stands in for both the flags API and the stream.
  2. Run node repro.cjs.
  3. The client starts over the stream and prints started, reading on.
  4. The script then makes both endpoints return 503 and drops the stream. The process exits with Error: Flags error response: status=503 (unhandled rejection, exit code 1), and still running is never printed.
const http = require('node:http');
const { Experiment } = require('@amplitude/experiment-node-server');

const configs = JSON.stringify([{
  key: 'f',
  metadata: { evaluationMode: 'local', flagType: 'release', deployed: true },
  variants: { on: { key: 'on', value: 'on' } },
  segments: [{ variant: 'on' }],
}]);
let down = false;
const streams = new Set();

const server = http.createServer((req, res) => {
  if (down) return res.writeHead(503).end();
  if (req.url.startsWith('/sdk/v2/flags')) return res.writeHead(200).end(configs);
  if (req.url.startsWith('/sdk/stream/v1/flags')) {
    res.writeHead(200, { 'content-type': 'text/event-stream' });
    res.write(`data: ${configs}\n\n`);
    streams.add(res);
    return;
  }
  res.writeHead(404).end();
});

server.listen(0, '127.0.0.1', async () => {
  const url = `http://127.0.0.1:${server.address().port}`;
  const client = Experiment.initializeLocal('key', { serverUrl: url, streamServerUrl: url, streamUpdates: true });
  await client.start();
  console.log('started, reading', client.evaluateV2({ user_id: 'u' }, ['f']).f?.key);

  down = true; // flags API and stream both fail from now on
  streams.forEach((stream) => stream.destroy()); // drop the stream

  setTimeout(() => {
    console.log('still running'); // never printed
    client.stop();
    server.close();
  }, 2000);
});

As a workaround, we mark each poller.start() promise as handled (promise.catch(() => {}) on the returned promise), so callers that await it still see the rejection.

Environment

  • JS Server SDK Version: 1.13.6
  • Installation Method: npm / pnpm
  • Browser and Version: n/a (server-side, Node.js 22 and 24)
Lingua principale
TypeScript
Stelle
2
Fork
9
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di amplitude/experiment-node-server

Tutte le issue di amplitude/experiment-node-server

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.