Skip to content

Instantly share code, notes, and snippets.

@RatoX
Created May 6, 2026 09:48
Show Gist options
  • Select an option

  • Save RatoX/60d8b7d6c68a8fc4d1c220fd4a5c61bb to your computer and use it in GitHub Desktop.

Select an option

Save RatoX/60d8b7d6c68a8fc4d1c220fd4a5c61bb to your computer and use it in GitHub Desktop.
sw_memory_leak_poc
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Service Worker fetch memory repro</title>
<style>
:root {
color-scheme: light dark;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
body {
margin: 0;
padding: 24px;
}
main {
display: grid;
gap: 16px;
max-width: 820px;
margin: 0 auto;
}
button {
width: fit-content;
min-height: 36px;
padding: 0 14px;
font: inherit;
}
textarea {
width: 100%;
min-height: 420px;
box-sizing: border-box;
padding: 12px;
font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
resize: vertical;
}
</style>
</head>
<body>
<main>
<button id="start" type="button" disabled>Start</button>
<textarea id="log" readonly></textarea>
</main>
<script>
const REQUESTS = 20000;
const CONCURRENCY = 10;
const PAYLOAD_BYTES = 4096;
const startButton = document.getElementById('start');
const logOutput = document.getElementById('log');
function log(message, data = {}) {
const suffix = Object.keys(data).length ? ` ${JSON.stringify(data)}` : '';
logOutput.value += `[${new Date().toISOString()}] ${message}${suffix}\n`;
logOutput.scrollTop = logOutput.scrollHeight;
}
async function setupServiceWorker() {
if (!('serviceWorker' in navigator)) {
throw new Error('Service workers are not available');
}
const registration = await navigator.serviceWorker.register('/sw.js');
log('Service worker registered', { scope: registration.scope });
await navigator.serviceWorker.ready;
if (!navigator.serviceWorker.controller) {
await new Promise(resolve => {
const timeout = setTimeout(resolve, 1000);
navigator.serviceWorker.addEventListener('controllerchange', () => {
clearTimeout(timeout);
resolve();
}, { once: true });
});
}
if (!navigator.serviceWorker.controller) {
log('Reloading so the service worker controls this page');
location.reload();
return;
}
log('Ready', { controller: navigator.serviceWorker.controller.scriptURL });
startButton.disabled = false;
}
async function runRequests() {
startButton.disabled = true;
const runId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
let next = 0;
let completed = 0;
let failed = 0;
const startedAt = performance.now();
log('Run started', { requests: REQUESTS, concurrency: CONCURRENCY, payloadBytes: PAYLOAD_BYTES, runId });
async function worker() {
while (next < REQUESTS) {
const requestId = next++;
const url = `/payload?run=${runId}&i=${requestId}&size=${PAYLOAD_BYTES}&cache=${Math.random()}`;
try {
const response = await fetch(url, { cache: 'no-store' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
await response.arrayBuffer();
completed += 1;
} catch (error) {
failed += 1;
log('Request failed', { requestId, error: String(error) });
}
if ((completed + failed) % 1000 === 0 || completed + failed === REQUESTS) {
log('Progress', { completed, failed, total: completed + failed });
}
}
}
await Promise.all(Array.from({ length: CONCURRENCY }, () => worker()));
log('Run finished', {
completed,
failed,
elapsedMs: Math.round(performance.now() - startedAt),
});
startButton.disabled = false;
}
startButton.addEventListener('click', () => {
runRequests().catch(error => {
log('Run crashed', { error: String(error), stack: error?.stack });
startButton.disabled = false;
});
});
setupServiceWorker().catch(error => {
log('Setup failed', { error: String(error), stack: error?.stack });
});
</script>
</body>
</html>
#!/usr/bin/env python3
import argparse
import time
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlencode, urlparse
ROOT = Path(__file__).resolve().parent
WEB_ROOT = ROOT
class HarnessHandler(BaseHTTPRequestHandler):
server_version = "WKWebViewHarness/1.0"
def do_GET(self):
parsed = urlparse(self.path)
path = parsed.path
if path == "/":
self.serve_file(WEB_ROOT / "index.html", "text/html; charset=utf-8")
return
if path == "/index.html":
self.serve_file(WEB_ROOT / "index.html", "text/html; charset=utf-8")
return
if path == "/blank.html":
self.serve_file(WEB_ROOT / "blank.html", "text/html; charset=utf-8")
return
if path == "/sw.js":
self.serve_file(
WEB_ROOT / "sw.js",
"application/javascript; charset=utf-8",
extra_headers={"Service-Worker-Allowed": "/"},
)
return
if path == "/payload":
self.serve_payload(parsed.query)
return
if path == "/redirect":
self.serve_redirect(parsed.query)
return
if path == "/favicon.ico":
self.send_response(HTTPStatus.NO_CONTENT)
self.end_headers()
return
self.send_error(HTTPStatus.NOT_FOUND, "Not found")
def log_message(self, fmt, *args):
print(f"[server] {self.address_string()} - {fmt % args}")
def serve_file(self, file_path, content_type, extra_headers=None):
if not file_path.exists():
self.send_error(HTTPStatus.NOT_FOUND, "Not found")
return
content = file_path.read_bytes()
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(content)))
self.send_header("Cache-Control", "no-store, max-age=0")
self.send_header("Pragma", "no-cache")
if extra_headers:
for name, value in extra_headers.items():
self.send_header(name, value)
self.end_headers()
self.wfile.write(content)
def serve_payload(self, query):
params = parse_qs(query)
size = parse_int(params, "size", 1024, minimum=0, maximum=8 * 1024 * 1024)
delay_ms = parse_int(params, "delay", 0, minimum=0, maximum=30_000)
if delay_ms > 0:
time.sleep(delay_ms / 1000)
body = make_body(size)
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store, max-age=0")
self.send_header("Pragma", "no-cache")
self.end_headers()
self.wfile.write(body)
def serve_redirect(self, query):
params = parse_qs(query)
target = params.get("to", ["/"])[0]
if not target.startswith("/"):
target = "/"
target_params = {
key: values[-1]
for key, values in params.items()
if key not in {"to"} and values
}
separator = "&" if "?" in target else "?"
location = target
if target_params:
location = f"{target}{separator}{urlencode(target_params)}"
self.send_response(HTTPStatus.FOUND)
self.send_header("Location", location)
self.send_header("Cache-Control", "no-store, max-age=0")
self.end_headers()
def parse_int(params, key, default, minimum, maximum):
raw = params.get(key, [str(default)])[0]
try:
value = int(raw)
except ValueError:
value = default
return max(minimum, min(maximum, value))
def make_body(size):
if size <= 0:
return b""
seed = b"0123456789abcdef"
repeats = (size // len(seed)) + 1
return (seed * repeats)[:size]
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8787)
args = parser.parse_args()
server = ThreadingHTTPServer((args.host, args.port), HarnessHandler)
url = f"http://{args.host}:{args.port}/"
print(f"[server] serving {ROOT}")
print(f"[server] open {url}")
server.serve_forever()
if __name__ == "__main__":
main()
self.addEventListener('install', event => {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment