DEV Community

Cover image for Chrome kills an MV3 service worker at 5 minutes. Here is how my AI image extension stopped losing results
Nichola Chaus
Nichola Chaus

Posted on AI-assisted

Chrome kills an MV3 service worker at 5 minutes. Here is how my AI image extension stopped losing results

I build a small Chrome extension that upscales and unblurs photos with an AI model running right on your computer. No server, the image never leaves the browser. On a laptop with a decent GPU an upscale takes seconds. On an old office PC with no GPU, the same model runs on the CPU through WebAssembly, and a 4x upscale of a big photo can take six or seven minutes.
For two weeks those slow machines kept losing their results at the very end. Here is what was going on and the fix that took the error count to zero.
The symptom
Users on slow PCs pressed Enhance, watched the progress bar crawl for several minutes, and then got an error instead of a picture. Under the hood it was Chrome's "The message port closed before a response was received" (sometimes worded as "message channel closed"). In our anonymous error stats it showed up 6 times from 5 users between September 27 and October 6, including Japan, Mexico and Canada. Every case had the same shape: no GPU, and a run longer than about five minutes. One user saw the raw English error text right before uninstalling.
The architecture that caused it
In Manifest V3 there is no background page. The model can't live in the service worker, which Chrome stops whenever it likes, so it runs in an offscreen document. The original message flow looked like this:
The editor page sends INFER to the service worker.
The service worker forwards it to the offscreen document and keeps sendResponse open with return true.
The offscreen document runs the model and answers.
The service worker passes the answer back to the page.
It worked perfectly in testing, because on my machine step 3 took seconds.
The 5-minute rule
Chrome's own docs on the extension service worker lifecycle say it plainly: the worker is terminated when a single request, like an event or an API call, takes longer than 5 minutes to process. A message the worker is still waiting to answer counts as that request. So on a slow PC Chrome killed the service worker in the middle of step 2, the port to the page died with it, and the result the offscreen document produced a minute later had nowhere to go.
The offscreen document itself was fine. It kept computing. The middleman was the problem.
The fix: take the service worker out of the long path
In version 1.1.1 the service worker does only the fast part: it creates the offscreen document if it isn't there yet and replies right away. The long INFER message goes from the page straight to the offscreen document.
On the page side it is one small helper. First chrome.runtime.sendMessage({ type: 'ENSURE_OFFSCREEN' }), which the worker answers in milliseconds. Then chrome.runtime.sendMessage({ type: 'INFER', target: 'offscreen', ...payload }), which the offscreen document answers when the model is done. The batch mode uses the same helper, so a folder of 30 photos goes through the same safe path.
One gotcha: runtime.sendMessage is delivered to every listener in the extension, the service worker included. If the worker's listener answers anything it doesn't recognise with an "unknown message" error, that answer wins the race and the page gets an error in a millisecond. So the first line of the worker's listener is now if (request.target === 'offscreen') return false; and the offscreen listener does the mirror check.
How I tested it without waiting 5 minutes
You don't need a slow PC to reproduce this. With the Chrome DevTools Protocol you can stop all service workers on demand: ServiceWorker.stopAllWorkers. The end-to-end test starts an upscale, kills the worker in the middle of the run, and waits for the result. On the old code the test fails with exactly the error users saw. On 1.1.1 the picture arrives.
The result
Version 1.1.1 went live on October 7. Since then the port error count is zero, on 1.1.1 and on 1.1.2 after it, while dozens of users have run enhancements. It is not the end of long-run problems (a busy model still makes a second tab wait), but no one loses a finished result anymore.
Takeaways for MV3 extensions
Never keep a service worker waiting on work that can take minutes. Let it start things, not carry them.
Talk to the offscreen document directly with a target field, and make every other listener ignore messages that aren't addressed to it.
Kill the worker in your tests with ServiceWorker.stopAllWorkers. If a run survives that, it survives a slow PC.
Map Chrome's channel errors to a human message in the user's language. A raw "message port closed" is an uninstall.
If you want to see the extension this happened in, it is Image Enhancer, a free AI photo upscaler that runs in your browser (https://www.devexthub.com/image-enhancer/?utm_source=devto). Happy to answer questions about running ONNX models in an offscreen document.

Top comments (0)