Add low-memory client concurrency guard
This commit is contained in:
parent
496518f190
commit
8cc92ca02b
@ -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
|
## [1.20.38] — 2026-08-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
2
TODO.md
2
TODO.md
@ -37,7 +37,7 @@ Priority levels:
|
|||||||
- [ ] Update only affected table rows instead of rebuilding entire tables.
|
- [ ] Update only affected table rows instead of rebuilding entire tables.
|
||||||
- [ ] Add an LRU cache for audio and images and remove old entries automatically.
|
- [ ] Add an LRU cache for audio and images and remove old entries automatically.
|
||||||
- [ ] Revoke discarded object URLs with `URL.revokeObjectURL(objectUrl)`.
|
- [ ] 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.
|
- [ ] Disable image preloading and reduce table/cache sizes on devices with approximately 4 GB RAM.
|
||||||
|
|
||||||
## P1 — Backend stability and bounded concurrency
|
## P1 — Backend stability and bounded concurrency
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<meta name="format-detection" content="telephone=no">
|
<meta name="format-detection" content="telephone=no">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<meta name="theme-color" content="#2563EB">
|
<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="manifest" href="/manifest.webmanifest">
|
||||||
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
|
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
|
||||||
<link rel="apple-touch-icon" href="/static/icon.svg">
|
<link rel="apple-touch-icon" href="/static/icon.svg">
|
||||||
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
||||||
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
<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) ── -->
|
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||||
@ -378,7 +378,7 @@ window.toggleNavTree = function(treeId, chevronId) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
<!-- 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -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(()=>{})`).
|
// 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.
|
// 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 (_) {} }
|
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)`
|
// 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.
|
// fires after each item. Replaces slow serial `for…await` loops over network calls.
|
||||||
async function runPool(items, worker, concurrency = 4, onProgress) {
|
async function runPool(items, worker, concurrency = 4, onProgress) {
|
||||||
const arr = [...items]; let i = 0, done = 0; const total = arr.length;
|
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() {
|
async function run() {
|
||||||
while (i < arr.length) {
|
while (i < arr.length) {
|
||||||
const idx = i++;
|
const idx = i++;
|
||||||
@ -347,7 +357,7 @@ async function runPool(items, worker, concurrency = 4, onProgress) {
|
|||||||
done++; if (onProgress) { try { onProgress(done, total); } catch (_) {} }
|
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) {
|
async function copyText(text) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user