How Chromium Terminates Idle Extension Service Workers
This article provides an overview of how the Chromium web browser manages the lifecycle of Manifest V3 extension service workers, focusing specifically on how it terminates workers when asynchronous events remain idle. To optimize memory and CPU usage, Chromium enforces strict timeout mechanisms—primarily a 30-second inactivity timer and a 5-minute single-request limit—that forcefully shut down service workers when asynchronous tasks stall or fail to resolve.
The Core Timeout Mechanisms
Chromium uses two main timers to govern extension service worker lifetimes and handle idle states:
- The 30-Second Inactivity Timer: After an event is dispatched to the service worker, Chromium starts a 30-second countdown. If no new events occur and no recognized asynchronous operations are pending, the browser considers the worker idle and terminates it.
- The 5-Minute Single-Request Timer: When an event triggers an asynchronous operation, Chromium pauses the 30-second termination timer to allow the task to finish. However, if that single asynchronous event remains pending (idle) for more than 5 minutes without resolving, Chromium assumes the task is stuck and forcefully terminates the service worker.
How Different Asynchronous Events Are Handled
Not all asynchronous operations are treated equally by Chromium's lifecycle manager. How the browser handles an idle asynchronous event depends on how that event was initiated.
1. Extension API Calls
since Chromium 110, calling most asynchronous chrome.*
APIs resets the 30-second inactivity timer. While the API call is
actively waiting for a response (remaining idle in the background), the
service worker is kept alive. If the API call hangs and fails to resolve
within the 5-minute maximum limit, the worker is terminated.
2. Standard Web APIs and Unattached Promises
Standard JavaScript asynchronous operations—such as
setTimeout, setInterval, or a basic
Promise—do not automatically keep a service worker alive.
If a service worker initiates a setTimeout set for 45
seconds and does nothing else, Chromium sees the worker as idle. The
30-second inactivity timer will expire, and the worker will be
terminated before the timeout completes.
To keep the worker alive during a standard Promise, the Promise must
be passed to the event.waitUntil() method inside a standard
Service Worker event (like install or
activate), which subjects it to the 5-minute limit instead
of the 30-second limit.
3. Fetch Requests
When a fetch() request is initiated, Chromium keeps the
service worker alive while waiting for the network response. If the
server hangs and the connection remains idle without closing, the
5-minute single-request timer will eventually trigger and kill the
worker.
4. Long-Lived Connections (Ports and WebSockets)
Asynchronous communication channels like
chrome.runtime.connect (messaging ports) and
WebSocket connections have specific idle rules:
- Messaging Ports: Opening a port keeps the service worker alive, provided messages are actively exchanged. In newer versions of Chromium, merely holding a port open without traffic will allow the 30-second idle timer to terminate the worker unless the port is connected to a native messaging host.
- WebSockets: Active WebSocket connections reset the 30-second timer whenever a message is sent or received. If the WebSocket remains completely idle for 30 seconds, the service worker is terminated, which automatically closes the socket.
Consequences of Termination
When Chromium terminates a service worker due to an idle asynchronous event, the shutdown is immediate:
- Aborted Operations: Any unresolved Promises,
pending
fetchrequests, or active timers are instantly aborted without throwing catchable errors. - Memory Wipe: All global variables and in-memory states are destroyed.
- Closed Connections: Any open messaging ports or network sockets are severed.
To prevent data loss from sudden termination, extension developers
must avoid relying on global variables during asynchronous workflows and
instead persist state immediately using chrome.storage or
IndexedDB.