`importLibrary` loops forever (tab freeze / OOM) when the bootstrap callback receives an error payload
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 72/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- javascript, typescript
Línea de trabajo
Start in dist/index.js by tracing bootstrap(), namespace.ib, and namespace.importLibrary when the callback receives the shown error payload. Run the browser or Node reproduction from the linked gist, then verify that the error path settles without freezing or growing memory while the ?ok control still resolves.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Environment
@googlemaps/js-api-loader2.1.3 (also 2.0.2, as vendored by vue3-google-map 0.27.2)- Maps JS
v=weekly(served3.66.6c), Chrome 154, Electron 138, and Firefox 156.
Bug
maps.googleapis.com/maps/api/js intermittently responds 200 with a body that calls the callback with an error instead of loading the API:
google.maps.__ib__({ "error": { "code": 503, "message": "The service is currently unavailable.", "status": "UNAVAILABLE" } });
We observed it in CI browser runs and measured it with curl from a separate network: ~1–4% of uncached requests, with or without a key, often in bursts. The error response has no caching headers, unlike the normal bootstrap. The same finding has been reported to the Maps JS API issue tracker: https://issuetracker.google.com/issues/566284337.
In dist/index.js, bootstrap() sets:
namespace[PENDING_BOOTSTRAP_KEY] = resolve; // ignores the callback argument
...
namespace[IMPORT_API_NAME] = (libraryName, ...args) =>
libraries.add(libraryName) && triggerBootstrap().then(() => namespace[IMPORT_API_NAME](libraryName, ...args));
When the callback carries an error, triggerBootstrap() resolves anyway. The .then then calls namespace.importLibrary, which is still this function, because the API never loaded. That call gets back the same resolved promise and chains again: an infinite microtask loop. The page's main thread never yields, memory grows several hundred MB/s, and the renderer is killed at the V8 heap limit. The importLibrary() promise never settles, so callers can't catch it, and error reporting never sees it.
Reproduction
Runnable repro: https://gist.github.com/mattico/360de14756ae2c96d912ebe475301194. It has a single-file browser page (no install, no API key) and a Node + happy-dom script, each with a control mode. Both replace the network with a stub that answers the bootstrap <script> the way the real endpoint does. The browser version, for reference:
<!doctype html>
<!--
Open this file in a browser (file:// is fine). No install, no API key.
repro.html the fake server returns the 503 error callback -> tab freezes
repro.html?ok control: the fake server returns the API -> resolves
-->
<meta charset="utf-8">
<title>js-api-loader error-callback repro</title>
<p id="status">Starting in 1s…</p>
<p>Heartbeat (stops when the main thread is stuck): <b id="beat">0</b></p>
<script type="module">
import { setOptions, importLibrary } from "https://cdn.jsdelivr.net/npm/@googlemaps/[email protected]/+esm";
const serverOk = new URLSearchParams(location.search).has("ok");
const status = (text) => (document.getElementById("status").textContent = text);
let beats = 0;
setInterval(() => (document.getElementById("beat").textContent = ++beats), 100);
// Stand in for maps.googleapis.com: answer the bootstrap <script> the loader
// appends the way the real endpoint does, either with the API (control) or
// with the error callback it intermittently serves.
const append = document.head.append.bind(document.head);
document.head.append = (node) => {
if (!(node instanceof HTMLScriptElement && node.src.includes("/maps/api/js"))) return append(node);
setTimeout(() => {
if (serverOk) {
google.maps.importLibrary = async (name) => ({ name }); // the real API replaces the stand-in
google.maps.__ib__();
} else {
google.maps.__ib__({ error: { code: 503, message: "The service is currently unavailable.", status: "UNAVAILABLE" } });
}
}, 0);
};
setTimeout(() => {
setOptions({ key: "YOUR_API_KEY", v: "weekly" });
status(`importLibrary("maps") with the server ${serverOk ? "OK" : "returning the 503 error callback"}…` +
(serverOk ? "" : " The heartbeat should stop now and the tab freeze."));
importLibrary("maps").then(
(lib) => status(`resolved: ${JSON.stringify(lib)}`),
(err) => status(`rejected: ${err.message}`),
);
}, 1000);
</script>
repro.html: after 1 s the heartbeat stops and the tab freezes. In Chrome 154 that's 100% CPU with memory climbing until an out-of-memory crash.repro.html?ok: control. The stub returns the API, andimportLibrary("maps")resolves.
In Node (node --max-old-space-size=256 repro.mjs from the gist), the control resolves. With the error callback no heartbeat ever prints, and the process dies with Reached heap limit … JavaScript heap out of memory in under a second.
Against the real endpoint: load a page that calls importLibrary("maps") with the cache disabled and reload repeatedly. A few percent of loads freeze (assuming that maps.googleapis.com/maps/api/js continues to return errors at that rate).
Reproduced in desktop browsers by auto-reloading a page containing Google's documented snippet verbatim (the same loop this package vendors) (a unique throwaway URL parameter per load, so the HTTP cache never answers) until the error callback was served:
- Chrome 154: the tab froze at 100% CPU with memory climbing until it crashed with an OOM.
- Firefox 156: the web console logged an out-of-memory error; as far as we could tell the tab itself did not crash.
Suggested fix
- Treat an error payload as a failed load:
namespace.__ib__ = (r) => r?.error ? fail(r.error) : resolve(). ResetbootstrapPromiseasonerrordoes, and remove the failed script, so the next call can retry.
This is safe: on success__ib__is called with no arguments, and an invalid key or disallowed referrer still gets the normal bootstrap (those surface later viagm_authFailure, not__ib__). So anerrorargument only means the transient failure, and a retry can't loop on a configuration error. Retrying with a short backoff recovered 100% of affected loads in our testing (27 of 900 cold loads hit the error, and all loaded after one to three retries). - Independently, never let the stand-in call itself after the bootstrap settles. If
namespace.importLibraryis still the stand-in, reject, or wait on a macrotask with a bound, rather than recursing through microtasks. That turns any future mismatch between the callback and the API install into an error instead of a frozen tab.
We're running a patch that does both and can open a PR if that's useful.
- Lenguaje dominante
- TypeScript
- Estrellas
- 451
- Forks
- 75
- Merge medio
- 2 min
- PR fusionados (30 d)
- 31
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de googlemaps/js-api-loader
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
googlemaps/js-api-loader#1002 · 7 comentarios · 23 reacciones ·
Los mantenedores suelen responder en 1 día
-
type: feature request wontfix
Dificultad 4/5 3-5 días Aptitud para principiantes 30/100
googlemaps/js-api-loader#1143 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dynamic Api KeysAbiertotype: question wontfix
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
googlemaps/js-api-loader#871 · 4 comentarios ·
Los mantenedores suelen responder en 1 día
-
type: feature request wontfix
Dificultad 5/5 Más de una semana Aptitud para principiantes 30/100
googlemaps/js-api-loader#210 · 18 comentarios · 24 reacciones ·
Los mantenedores suelen responder en 1 día
Todos los issues de googlemaps/js-api-loader
Issues similares
-
area: build-and-ci S3: minor
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
manaflow-ai/cmux#39902 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
refined-github/refined-github#10387 ·
Los mantenedores suelen responder en 1 día
-
documentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
CopilotKit/CopilotKit#8283 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
Los mantenedores suelen responder en 1 día
-
complexity:simple priority:P3 ready-to-build
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
junruxiong/IncarnaMind#2919 ·