Add low-memory client concurrency guard

This commit is contained in:
mARTin-B78 2026-08-19 17:01:06 +02:00
parent 496518f190
commit 8cc92ca02b
5 changed files with 21 additions and 6 deletions

View File

@ -5,6 +5,11 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
---
## [1.20.39] — 2026-08-19
### Changed
- **Low-memory devices now receive conservative batch concurrency.** Shared frontend worker pools cap themselves at two concurrent tasks when the browser reports approximately 4 GB RAM or less, reducing memory spikes during bulk operations.
## [1.20.38] — 2026-08-19
### Added

View File

@ -37,7 +37,7 @@ Priority levels:
- [ ] Update only affected table rows instead of rebuilding entire tables.
- [ ] Add an LRU cache for audio and images and remove old entries automatically.
- [ ] Revoke discarded object URLs with `URL.revokeObjectURL(objectUrl)`.
- [ ] Add low-memory mode using `navigator.deviceMemory` where available.
- [x] Add low-memory mode using `navigator.deviceMemory` where available; shared worker pools cap at two tasks on devices reporting approximately 4 GB RAM or less.
- [ ] Disable image preloading and reduce table/cache sizes on devices with approximately 4 GB RAM.
## P1 — Backend stability and bounded concurrency

View File

@ -1 +1 @@
1.20.38
1.20.39

View File

@ -10,7 +10,7 @@
<meta name="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB">
<meta name="app-version" content="1.20.38">
<meta name="app-version" content="1.20.39">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg">
@ -27,7 +27,7 @@
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/style.css?v=1.20.38">
<link rel="stylesheet" href="/static/style.css?v=1.20.39">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -378,7 +378,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.20.36"></script>
<script src="/static/loader.js?v=1.20.39"></script>
</body>
</html>

View File

@ -336,10 +336,20 @@ function debounce(fn, ms) { let t; return (...a) => { clearTimeout(t); t = setTi
// Log a non-fatal error instead of silently swallowing it (was `.catch(()=>{})`).
// Use in best-effort paths so failures are visible in the console for debugging.
function logErr(context, err) { try { console.warn(`[${context}]`, err && (err.message || err)); } catch (_) {} }
// Keep batch work conservative on small client devices. Desktop machines
// without the Device Memory API keep the normal pool size; browsers that do
// expose it get a hard cap before multiple audio/image requests can build up.
function ttvcIsLowMemoryDevice() {
const memory = Number(globalThis.navigator?.deviceMemory);
return Number.isFinite(memory) && memory > 0 && memory <= 4;
}
window.ttvcIsLowMemoryDevice = ttvcIsLowMemoryDevice;
// Run an async worker over items with bounded concurrency (default 4). `onProgress(done,total)`
// fires after each item. Replaces slow serial `for…await` loops over network calls.
async function runPool(items, worker, concurrency = 4, onProgress) {
const arr = [...items]; let i = 0, done = 0; const total = arr.length;
const requested = Math.max(1, Number(concurrency) || 1);
const effectiveConcurrency = ttvcIsLowMemoryDevice() ? Math.min(2, requested) : requested;
async function run() {
while (i < arr.length) {
const idx = i++;
@ -347,7 +357,7 @@ async function runPool(items, worker, concurrency = 4, onProgress) {
done++; if (onProgress) { try { onProgress(done, total); } catch (_) {} }
}
}
await Promise.all(Array.from({ length: Math.min(concurrency, arr.length) }, run));
await Promise.all(Array.from({ length: Math.min(effectiveConcurrency, arr.length) }, run));
}
async function copyText(text) {
try {