bug: Webhook requests repeatedly register middleware on cached bots until the call stack overflows
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 78/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- typescript
Direzione di ricerca
Start in start.ts at the webhook handler and then inspect src/bots.ts at getBot and the bot cache. Reproduce the repeated registration with the minimal middleware example from the issue, then verify that webhook requests reuse a configured bot without growing its middleware chain and that a terminating middleware completes normally.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
[!NOTE]
This issue was primarily written by GPT-5.6 Sol xhigh in ChatGPT Work.
Summary
In webhook mode, JsonBot registers the same application composer on the bot for every incoming POST request:
const bot = getBot(botMode, botToken);
if (bot) {
bot.use(composer);
return await webhookCallback(bot, "std/http")(req);
}
Source: https://github.com/SpEcHiDe/JsonBot/blob/3770d5cdcb40fe1ac8771d344eb5a1a04e5eca50/start.ts#L38-L46
However, getBot caches and returns the same Bot instance for subsequent requests using the same token:
const bots = new Map<string, Bot<MyContext>>();
export function getBot(mode: string, token: string) {
let bot = bots.get(token);
if (!bot) {
// Construct and configure the bot
// ...
bots.set(token, bot);
}
return bot;
}
Source: https://github.com/SpEcHiDe/JsonBot/blob/3770d5cdcb40fe1ac8771d344eb5a1a04e5eca50/src/bots.ts#L4-L8
Source: https://github.com/SpEcHiDe/JsonBot/blob/3770d5cdcb40fe1ac8771d344eb5a1a04e5eca50/src/bots.ts#L59-L65
Consequently, every webhook request permanently appends another copy of the application composer to the cached bot's middleware chain. The chain grows without bound until dispatching an update throws:
RangeError: Maximum call stack size exceeded
at .../src/composer.ts:147:12
at .../src/composer.ts:149:15
at .../src/composer.ts:149:15
...
The repeated composer.ts:149 frames in the reported stack trace are consistent with grammY recursively entering the increasingly deep chain of composed middleware.
Minimal reproduction
The failure still occurs when the registered application composer contains only a terminating leaf middleware. The leaf does not call next and therefore cannot itself recurse into downstream middleware:
import { Composer } from "grammy/mod.ts";
let leafCalls = 0;
const applicationComposer = new Composer();
// This is a terminating leaf. It does not accept or call `next`.
applicationComposer.use(() => {
leafCalls++;
});
// Represents the Bot instance retained in JsonBot's `bots` map.
// Bot extends Composer, so the relevant composition behavior is identical.
const cachedBot = new Composer();
// Represents repeatedly executing `bot.use(composer)` inside the
// webhook request handler.
for (let request = 0; request < 20_000; request++) {
cachedBot.use(applicationComposer);
}
try {
await cachedBot.middleware()(
{} as never,
() => Promise.resolve(),
);
} catch (error) {
console.log(error instanceof RangeError); // true
console.log(leafCalls); // 0
}
This produces RangeError: Maximum call stack size exceeded. The precise number of registrations required depends on the JavaScript engine and its available stack size.
Notably, leafCalls remains 0. The terminating application middleware is never reached before the stack overflows.
No Telegram API request or real context object is needed to reproduce the failure.
Registering applicationComposer once and then invoking the resulting middleware for 20,000 requests completes successfully.
Expected behavior
The application composer should be registered once when a bot is created. Processing additional webhook requests should not mutate or enlarge the bot's middleware chain.
A terminating application composer should then handle an update and stop the middleware flow normally.
Actual behavior
Every webhook request registers the application composer again on the cached bot.
The middleware chain and its retained closures therefore grow with the number of requests. After enough requests, middleware dispatch exceeds the maximum call-stack size.
Once this happens, later requests for the same cached bot continue to fail because the oversized middleware chain remains attached and continues growing.
The stack overflow occurs before execution reaches even the first terminating application middleware.
Suspected cause
Composer.use appends middleware by replacing the current handler with a new handler that wraps the complete previous handler:
use(...middleware: Array<Middleware<C>>) {
const composer = new Composer(...middleware);
this.handler = concat(this.handler, flatten(composer));
return composer;
}
Conceptually, repeated registration produces:
H₁ = concat(H₀, applicationComposer)
H₂ = concat(H₁, applicationComposer)
H₃ = concat(H₂, applicationComposer)
...
Hₙ = concat(Hₙ₋₁, applicationComposer)
When Hₙ handles an update, concat invokes its first handler before it can invoke andThen:
return async (ctx, next) => {
let nextCalled = false;
await first(ctx, async () => {
if (nextCalled) throw new Error("`next` already called before!");
else nextCalled = true;
await andThen(ctx, next);
});
};
Execution must therefore descend through the accumulated outer handlers:
Hₙ → Hₙ₋₁ → Hₙ₋₂ → ... → H₀
Only after that descent could the first registered copy of applicationComposer run and terminate the middleware flow. With enough registrations, the call stack is exhausted during the descent, before that leaf is reached.
The terminating nature of the application composer prevents later middleware from running, but it does not prevent the cached bot's repeatedly wrapped outer handler from overflowing the stack.
The bot.use(composer) call itself does not normally throw immediately. The overflow occurs when grammY subsequently dispatches an update through the accumulated middleware chain.
The newer grammY revision currently pinned by JsonBot retains the same relevant Composer.use and concat implementation, so updating the grammY revision alone does not resolve this application lifecycle problem.
Suggested fix
The application composer should be registered exactly once when each bot is created.
Webhook requests should retrieve the already-configured bot and dispatch the update without mutating its middleware chain.
- Lingua principale
- TypeScript
- Stelle
- 27
- Fork
- 8
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Nessun modello di pull request
- Nessuna guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Issue simili
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
awslabs/visual-asset-management-system#804 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
restatedev/sdk-typescript#813 ·
-
bug desktop P1 session XS
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
Merit-Systems/x402scan#1312 ·
-
type:bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
Azure/apiops-cli#331 ·
I maintainer di solito rispondono entro 3 giorni