Skip to content

Conversation

DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Oct 19, 2025

  • Load common.js and style.css sequentially for all config pages (no more "xy is undefined" due to missing common.js)
  • Restrict all requests in index.js to single connection

This greatly improves UI stability in low heap as well as bad wifi signal conditions and does not noticeably slow down any UI access.
I also increased the retries on failed requests from 1 to 5 but shortened the request interval so some requests may even be faster than before.

Combined with #4939 this makes the ESP8266 work flawlessly, I was even able to increase the number of LEDs from current 720 to 900 and its still more stable than it is currently. With this PR the UI keeps running down to about 6k of free heap whereas it is already struggeling at 10k without these changes.

the changes add ~900bytes to flash usage.

Summary by CodeRabbit

  • New Features

    • Dynamic resource loading with automatic retry on failure for improved reliability
  • Bug Fixes

    • Prevented flash of unstyled content during page load for smoother startup experience
    • Enhanced error handling during initialization with user-facing notifications
  • Chores

    • Refactored initialization architecture to use sequential, promise-based loading patterns
    • Optimized WebSocket memory handling

- load common.js and style.css sequentially for all config pages
- restrict all requrests in index.js to single connection
Copy link
Contributor

coderabbitai bot commented Oct 19, 2025

Walkthrough

This PR refactors WLED's initialization system to use dynamic resource loading with retry logic. It converts callback-based data loading in index.js to async/await patterns, adds a centralized loadResources function in common.js, and updates all settings HTML pages to dynamically load dependencies rather than using static includes. Additionally, a memory pre-check is removed from ws.cpp.

Changes

Cohort / File(s) Summary
Resource Loading Infrastructure
wled00/data/common.js
Introduces new loadResources(files, init) function that sequentially loads external JS/CSS resources with retry-on-error logic and a completion callback.
Initialization Refactor
wled00/data/index.js
Converts callback-based data loading to async/await patterns; updates signatures for loadPalettes(), loadFX(), loadFXData(), loadPalettesData(), loadPresets(), requestJson(), and loadSkinCSS() to return Promises; centralizes initialization with try/catch error handling and toast notifications.
Settings HTML Pages
wled00/data/settings.htm, settings_2D.htm, settings_dmx.htm, settings_leds.htm, settings_sec.htm, settings_sync.htm, settings_time.htm, settings_ui.htm, settings_um.htm, settings_wifi.htm
Replaces static script/style includes with dynamic loaders using IIFE pattern; adds loading guards to prevent flash of unstyled content; implements retry logic for common.js loading; defers initialization (S()) until resources are fully loaded.
Backend Memory Handling
wled00/ws.cpp
Removes pre-emptive heap-availability check in sendDataWs() for ESP8266 prior to creating AsyncWebSocketBuffer, relying on subsequent allocation checks instead.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Rationale: The refactor spans significant scope with dense async/await logic changes in index.js affecting multiple function signatures and initialization flow. While the 10+ HTML files follow a consistent, repetitive pattern (reducing per-file cognitive load), the aggregate number of files and need to verify consistency across all pages increases overall review effort. The backend change is minor but requires context verification.

Possibly related PRs

Suggested reviewers

  • netmindz
  • blazoncek

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Use sequential loading and requests for all UI resources" directly and comprehensively captures the primary intent of the pull request. The changes include adding sequential resource loading via a new loadResources function and dynamically updating all configuration pages to load common.js and style.css sequentially with retry logic, as well as refactoring index.js initialization and data requests to use promise-based sequential flows instead of nested callbacks. The title is specific, concise, and avoids vague terminology, making it clear to teammates scanning the history that the PR addresses sequential initialization patterns for UI resources.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
wled00/data/common.js (1)

54-82: Bound retries + backoff; avoid perma-hidden UI and tight retry loop

Current logic retries indefinitely every 100ms on load error, which can keep the UI hidden forever if a resource is missing or path is wrong. Add a bounded retry with backoff and a safe skip to the next resource, plus a once-only load handler and fix a small comment typo.

Apply this diff:

-function loadResources(files, init) {
-	let i = 0;
+function loadResources(files, init, opts) {
+	let i = 0;
+	const retries  = opts?.retries ?? 5;
+	const baseWait = opts?.delay  ?? 100;   // ms
+	const backoff  = opts?.backoff ?? 1.5;  // multiplier
+	const attempts = Object.create(null);
 	const loadNext = () => {
 		if (i >= files.length) {
 			if (init) {
-				d.documentElement.style.visibility = 'visible'; // make page visible after all files are laoded if it was hidden (prevent ugly display)
-				d.readyState === 'complete' ? init() : window.addEventListener('load', init);
+				d.documentElement.style.visibility = 'visible'; // make page visible after all files are loaded if it was hidden (prevent ugly display)
+				if (d.readyState === 'complete') init();
+				else window.addEventListener('load', init, { once: true });
 			}
 			return;
 		}
 		const file = files[i++];
 		const isCSS = file.endsWith('.css');
 		const el = d.createElement(isCSS ? 'link' : 'script');
 		if (isCSS) {
 			el.rel = 'stylesheet';
 			el.href = file;
 		} else {
 			el.src = file;
 		}
 		el.onload = () => {	setTimeout(loadNext, 0);};
-		el.onerror = () => {
-			i--; // load this file again
-			setTimeout(loadNext, 100);
-		};
+		el.onerror = () => {
+			const n = (attempts[file] || 0) + 1;
+			attempts[file] = n;
+			if (n >= retries) {
+				console.warn(`loadResources: giving up on ${file} after ${n} attempts`);
+				// skip this file and continue with the rest
+				setTimeout(loadNext, 0);
+				return;
+			}
+			// retry same file with capped exponential backoff
+			const wait = Math.min(5000, Math.round(baseWait * Math.pow(backoff, n - 1)));
+			i--; // reattempt current file
+			setTimeout(loadNext, wait);
+		};
 		d.head.appendChild(el);
 	};
 	loadNext();
 }

Optionally, let callers pass {retries, delay, backoff} to tune behavior per page.

wled00/data/settings_ui.htm (1)

11-18: Add bounded retries and minimal fallback for common.js loader

Prevent an infinite retry loop and blank page if common.js can’t be fetched. Add a small counter, backoff, and a user-visible fallback.

-	(function loadFiles() {
+	(function loadFiles() {
+		let tries = 0;
 		const l = document.createElement('script');
 		l.src = 'common.js';
-		l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-		l.onerror = () => setTimeout(loadFiles, 100);
+		l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
+		l.onerror = () => {
+			if (++tries >= 5) {
+				console.warn('Failed to load common.js after 5 attempts');
+				d.documentElement.style.visibility = 'visible';
+				alert('Failed to load common.js. Please reload the page.');
+				return;
+			}
+			setTimeout(loadFiles, Math.min(5000, 100 * tries)); // simple backoff
+		};
 		document.head.appendChild(l);
 	})();
wled00/data/settings_time.htm (1)

11-18: Harden common.js retry loop

Same as other settings pages: add bounded retries with backoff and a visible fallback to avoid an endlessly hidden page if common.js fails.

-	(function loadFiles() {
+	(function loadFiles() {
+		let tries = 0;
 		const l = document.createElement('script');
 		l.src = 'common.js';
 		l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-		l.onerror = () => setTimeout(loadFiles, 100);
+		l.onerror = () => {
+			if (++tries >= 5) {
+				console.warn('Failed to load common.js after 5 attempts');
+				d.documentElement.style.visibility = 'visible';
+				alert('Failed to load common.js. Please reload the page.');
+				return;
+			}
+			setTimeout(loadFiles, Math.min(5000, 100 * tries));
+		};
 		document.head.appendChild(l);
 	})();
wled00/data/settings_um.htm (1)

13-21: Bound retries with backoff for loader; add UX on failure

Mirror the improved loader pattern so this page doesn’t get stuck hidden if common.js can’t be fetched.

-	(function loadFiles() {
+	(function loadFiles() {
+		let tries = 0;
 		const l = document.createElement('script');
 		l.src = 'common.js';
 		l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-		l.onerror = () => setTimeout(loadFiles, 100);
+		l.onerror = () => {
+			if (++tries >= 5) {
+				console.warn('Failed to load common.js after 5 attempts');
+				d.documentElement.style.visibility = 'visible';
+				alert('Failed to load common.js. Please reload the page.');
+				return;
+			}
+			setTimeout(loadFiles, Math.min(5000, 100 * tries));
+		};
 		document.head.appendChild(l);
 	})();
wled00/data/settings_sec.htm (1)

9-16: Avoid infinite retry loop; add backoff and user feedback

Harden the loader to prevent an endlessly hidden UI if common.js fails.

-		(function loadFiles() {
+		(function loadFiles() {
+			let tries = 0;
 			const l = document.createElement('script');
 			l.src = 'common.js';
 			l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-			l.onerror = () => setTimeout(loadFiles, 100);
+			l.onerror = () => {
+				if (++tries >= 5) {
+					console.warn('Failed to load common.js after 5 attempts');
+					d.documentElement.style.visibility = 'visible';
+					alert('Failed to load common.js. Please reload the page.');
+					return;
+				}
+				setTimeout(loadFiles, Math.min(5000, 100 * tries));
+			};
 			document.head.appendChild(l);
 		})();
wled00/data/settings_sync.htm (1)

48-50: Redundant getURL function definition.

The getURL function is defined locally on lines 48-50, but it's already provided by common.js which is loaded dynamically before S() is called (line 13). Since getURL is only called from within S() (lines 45-46) after common.js has loaded, this local definition appears redundant.

Consider removing the local definition to avoid duplication:

-function getURL(path) {
-	return (loc ? locproto + "//" + locip : "") + path;
-}

Note: Verify that no other code paths call getURL before common.js loads.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca5debe and 8fc6cac.

📒 Files selected for processing (13)
  • wled00/data/common.js (1 hunks)
  • wled00/data/index.js (13 hunks)
  • wled00/data/settings.htm (1 hunks)
  • wled00/data/settings_2D.htm (2 hunks)
  • wled00/data/settings_dmx.htm (2 hunks)
  • wled00/data/settings_leds.htm (3 hunks)
  • wled00/data/settings_sec.htm (2 hunks)
  • wled00/data/settings_sync.htm (2 hunks)
  • wled00/data/settings_time.htm (2 hunks)
  • wled00/data/settings_ui.htm (2 hunks)
  • wled00/data/settings_um.htm (2 hunks)
  • wled00/data/settings_wifi.htm (2 hunks)
  • wled00/ws.cpp (0 hunks)
💤 Files with no reviewable changes (1)
  • wled00/ws.cpp
🧰 Additional context used
📓 Path-based instructions (3)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/common.js
  • wled00/data/settings_dmx.htm
  • wled00/data/settings_time.htm
  • wled00/data/settings_um.htm
  • wled00/data/settings_leds.htm
  • wled00/data/settings_sec.htm
  • wled00/data/settings_wifi.htm
  • wled00/data/settings_sync.htm
  • wled00/data/settings_2D.htm
  • wled00/data/settings_ui.htm
  • wled00/data/index.js
  • wled00/data/settings.htm
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/common.js
  • wled00/data/settings_dmx.htm
  • wled00/data/settings_time.htm
  • wled00/data/settings_um.htm
  • wled00/data/settings_leds.htm
  • wled00/data/settings_sec.htm
  • wled00/data/settings_wifi.htm
  • wled00/data/settings_sync.htm
  • wled00/data/settings_2D.htm
  • wled00/data/settings_ui.htm
  • wled00/data/index.js
  • wled00/data/settings.htm
wled00/data/settings*.htm

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Name settings pages as settings*.htm within the web UI

Files:

  • wled00/data/settings_dmx.htm
  • wled00/data/settings_time.htm
  • wled00/data/settings_um.htm
  • wled00/data/settings_leds.htm
  • wled00/data/settings_sec.htm
  • wled00/data/settings_wifi.htm
  • wled00/data/settings_sync.htm
  • wled00/data/settings_2D.htm
  • wled00/data/settings_ui.htm
  • wled00/data/settings.htm
🧠 Learnings (1)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/settings*.htm : Name settings pages as settings*.htm within the web UI

Applied to files:

  • wled00/data/settings.htm
🪛 Biome (2.1.2)
wled00/data/index.js

[error] 2815-2816: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
🔇 Additional comments (13)
wled00/data/settings_ui.htm (3)

7-7: LGTM: prevent FOUC while dependencies load

Hiding html until resources load avoids flicker; loadResources() makes it visible on completion.


221-221: LGTM: onload removal aligns with deferred init

Init via loader keeps ordering guarantees and avoids “xy is undefined” before common.js.


7-7: File already uses correct tab-based indentation per guidelines

The indentation in wled00/data/settings_ui.htm is already compliant with coding guidelines—all indented lines use tabs (^I), not spaces. Ensure npm run build is run after making any web UI modifications to regenerate embedded headers before firmware compilation.

wled00/data/settings_time.htm (2)

7-7: LGTM: no-FUOC guard matches loadResources reveal

Hiding html until resource load completes is appropriate here.


132-132: LGTM: init moved off body onload

Consistent with the new sequential loader flow.

wled00/data/settings_um.htm (2)

7-7: LGTM: initial hide to prevent unstyled flash

Pairs correctly with loadResources’ reveal.


284-284: LGTM: body onload removal aligns with deferred init

Keeps init ordering consistent.

wled00/data/settings_sec.htm (2)

7-7: LGTM: FOUC prevention matches common.js reveal

Consistent with the shared loader behavior.


42-42: LGTM: loader-based init replaces body onload

Keeps dependency order deterministic.

wled00/data/settings_dmx.htm (1)

10-16: Verify infinite retry behavior is intentional.

The loadFiles IIFE will retry loading common.js indefinitely every 100ms on failure, whereas other data loading functions in index.js limit retries to 5 attempts. Under persistent network failures or misconfigurations, this could result in an infinite loop of retry attempts.

Consider whether a retry limit (matching the 5-retry pattern used elsewhere) would be more appropriate:

-(function loadFiles() {
+(function loadFiles(attempt = 0) {
 	const l = document.createElement('script');
 	l.src = 'common.js';
 	l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-	l.onerror = () => setTimeout(loadFiles, 100);
+	l.onerror = () => { if (attempt < 5) setTimeout(() => loadFiles(attempt + 1), 100); };
 	document.head.appendChild(l);
 })();

This pattern appears in all settings_*.htm files.

wled00/data/index.js (3)

279-294: Sequential initialization looks good, but verify error handling doesn't halt progression.

The sequential loading pattern correctly addresses the low-heap/503-error issue mentioned in the PR objectives. Each async function properly resolves even on error, allowing initialization to continue.

One minor suggestion: Consider logging initialization progress for debugging:

 (async ()=>{
 	try {
+		console.log('[WLED] Starting initialization...');
 		await loadPalettes();        // loads base palettes and builds #pallist (safe first)
 		await loadFXData();          // loads fx data
 		await loadFX();              // populates effect list
 		await loadPalettesData();    // fills palettesData[] for previews
 		await requestJson();         // fills lastinfo.cpalcount (safe now, pallist exists)
 		populatePalettes();          // repopulate with custom palettes now that cpalcount is known
 		await loadPresets();         // load presets last
 		if (cfg.comp.css) await loadSkinCSS('skinCss');
+		console.log('[WLED] Initialization complete');
 		if (!ws) makeWS();
 	} catch(e) {
 		showToast("Init failed: " + e, true);
+		console.error('[WLED] Initialization error:', e);
 	}
 })();

516-537: Well-structured Promise conversion with retry logic.

The conversion from callback-based to Promise-based loading is clean and maintains the retry logic (5 attempts with 100ms intervals). The recursive retry pattern correctly preserves the Promise chain.


1693-1768: requestJson Promise conversion maintains backward compatibility well.

The Promise-based refactoring preserves the WebSocket fallback logic and correctly handles the command parameter. The retry logic (up to 10 attempts for requestJson vs 5 for data loading) seems intentional for higher reliability of state changes.

Minor observation: The higher retry count (10) for requestJson compared to data loading (5) appears deliberate for ensuring command reliability.

Comment on lines +2807 to +2832
function loadPalettesData() {
return new Promise((resolve) => {
if (palettesData) return resolve(); // already loaded
const lsKey = "wledPalx";
var lsPalData = localStorage.getItem(lsKey);
if (lsPalData) {
try {
var d = JSON.parse(lsPalData);
if (d && d.vid == d.vid) {
palettesData = d.p;
return resolve();
}
} catch (e) {}
}

palettesData = {};
getPalettesData(0, ()=>{
localStorage.setItem(lsKey, JSON.stringify({
p: palettesData,
vid: lastinfo.vid
}));
redrawPalPrev();
if (callback) setTimeout(callback, 99);
palettesData = {};
getPalettesData(0, () => {
localStorage.setItem(lsKey, JSON.stringify({
p: palettesData,
vid: lastinfo.vid
}));
redrawPalPrev();
setTimeout(resolve, 99); // delay optional
});
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical bug: Self-comparison in version check.

Line 2815 contains a self-comparison d.vid == d.vid which will always be true, making the version check ineffective. This appears to be a typo that should compare against lastinfo.vid.

Apply this fix:

 		try {
 			var d = JSON.parse(lsPalData);
-			if (d && d.vid == d.vid) {
+			if (d && d.vid == lastinfo.vid) {
 				palettesData = d.p;
 				return resolve();
 			}
 		} catch (e) {}

Based on static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function loadPalettesData() {
return new Promise((resolve) => {
if (palettesData) return resolve(); // already loaded
const lsKey = "wledPalx";
var lsPalData = localStorage.getItem(lsKey);
if (lsPalData) {
try {
var d = JSON.parse(lsPalData);
if (d && d.vid == d.vid) {
palettesData = d.p;
return resolve();
}
} catch (e) {}
}
palettesData = {};
getPalettesData(0, ()=>{
localStorage.setItem(lsKey, JSON.stringify({
p: palettesData,
vid: lastinfo.vid
}));
redrawPalPrev();
if (callback) setTimeout(callback, 99);
palettesData = {};
getPalettesData(0, () => {
localStorage.setItem(lsKey, JSON.stringify({
p: palettesData,
vid: lastinfo.vid
}));
redrawPalPrev();
setTimeout(resolve, 99); // delay optional
});
});
}
}
function loadPalettesData() {
return new Promise((resolve) => {
if (palettesData) return resolve(); // already loaded
const lsKey = "wledPalx";
var lsPalData = localStorage.getItem(lsKey);
if (lsPalData) {
try {
var d = JSON.parse(lsPalData);
if (d && d.vid == lastinfo.vid) {
palettesData = d.p;
return resolve();
}
} catch (e) {}
}
palettesData = {};
getPalettesData(0, () => {
localStorage.setItem(lsKey, JSON.stringify({
p: palettesData,
vid: lastinfo.vid
}));
redrawPalPrev();
setTimeout(resolve, 99); // delay optional
});
});
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 2815-2816: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)

🤖 Prompt for AI Agents
In wled00/data/index.js around lines 2807 to 2832, the version check currently
does a self-comparison (d.vid == d.vid) which is wrong; change that comparison
to validate against the current app version (e.g., d.vid === lastinfo.vid) and
guard for missing lastinfo or lastinfo.vid before comparing; update the if
condition accordingly so that cached palettes are only used when d exists and
d.vid equals lastinfo.vid.

Comment on lines +2834 to 2852
function getPalettesData(page, callback) {
fetch(getURL(`/json/palx?page=${page}`), {method: 'get'})
.then(res => res.ok ? res.json() : Promise.reject())
.then(json => {
retry = false;
retry = 0;
palettesData = Object.assign({}, palettesData, json.p);
if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75);
else callback();
})
.catch((error)=>{
if (!retry) {
retry = true;
setTimeout(()=>{getPalettesData(page,callback);}, 500); // retry
if (retry<5) {
retry++;
setTimeout(()=>{getPalettesData(page,callback);}, 100);
} else {
showToast(error, true);
callback();
}
showToast(error, true);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify retry counter reset between page loads.

The retry variable (line 19) is used across multiple loading functions. Ensure it's properly scoped or reset between different loading operations to prevent retry exhaustion from one function affecting another.

Run the following check to verify retry counter usage:

The retry counter should be reset (e.g., retry = 0) at the start of each independent loading operation or scoped locally to each function.


🏁 Script executed:

#!/bin/bash
# Check all usages of the retry variable
rg -n '\bretry\b' wled00/data/index.js

Length of output: 378


I need to identify the functions using the retry variable to understand if they could run concurrently and if there's actual isolation or interference.

Now I need to identify the functions using the retry counter to understand if they can run concurrently.


Let me search differently to identify these functions more clearly:



Let me use a more targeted approach:


Actually, let me search for the specific functions more directly by examining the context around the retry lines:




Let me try a simpler approach - just look at the actual file around the retry lines:




🏁 Script executed:

#!/bin/bash
# Find function definitions containing retry usage
# Search for function declarations near retry lines
rg -B 10 '\bretry\b' wled00/data/index.js | grep -E '(^[0-9]+-.*function |^[0-9]+-.*\bretry\b)'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get context for each retry usage - find function names
cat wled00/data/index.js | awk '/^function|^var.*retry|^\t*(if|retry).*retry/ {
  if (/^function/) func=$0;
  if (/retry/) print NR": " func " >> " $0
}'

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Find lines with retry and show what function they belong to
# Using sed to extract function scope
rg -B 20 '\bretry\b' wled00/data/index.js | grep -A 5 'function.*' | head -100

Length of output: 908


🏁 Script executed:

#!/bin/bash
# Extract the function definitions that contain retry usage
ast-grep --pattern 'function $NAME($_) {
  $$$
  retry
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get context around each retry usage to identify functions
rg -B 25 'retry\s*=' wled00/data/index.js | grep -E '(function |retry\s*=)'

Length of output: 214


🏁 Script executed:

#!/bin/bash
# Show file content around critical retry lines  
sed -n '515,560p' wled00/data/index.js
sed -n '2830,2850p' wled00/data/index.js

Length of output: 1466


Now let me check where these functions are called to understand if they execute concurrently:


---

🏁 Script executed:

```shell
#!/bin/bash
# Find where loadPalettes, loadFX, loadFXData, getPalettesData are called
rg -n '(loadPalettes|loadFX|loadFXData|getPalettesData)\(' wled00/data/index.js | grep -v 'function \|async function'

Length of output: 566


Address the global retry variable causing race conditions between concurrent loading functions.

The concern is valid. The code shares a single global retry variable across multiple async loading functions (loadPalettes, loadFX, loadFXData, getPalettesData). While the primary execution path (lines 282-284) awaits these sequentially, the retry paths scheduled via setTimeout can fire concurrently:

  1. Race scenario: If loadPalettes() fails and schedules a retry via setTimeout, then getPalettesData() is called and fails simultaneously, both functions increment/reset the same global retry counter. The first function to complete its .then() resets retry=0, corrupting the second function's retry tracking.

  2. Pagination conflict: During getPalettesData() pagination with retries active, another loading function's failure can increment the shared retry variable, causing one function's exhausted retries to block another's recovery attempts.

Fix: Make retry local to each function or maintain per-function retry tracking. For example, change from global var retry = 0; to passing retry as a parameter or storing it in a scoped context within each async function.

🤖 Prompt for AI Agents
In wled00/data/index.js around lines 2834 to 2852, the function getPalettesData
relies on a shared global retry counter which causes race conditions when
multiple loaders retry concurrently; make retry local to each call by removing
dependency on the global variable and either (A) add a retry parameter to
getPalettesData(page, callback, retry = 0) and use that in recursive calls and
setTimeout retries so each call tracks its own attempts, or (B) declare a local
let retry = 0 inside a wrapper function that defines the recursive inner loader
(or use a closure) so pagination recursion and scheduled retries increment/reset
only that call’s counter; update all call sites accordingly so no global retry
variable is used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant