Recuperarse de la pérdida de dispositivo WebGPU
Diseña una recuperación acotada de WebGPU que conserve el estado de la página e impida que tareas asíncronas obsoletas sustituyan un dispositivo nuevo.
BotBrowser Team
Quieres la documentación estructurada de Plataforma?
Este artículo forma parte de la biblioteca editorial. Para pasos de configuración, material de referencia y actualizaciones continuas, entra en la sección de docs.
Cuando se pierde un dispositivo WebGPU, puede dejar de funcionar la parte gráfica aunque el resto de la página siga operativa. La especificación del W3C expone este evento del ciclo de vida mediante GPUDevice.lost; MDN explica que su promesa permanece pendiente durante la vida del dispositivo y se resuelve con información de pérdida cuando este deja de estar disponible. Conserva la tarea de la persona en el estado normal de la aplicación, cambia pronto a una vista utilizable sin GPU y trata cada nueva inicialización como una generación distinta. Un token de generación permite ignorar resultados tardíos del dispositivo sustituido.
Separa el estado de la aplicación de los recursos GPU
El dispositivo gráfico es un recurso para presentar o calcular un resultado. No debería ser el único lugar donde se conservan un documento, una selección, los valores de un formulario u otro trabajo que la persona espera mantener. Guarda esos datos en el estado habitual de la página o aplicación. Así, la pérdida puede retirar el recurso de renderizado sin borrar la tarea.
GPUDevice.lost es una promesa asociada a un dispositivo concreto. Se resuelve cuando ese dispositivo se pierde, pero no promete que haya otro disponible. La especificación de WebGPU describe un cambio en la capacidad de uso, no un diagnóstico de un componente físico. La interfaz debe indicar con precisión qué función se ha interrumpido, conservar el trabajo accesible y evitar afirmar por qué se perdió el dispositivo de una máquina.
Esta guía no aborda la cuestión de privacidad de la visión general sobre fingerprinting de WebGPU, que trata el uso de señales expuestas para el seguimiento. Tampoco sustituye la validación de versiones del navegador, orientada a evaluar y revertir una combinación de navegador y perfil. Aquí se responde una pregunta de ejecución: ¿qué debe ocurrir con una función activa cuando su dispositivo actual deja de ser utilizable?
Protege la máquina de estados con un token de generación
Incrementa un contador monótono cada vez que una inicialización se cancela, se detiene o queda sustituida. Cada continuación asíncrona guarda el contador con el que comenzó. Antes de modificar el estado visible, comprueba que sigue siendo la generación actual. Aplica la misma comprobación al controlador de lost: un evento tardío del dispositivo anterior no debe reemplazar el estado de uno nuevo.
El siguiente controlador se puede adaptar al producto. La aplicación implementa showFallback, showGraphics, announce, stopRenderLoop, detachGraphics y disposeDeviceResources: detiene el ciclo anterior, separa la vista, libera buffers y textures propios, y destruye el dispositivo. Los datos de la aplicación permanecen fuera del controlador. Llama a initialize solo al abrir la función o tras una acción deliberada. El ejemplo no reintenta ni aplica un contador; el responsable de la función debe imponer cualquier límite de producto fuera del controlador.
let generation = 0;
let activeDevice = null;
const isCurrent = token => token === generation;
function stopGraphics() {
generation += 1;
const oldDevice = activeDevice;
activeDevice = null;
try {
retireDevice(oldDevice);
} finally {
showFallback('Graphics stopped. Your page state is still available.');
}
}
function retireDevice(device) {
if (!device) return;
try {
stopRenderLoop(device);
} finally {
try {
detachGraphics(device);
} finally {
try {
disposeDeviceResources(device);
} finally {
device.destroy();
}
}
}
}
async function initialize(gpu = navigator.gpu) {
const token = ++generation;
const oldDevice = activeDevice;
activeDevice = null;
showFallback('Starting the graphics feature…');
try {
retireDevice(oldDevice);
if (!gpu) throw new Error('WebGPU is unavailable in this context.');
const adapter = await gpu.requestAdapter();
if (!isCurrent(token)) return;
if (!adapter) throw new Error('No adapter was returned.');
const device = await adapter.requestDevice();
if (!isCurrent(token)) {
retireDevice(device);
return;
}
activeDevice = device;
showGraphics(device);
announce('Graphics are ready.');
void device.lost.then(info => {
if (!isCurrent(token)) return;
generation += 1;
activeDevice = null;
try {
retireDevice(device);
} finally {
showFallback(`Graphics stopped (${info.reason || 'device lost'}). Your page state is still available.`);
}
});
} catch {
if (isCurrent(token)) {
const failedDevice = activeDevice;
activeDevice = null;
try {
retireDevice(failedDevice);
} finally {
showFallback('Graphics could not start. Your page state is still available.');
}
}
}
}
En producción, procesa la información de pérdida conforme al contrato de la API y a tus necesidades de soporte; no la conviertas en una clasificación de hardware. Si requestDevice() termina después de stopGraphics() o de una nueva llamada a initialize(), el token hace que se destruya el dispositivo obsoleto en vez de mostrarlo. También se ignora un rechazo tardío. Si la promesa lost anterior se resuelve después de iniciar otra generación, no puede sobrescribir la vista nueva.
Elige un resultado comprensible
La transición debe explicar qué sigue disponible, no especular sobre la causa de la pérdida.
Falta WebGPU, el adapter es null o la inicialización falla. Mantén visible la vista HTML normal e indica que no se pudieron iniciar los gráficos mejorados. No reintentes automáticamente; conserva la función como opcional.
Se resuelve la promesa lost de un dispositivo listo. Marca como perdida solo esa generación y conserva los datos y controles de la página. Ofrece reintentar únicamente cuando la persona tenga motivos para hacerlo.
Una inicialización nueva sustituye a un dispositivo activo y falla o funciona. Antes de solicitar el reemplazo, detén y separa el renderer anterior, libera sus recursos y destruye el dispositivo. Si la solicitud falla, conserva la alternativa y no restaures el dispositivo anterior; si funciona, conecta solo el nuevo.
Comienza otra generación mientras una solicitud anterior sigue pendiente. Considera obsoleto el resultado anterior y libera cualquier dispositivo que haya creado. No permitas que cambie el estado ni la vista de la generación nueva.
Un reintento deliberado funciona. Conecta el dispositivo nuevo y renderiza desde el estado actual de la aplicación. Observa únicamente la promesa de pérdida del dispositivo nuevo.
El reintento deliberado falla o se agota el presupuesto de la aplicación. Conserva una alternativa utilizable y explica que los gráficos mejorados siguen indisponibles. Detén los intentos hasta una nueva acción de la persona o un reinicio definido por la aplicación; el responsable de la función aplica el presupuesto, no este ejemplo.
Si el producto permite reintentar, usa un botón con estados claros de carga y desactivación. Un bucle de reintentos puede reservar recursos repetidamente sin mejorar la experiencia. Este ejemplo no reintenta automáticamente ni aplica un contador; el responsable de la función debe definir fuera del controlador un presupuesto pequeño o exigir una nueva acción tras fallar. Ni la especificación ni GPUDevice.lost garantizan que reintentar recupere la misma tarea.
Prueba los cambios del ciclo de vida
El componente responsable de la tarea debe decidir si la vista gráfica es opcional, qué datos deben conservarse y qué vista puede seguir funcionando sin dispositivo. El renderer puede informar de que no hay dispositivo, pero no debería descartar el documento ni iniciar reintentos por su cuenta. La página debe coordinar el estado, los controles y la intención de la persona.
Trata cada inicialización como una transacción breve: invalida la generación anterior, separa su ruta de renderizado y conserva los datos de la aplicación; después solicita los recursos de la nueva generación. Publica el dispositivo solo tras superar la última comprobación del token. Ante un error, solo la generación actual puede cambiar la interfaz.
La pérdida del dispositivo y la detención de la aplicación pueden coincidir con la limpieza. Invalida el token antes de destruir o separar el dispositivo activo, para que las continuaciones pendientes no escriban en una generación antigua. La limpieza debe poder repetirse sin alterar el resultado ni destruir por accidente el dispositivo sustituto.
No conviertas cada motivo de pérdida en una instrucción de recuperación. La información puede distinguir categorías generales definidas por la API para el estado o soporte, pero no identifica una GPU concreta ni demuestra por qué cambió el entorno. La interfaz debe seguir siendo segura cuando el motivo falte, sea desconocido o no le resulte útil a la persona.
Comprueba si la función puede reconstruirse desde el estado normal de la aplicación. Un gráfico puede volver a generarse a partir de filas y filtros guardados; un resultado intermedio que solo existía en memoria GPU quizá no se recupere. Si la tarea requiere conservarlo, guarda puntos de control bajo control de la aplicación y explica qué trabajo se mantiene y qué operación debe repetirse.
El dispositivo también puede perderse mientras la persona realiza una acción. Desactiva solo los controles que dependan de la ruta gráfica; conserva navegación, cancelación, exportación y alternativas accesibles cuando sigan siendo válidas. Si la acción pendiente aún no está en el estado de la aplicación, complétala por una vía sin GPU o indica que debe repetirse. Un estado accesible puede anunciar el cambio sin desplazar el foco.
No uses la recarga de página como respuesta predeterminada. Puede borrar un formulario sin enviar, reiniciar la navegación, repetir solicitudes de red o hacer que la persona reconstruya su contexto. La pérdida es un evento del ciclo de vida de un recurso, no una razón automática para reiniciar toda la aplicación. Recarga solo si existe una causa independiente y el trabajo pendiente está protegido.
Una llamada explícita a GPUDevice.destroy() también termina la vida útil del dispositivo. Invalida su generación antes de limpiar; de lo contrario, la resolución posterior de lost puede competir con la detención y parecer un fallo inesperado. La pérdida del dispositivo actual puede activar la alternativa; un evento de una generación detenida ya no debe cambiar la interfaz.
Para probar carreras, usa promesas aplazables en vez de temporizadores arbitrarios. Inicia A y deja pendiente la solicitud de adapter; inicia B, permite que B se muestre y luego resuelve A. La vista final debe seguir perteneciendo a B. Repite el caso reteniendo requestDevice() de A hasta que B esté listo; el dispositivo tardío de A debe liberarse y no conectarse al renderer.
El doble de prueba debe exponer los mismos límites asíncronos que la API: solicitudes controlables de adapter y device, además de una promesa lost para cada dispositivo. Cuenta las llamadas a destroy() y las conexiones al renderer. Una vez sustituida, la generación antigua no debe ejecutar callbacks de estado y el dispositivo actual debe permanecer conectado. Así el fixture se puede ejecutar sin un adapter real.
Añade una prueba con A activo: inicialízalo, confirma que está conectado y llama después a initialize() para arrancar B. Antes de que B solicite un adapter, detén el ciclo de renderizado de A, separa su vista, libera sus recursos y destruye el dispositivo. Si falla la solicitud de adapter o device de B, la alternativa permanece visible y A no revive; si B funciona, solo B se conecta. En ambos casos, resolver después la promesa lost de A no cambia el estado actual.
Añade un caso con un dispositivo activo: inicializa A, confirma que está conectado y luego llama a initialize() para iniciar B. Antes de que B solicite un adapter, el ciclo de renderizado de A debe detenerse, su vista separarse, sus recursos liberarse y el dispositivo destruirse. Si falla el adapter o device de B, la alternativa queda visible y A no revive; si B funciona, solo B se conecta. En ambos resultados, una resolución tardía de lost de A no debe cambiar el estado.
Detener y reintentar son acciones distintas. Detener incrementa la generación, retira el dispositivo anterior y vuelve a una alternativa estable sin iniciar solicitudes. Solo un reintento deliberado comienza otra generación y muestra la carga. Si falla, se vuelve a la alternativa y se aplica la política limitada. El contador refleja intentos de la aplicación, no eventos de pérdida de un solo dispositivo.
Comprueba quién posee cada recurso cuando se sustituye un dispositivo. El dispositivo creado tarde por una promesa pertenece a la generación obsoleta y se debe liberar según el ciclo de vida de la API. Un catch o callback lost antiguo no debe borrar el dispositivo actual. Mantén las referencias separadas por generación siempre que sea posible y valida el token antes de escribir estado compartido.
Reconstruir no significa reconectar recursos antiguos. Buffers, textures, pipelines, bind groups y command encoders pertenecen al dispositivo que los creó. Cuando haya un dispositivo nuevo, recrea solo los recursos necesarios para el modo activo y vuelve a enlazarlos desde datos de la aplicación. Si el contexto canvas depende del dispositivo, configura primero la ruta nueva antes de enviar trabajo.
Conserva los datos autoritativos del modelo en CPU para que los recursos nuevos representen la misma tarea. No guardes recursos del dispositivo antiguo en una caché global suponiendo que podrán usarse con otro. Solo los valores reconstruidos desde el estado de la aplicación pueden cruzar ese límite de ciclo de vida.
Mantén separadas la coordinación de recuperación y la implementación del renderizado. El renderer puede preparar recursos para un dispositivo recibido; la página decide si esa preparación sigue perteneciendo a la generación actual. Si la preparación también es asíncrona, transmite el mismo token y vuelve a comprobarlo antes de publicar el resultado.
Puede ocurrir otra pérdida o una cancelación mientras cargan shaders, textures u otros recursos. Cancela las solicitudes obsoletas cuando sea posible; si no, ignora sus resultados tardíos y libera los recursos que crearon. Así el renderer anterior no continúa enviando comandos al dispositivo sustituto.
Registra los resultados por transición, no por máquina. Una inicialización correcta significa que la función solicitada está lista para el estado actual; una pérdida significa que el trabajo gráfico afectado ya no se puede usar; una recuperación correcta significa que el nuevo dispositivo reconstruyó los recursos necesarios desde los datos actuales. Son resultados verificables sin inventar categorías de hardware ni prometer recuperación universal.
Comprueba que el renderer solo se muestre cuando la preparación de la generación actual haya terminado. Si empieza otra generación mientras cargan recursos, el resultado antiguo no debe aparecer ni siquiera de forma breve. La persona solo debería ver el resultado de su elección más reciente.
Prueba las transiciones con promesas controladas, no dependiendo de una configuración gráfica concreta. Un caso útil mantiene pendiente la primera solicitud de adapter o device hasta que se inicia otra inicialización. El resultado esperado es que la primera generación nunca publique su resultado; si creó un dispositivo, debe destruirlo. Otro caso resuelve lost del dispositivo antiguo después de que el sustituto esté listo: la vista y el estado nuevos deben permanecer intactos.
Incluye también los casos normales: API ausente, adapter igual a null, rechazo al solicitar adapter o device, pérdida tras inicializar, cancelación de la persona, reintento exitoso y límite agotado. En todos ellos comprueba que los controles siguen operativos, que las selecciones se conservan, que el estado es accesible y que la vista coincide con los datos de la aplicación. Si hay animación, prueba aparte el comportamiento con movimiento reducido; la transición no debe depender de ella.
Registra solo información operativa necesaria para comprender la transición, como una etapa aproximada y si se mostró la alternativa. No recopiles datos del adapter, no uses umbrales de hardware, no sondees la GPU ni trates la pérdida como señal de fingerprinting. La especificación pública de WebGPU y la referencia de MDN sobre GPUDevice.lost fundamentan el comportamiento descrito; ninguna garantiza el mismo resultado de recuperación en todos los navegadores y hosts.
Mantén útil la alternativa
La alternativa forma parte de la función, no es una página de error. Conserva en HTML normal la información y las acciones esenciales, mantén las selecciones y explica que se detuvo la vista mejorada mientras la página sigue disponible. Si un resultado solo existía en memoria GPU y no se puede reconstruir, dilo con claridad en lugar de insinuar que se guardó.
BotBrowser documenta modos de WebGPU seleccionables que los equipos pueden probar en una configuración admitida, pero BotBrowser no garantiza la recuperación tras perder un dispositivo, no controla la reclamación de recursos del host ni habilita WebGPU en todos los entornos. Valida la recuperación visible para la persona en los entornos que admitas y mantén este contrato de aplicación separado de la identificación del adapter y del fingerprinting.
Fuentes
Artículos Relacionados
Lleva BotBrowser de la investigación a producción
Usa estas guías para entender el modelo y después avanzar hacia validación multiplataforma, contextos aislados y despliegue de navegador preparado para escalar.