fix(gateway): cap channel startup fanout

This commit is contained in:
Peter Steinberger
2026-05-02 15:26:25 +01:00
parent 829364f85e
commit bf2711b40e
7 changed files with 177 additions and 21 deletions

View File

@@ -420,6 +420,18 @@ describe("gateway bonjour advertiser", () => {
expect.stringContaining("suppressing ciao netmask assertion"),
);
logger.warn.mockClear();
expect(
handler?.(
new Error(
"Can't probe for a service which is announced already. Received announcing for service OpenClaw Gateway._openclaw._tcp.local.",
),
),
).toBe(true);
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining("suppressing ciao self-probe race"),
);
await started.stop();
});

View File

@@ -400,7 +400,11 @@ export async function startGatewayBonjourAdvertiser(
);
} else {
const label =
classification.kind === "netmask-assertion" ? "netmask assertion" : "interface assertion";
classification.kind === "netmask-assertion"
? "netmask assertion"
: classification.kind === "self-probe"
? "self-probe race"
: "interface assertion";
logger.warn(`bonjour: suppressing ciao ${label}: ${classification.formatted}`);
requestCiaoRecovery?.(classification);
}

View File

@@ -49,6 +49,20 @@ describe("bonjour-ciao", () => {
});
});
it("classifies ciao self-probe races separately from side effects", () => {
expect(
classifyCiaoUnhandledRejection(
new Error(
"Can't probe for a service which is announced already. Received announcing for service OpenClaw Gateway._openclaw._tcp.local.",
),
),
).toEqual({
kind: "self-probe",
formatted:
"Can't probe for a service which is announced already. Received announcing for service OpenClaw Gateway._openclaw._tcp.local.",
});
});
it("suppresses ciao announcement cancellation rejections", () => {
expect(ignoreCiaoUnhandledRejection(new Error("Ciao announcement cancelled by shutdown"))).toBe(
true,

View File

@@ -5,6 +5,8 @@ const CIAO_INTERFACE_ASSERTION_MESSAGE_RE =
/REACHED ILLEGAL STATE!?\s+IPV4 ADDRESS CHANGED? FROM (?:DEFINED TO UNDEFINED|UNDEFINED TO DEFINED)!?/u;
const CIAO_NETMASK_ASSERTION_MESSAGE_RE =
/IP ADDRESS VERSION MUST MATCH\.\s+NETMASK CANNOT HAVE A VERSION DIFFERENT FROM THE ADDRESS!?/u;
const CIAO_SELF_PROBE_MESSAGE_RE =
/CAN'T PROBE FOR A SERVICE WHICH IS ANNOUNCED ALREADY\.\s+RECEIVED (?:PROBING|ANNOUNCING|ANNOUNCED) FOR SERVICE\b/u;
// Restricted sandboxes (NemoClaw, Docker-in-Docker, k3s with locked-down policy)
// can refuse os.networkInterfaces(), which ciao calls during NetworkManager init.
// Node surfaces this as a SystemError mentioning the libuv syscall by name.
@@ -14,6 +16,7 @@ export type CiaoProcessErrorClassification =
| { kind: "cancellation"; formatted: string }
| { kind: "interface-assertion"; formatted: string }
| { kind: "netmask-assertion"; formatted: string }
| { kind: "self-probe"; formatted: string }
| { kind: "interface-enumeration-failure"; formatted: string };
function collectCiaoProcessErrorCandidates(reason: unknown): unknown[] {
@@ -69,6 +72,9 @@ export function classifyCiaoProcessError(reason: unknown): CiaoProcessErrorClass
if (CIAO_NETMASK_ASSERTION_MESSAGE_RE.test(message)) {
return { kind: "netmask-assertion", formatted };
}
if (CIAO_SELF_PROBE_MESSAGE_RE.test(message)) {
return { kind: "self-probe", formatted };
}
if (CIAO_INTERFACE_ENUMERATION_FAILURE_RE.test(message)) {
return { kind: "interface-enumeration-failure", formatted };
}