From 7493bab6fb3372f8c1cfc2e023facba468098792 Mon Sep 17 00:00:00 2001 From: daiping8 Date: Sat, 27 Jun 2026 15:46:43 +0800 Subject: [PATCH 1/5] [dashboard] Fix Worker table crash when psutil memory info is null memoryConverter threw "Cannot read properties of null (reading 'toFixed')" when the reporter agent serialized a null value for a psutil process stat field (e.g. pfaults/pageins on platforms where psutil cannot read them or raises AccessDenied). null < 1024 coerced to true, so the code reached null.toFixed(4) and crashed, making the node Worker info page fail to render. Guard against null/undefined/NaN and return "-" (an explicit no-data placeholder) instead of throwing. Update the type signature to reflect that psutil as_dict() can emit null, and add regression tests for null, undefined, and NaN. Signed-off-by: daiping8 --- .../dashboard/client/src/util/converter.ts | 15 +++++++++++- .../client/src/util/converter.unit.test.ts | 23 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/python/ray/dashboard/client/src/util/converter.ts b/python/ray/dashboard/client/src/util/converter.ts index 0bfd52736de6..0a42587b0f56 100644 --- a/python/ray/dashboard/client/src/util/converter.ts +++ b/python/ray/dashboard/client/src/util/converter.ts @@ -1,4 +1,17 @@ -export const memoryConverter = (bytes: number) => { +export const memoryConverter = (bytes: number | undefined | null) => { + // The reporter agent serializes psutil process stats via as_dict(), which + // yields null for fields psutil cannot read (e.g. pfaults/pageins on some + // platforms, or when AccessDenied is raised). Guard against null/undefined + // /NaN so the Worker table keeps rendering instead of crashing on `.toFixed`. + if ( + bytes === null || + bytes === undefined || + typeof bytes !== "number" || + isNaN(bytes) + ) { + return "-"; + } + if (bytes < 1024) { return `${bytes.toFixed(4)}B`; } diff --git a/python/ray/dashboard/client/src/util/converter.unit.test.ts b/python/ray/dashboard/client/src/util/converter.unit.test.ts index a3d544c78d60..899638bed47f 100644 --- a/python/ray/dashboard/client/src/util/converter.unit.test.ts +++ b/python/ray/dashboard/client/src/util/converter.unit.test.ts @@ -37,4 +37,27 @@ describe("memoryConverter", () => { test.each(table)("$name", ({ input, expected }) => { expect(memoryConverter(input)).toEqual(expected); }); + + // The reporter agent serializes psutil process stats with as_dict(), which + // emits null for fields that psutil cannot read (e.g. pfaults/pageins on + // some platforms or when AccessDenied is raised). memoryConverter must not + // crash on null/undefined/NaN, otherwise the Worker table fails to render. + // See: https://github.com/ray-project/ray/issues (Cannot read properties of + // null (reading 'toFixed') at converter.ts) + describe("edge cases", () => { + test("returns '-' for null instead of throwing", () => { + expect(() => memoryConverter(null)).not.toThrow(); + expect(memoryConverter(null)).toEqual("-"); + }); + + test("returns '-' for undefined instead of throwing", () => { + expect(() => memoryConverter(undefined)).not.toThrow(); + expect(memoryConverter(undefined)).toEqual("-"); + }); + + test("returns '-' for NaN instead of throwing", () => { + expect(() => memoryConverter(NaN)).not.toThrow(); + expect(memoryConverter(NaN)).toEqual("-"); + }); + }); }); From dc5e49d6a1aed9df70137c98748b6f76756ea4ef Mon Sep 17 00:00:00 2001 From: daiping8 Date: Mon, 29 Jun 2026 09:40:53 +0800 Subject: [PATCH 2/5] [dashboard] Simplify memoryConverter guard per review Address review feedback on #64402: - Drop redundant explicit null/undefined checks; typeof !== "number" already covers them. - Use Number.isNaN instead of the global isNaN to avoid implicit coercion. - Remove redundant not.toThrow() wrappers from the edge-case tests. Signed-off-by: 10353800 <10353800@users.noreply.github.com> Signed-off-by: daiping8 --- python/ray/dashboard/client/src/util/converter.ts | 7 +------ .../ray/dashboard/client/src/util/converter.unit.test.ts | 9 +++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/python/ray/dashboard/client/src/util/converter.ts b/python/ray/dashboard/client/src/util/converter.ts index 0a42587b0f56..f103ccfba38a 100644 --- a/python/ray/dashboard/client/src/util/converter.ts +++ b/python/ray/dashboard/client/src/util/converter.ts @@ -3,12 +3,7 @@ export const memoryConverter = (bytes: number | undefined | null) => { // yields null for fields psutil cannot read (e.g. pfaults/pageins on some // platforms, or when AccessDenied is raised). Guard against null/undefined // /NaN so the Worker table keeps rendering instead of crashing on `.toFixed`. - if ( - bytes === null || - bytes === undefined || - typeof bytes !== "number" || - isNaN(bytes) - ) { + if (typeof bytes !== "number" || Number.isNaN(bytes)) { return "-"; } diff --git a/python/ray/dashboard/client/src/util/converter.unit.test.ts b/python/ray/dashboard/client/src/util/converter.unit.test.ts index 899638bed47f..6725da662f2f 100644 --- a/python/ray/dashboard/client/src/util/converter.unit.test.ts +++ b/python/ray/dashboard/client/src/util/converter.unit.test.ts @@ -45,18 +45,15 @@ describe("memoryConverter", () => { // See: https://github.com/ray-project/ray/issues (Cannot read properties of // null (reading 'toFixed') at converter.ts) describe("edge cases", () => { - test("returns '-' for null instead of throwing", () => { - expect(() => memoryConverter(null)).not.toThrow(); + test("returns '-' for null", () => { expect(memoryConverter(null)).toEqual("-"); }); - test("returns '-' for undefined instead of throwing", () => { - expect(() => memoryConverter(undefined)).not.toThrow(); + test("returns '-' for undefined", () => { expect(memoryConverter(undefined)).toEqual("-"); }); - test("returns '-' for NaN instead of throwing", () => { - expect(() => memoryConverter(NaN)).not.toThrow(); + test("returns '-' for NaN", () => { expect(memoryConverter(NaN)).toEqual("-"); }); }); From e5b3b5014a7c46542682303d58e24728bd93e51d Mon Sep 17 00:00:00 2001 From: daiping8 Date: Sat, 27 Jun 2026 15:46:43 +0800 Subject: [PATCH 3/5] [dashboard] Fix Worker table crash when psutil memory info is null memoryConverter threw "Cannot read properties of null (reading 'toFixed')" when the reporter agent serialized a null value for a psutil process stat field (e.g. pfaults/pageins on platforms where psutil cannot read them or raises AccessDenied). null < 1024 coerced to true, so the code reached null.toFixed(4) and crashed, making the node Worker info page fail to render. Guard against null/undefined/NaN and return "-" (an explicit no-data placeholder) instead of throwing. Update the type signature to reflect that psutil as_dict() can emit null, and add regression tests for null, undefined, and NaN. Signed-off-by: daiping8 --- .../dashboard/client/src/util/converter.ts | 15 +++++++++++- .../client/src/util/converter.unit.test.ts | 23 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/python/ray/dashboard/client/src/util/converter.ts b/python/ray/dashboard/client/src/util/converter.ts index 0bfd52736de6..0a42587b0f56 100644 --- a/python/ray/dashboard/client/src/util/converter.ts +++ b/python/ray/dashboard/client/src/util/converter.ts @@ -1,4 +1,17 @@ -export const memoryConverter = (bytes: number) => { +export const memoryConverter = (bytes: number | undefined | null) => { + // The reporter agent serializes psutil process stats via as_dict(), which + // yields null for fields psutil cannot read (e.g. pfaults/pageins on some + // platforms, or when AccessDenied is raised). Guard against null/undefined + // /NaN so the Worker table keeps rendering instead of crashing on `.toFixed`. + if ( + bytes === null || + bytes === undefined || + typeof bytes !== "number" || + isNaN(bytes) + ) { + return "-"; + } + if (bytes < 1024) { return `${bytes.toFixed(4)}B`; } diff --git a/python/ray/dashboard/client/src/util/converter.unit.test.ts b/python/ray/dashboard/client/src/util/converter.unit.test.ts index a3d544c78d60..899638bed47f 100644 --- a/python/ray/dashboard/client/src/util/converter.unit.test.ts +++ b/python/ray/dashboard/client/src/util/converter.unit.test.ts @@ -37,4 +37,27 @@ describe("memoryConverter", () => { test.each(table)("$name", ({ input, expected }) => { expect(memoryConverter(input)).toEqual(expected); }); + + // The reporter agent serializes psutil process stats with as_dict(), which + // emits null for fields that psutil cannot read (e.g. pfaults/pageins on + // some platforms or when AccessDenied is raised). memoryConverter must not + // crash on null/undefined/NaN, otherwise the Worker table fails to render. + // See: https://github.com/ray-project/ray/issues (Cannot read properties of + // null (reading 'toFixed') at converter.ts) + describe("edge cases", () => { + test("returns '-' for null instead of throwing", () => { + expect(() => memoryConverter(null)).not.toThrow(); + expect(memoryConverter(null)).toEqual("-"); + }); + + test("returns '-' for undefined instead of throwing", () => { + expect(() => memoryConverter(undefined)).not.toThrow(); + expect(memoryConverter(undefined)).toEqual("-"); + }); + + test("returns '-' for NaN instead of throwing", () => { + expect(() => memoryConverter(NaN)).not.toThrow(); + expect(memoryConverter(NaN)).toEqual("-"); + }); + }); }); From fb0c86b378776734bc705aa88cb31abe52458bc4 Mon Sep 17 00:00:00 2001 From: daiping8 Date: Mon, 29 Jun 2026 09:40:53 +0800 Subject: [PATCH 4/5] [dashboard] Simplify memoryConverter guard per review Address review feedback on #64402: - Drop redundant explicit null/undefined checks; typeof !== "number" already covers them. - Use Number.isNaN instead of the global isNaN to avoid implicit coercion. - Remove redundant not.toThrow() wrappers from the edge-case tests. Signed-off-by: 10353800 <10353800@users.noreply.github.com> Signed-off-by: daiping8 --- python/ray/dashboard/client/src/util/converter.ts | 7 +------ .../ray/dashboard/client/src/util/converter.unit.test.ts | 9 +++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/python/ray/dashboard/client/src/util/converter.ts b/python/ray/dashboard/client/src/util/converter.ts index 0a42587b0f56..f103ccfba38a 100644 --- a/python/ray/dashboard/client/src/util/converter.ts +++ b/python/ray/dashboard/client/src/util/converter.ts @@ -3,12 +3,7 @@ export const memoryConverter = (bytes: number | undefined | null) => { // yields null for fields psutil cannot read (e.g. pfaults/pageins on some // platforms, or when AccessDenied is raised). Guard against null/undefined // /NaN so the Worker table keeps rendering instead of crashing on `.toFixed`. - if ( - bytes === null || - bytes === undefined || - typeof bytes !== "number" || - isNaN(bytes) - ) { + if (typeof bytes !== "number" || Number.isNaN(bytes)) { return "-"; } diff --git a/python/ray/dashboard/client/src/util/converter.unit.test.ts b/python/ray/dashboard/client/src/util/converter.unit.test.ts index 899638bed47f..6725da662f2f 100644 --- a/python/ray/dashboard/client/src/util/converter.unit.test.ts +++ b/python/ray/dashboard/client/src/util/converter.unit.test.ts @@ -45,18 +45,15 @@ describe("memoryConverter", () => { // See: https://github.com/ray-project/ray/issues (Cannot read properties of // null (reading 'toFixed') at converter.ts) describe("edge cases", () => { - test("returns '-' for null instead of throwing", () => { - expect(() => memoryConverter(null)).not.toThrow(); + test("returns '-' for null", () => { expect(memoryConverter(null)).toEqual("-"); }); - test("returns '-' for undefined instead of throwing", () => { - expect(() => memoryConverter(undefined)).not.toThrow(); + test("returns '-' for undefined", () => { expect(memoryConverter(undefined)).toEqual("-"); }); - test("returns '-' for NaN instead of throwing", () => { - expect(() => memoryConverter(NaN)).not.toThrow(); + test("returns '-' for NaN", () => { expect(memoryConverter(NaN)).toEqual("-"); }); }); From 132e0cb97b999029d7039983263a673a683fcaf3 Mon Sep 17 00:00:00 2001 From: daiping8 Date: Mon, 29 Jun 2026 20:16:17 +0800 Subject: [PATCH 5/5] format Signed-off-by: daiping8 --- python/ray/dashboard/client/src/util/converter.ts | 4 ---- python/ray/dashboard/client/src/util/converter.unit.test.ts | 6 ------ 2 files changed, 10 deletions(-) diff --git a/python/ray/dashboard/client/src/util/converter.ts b/python/ray/dashboard/client/src/util/converter.ts index f103ccfba38a..54d926b96688 100644 --- a/python/ray/dashboard/client/src/util/converter.ts +++ b/python/ray/dashboard/client/src/util/converter.ts @@ -1,8 +1,4 @@ export const memoryConverter = (bytes: number | undefined | null) => { - // The reporter agent serializes psutil process stats via as_dict(), which - // yields null for fields psutil cannot read (e.g. pfaults/pageins on some - // platforms, or when AccessDenied is raised). Guard against null/undefined - // /NaN so the Worker table keeps rendering instead of crashing on `.toFixed`. if (typeof bytes !== "number" || Number.isNaN(bytes)) { return "-"; } diff --git a/python/ray/dashboard/client/src/util/converter.unit.test.ts b/python/ray/dashboard/client/src/util/converter.unit.test.ts index 6725da662f2f..7adef830751c 100644 --- a/python/ray/dashboard/client/src/util/converter.unit.test.ts +++ b/python/ray/dashboard/client/src/util/converter.unit.test.ts @@ -38,12 +38,6 @@ describe("memoryConverter", () => { expect(memoryConverter(input)).toEqual(expected); }); - // The reporter agent serializes psutil process stats with as_dict(), which - // emits null for fields that psutil cannot read (e.g. pfaults/pageins on - // some platforms or when AccessDenied is raised). memoryConverter must not - // crash on null/undefined/NaN, otherwise the Worker table fails to render. - // See: https://github.com/ray-project/ray/issues (Cannot read properties of - // null (reading 'toFixed') at converter.ts) describe("edge cases", () => { test("returns '-' for null", () => { expect(memoryConverter(null)).toEqual("-");