mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(telegram): retain socket failure context (#97130)
Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com>
This commit is contained in:
committed by
GitHub
parent
1bccd29304
commit
072d3ed7b5
@@ -957,7 +957,7 @@ describe("resolveTelegramFetch", () => {
|
||||
expect(eighthDispatcher).toBe(firstDispatcher);
|
||||
expect(ninthDispatcher).toBe(firstDispatcher);
|
||||
expectPinnedFallbackIpDispatcher(3);
|
||||
expectLoggerMessageContaining(loggerWarn, "fetch fallback: DNS-resolved IP unreachable");
|
||||
expectLoggerMessageContaining(loggerWarn, "fetch fallback: primary connection path failed");
|
||||
expectLoggerMessageContaining(
|
||||
loggerDebug,
|
||||
"fetch fallback: recovered from attempt 2 to attempt 0",
|
||||
@@ -1193,6 +1193,31 @@ describe("resolveTelegramFetch", () => {
|
||||
expect(undiciFetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not automatically retry structured EADDRNOTAVAIL fetch failures", async () => {
|
||||
const fetchError = buildFetchFallbackError("EADDRNOTAVAIL");
|
||||
undiciFetch.mockRejectedValue(fetchError);
|
||||
|
||||
const resolved = resolveTelegramFetchOrThrow(undefined, STICKY_IPV4_FALLBACK_NETWORK);
|
||||
|
||||
await expect(resolved("https://api.telegram.org/botx/sendMessage")).rejects.toThrow(
|
||||
"fetch failed",
|
||||
);
|
||||
|
||||
expect(undiciFetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("preserves EADDRNOTAVAIL in forced fallback diagnostics", () => {
|
||||
const transport = resolveTelegramTransport(undefined, STICKY_IPV4_FALLBACK_NETWORK);
|
||||
const fetchError = buildFetchFallbackError("EADDRNOTAVAIL");
|
||||
|
||||
expect(transport.forceFallback?.("probe timeout/network error", fetchError)).toBe(true);
|
||||
expect(transport.forceFallback?.("probe timeout/network error", fetchError)).toBe(true);
|
||||
|
||||
expectLoggerMessageContaining(loggerWarn, "primary connection path failed");
|
||||
expectLoggerMessageContaining(loggerWarn, "codes=EADDRNOTAVAIL");
|
||||
expectNoLoggerMessageContaining(loggerWarn, "DNS-resolved IP unreachable");
|
||||
});
|
||||
|
||||
it("retries sticky fallback when the local network is down during connect", async () => {
|
||||
undiciFetch
|
||||
.mockRejectedValueOnce(buildFetchFallbackError("ENETDOWN"))
|
||||
|
||||
@@ -488,9 +488,10 @@ export type TelegramTransport = {
|
||||
dispatcherAttempts?: TelegramDispatcherAttempt[];
|
||||
/**
|
||||
* Promote this transport to its next fallback dispatcher before the next
|
||||
* request. Returns false when no fallback path exists.
|
||||
* request. The original error, when available, is retained in diagnostics.
|
||||
* Returns false when no fallback path exists.
|
||||
*/
|
||||
forceFallback?: (reason: string) => boolean;
|
||||
forceFallback?: (reason: string, err?: unknown) => boolean;
|
||||
/**
|
||||
* Release all dispatchers owned by this transport and the TCP sockets they
|
||||
* hold. Safe to call multiple times; subsequent calls resolve immediately.
|
||||
@@ -563,7 +564,8 @@ function createTelegramTransportAttempts(params: {
|
||||
},
|
||||
exportAttempt: { dispatcherPolicy: fallbackIpPolicy },
|
||||
logLevel: "warn",
|
||||
logMessage: "fetch fallback: DNS-resolved IP unreachable; trying alternative Telegram API IP",
|
||||
logMessage:
|
||||
"fetch fallback: primary connection path failed; trying alternative Telegram API IP",
|
||||
});
|
||||
|
||||
return attempts;
|
||||
@@ -864,8 +866,8 @@ export function resolveTelegramTransport(
|
||||
fetch: resolvedFetch,
|
||||
sourceFetch,
|
||||
dispatcherAttempts: transportAttempts.map((attempt) => attempt.exportAttempt),
|
||||
forceFallback: (reason: string) =>
|
||||
promoteStickyAttempt(stickyAttemptIndex + 1, new Error("forced fallback"), reason),
|
||||
forceFallback: (reason: string, err?: unknown) =>
|
||||
promoteStickyAttempt(stickyAttemptIndex + 1, err ?? new Error("forced fallback"), reason),
|
||||
close,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ describe("probeTelegram retry logic", () => {
|
||||
|
||||
const result = await probePromise;
|
||||
expect(result.ok).toBe(true);
|
||||
expect(localForceFallback).toHaveBeenCalledWith("probe timeout/network error");
|
||||
expect(localForceFallback).toHaveBeenCalledWith("probe timeout/network error", timeoutError);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3); // 1 failed + 1 getMe success + 1 webhook
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
|
||||
@@ -162,7 +162,8 @@ export async function probeTelegram(
|
||||
// On timeout or network error, promote the transport to its IPv4
|
||||
// fallback dispatcher so the next retry (and all future probes
|
||||
// sharing this cached transport) skip the stalled IPv6 path.
|
||||
transport.forceFallback?.("probe timeout/network error");
|
||||
// Keep the original socket code in transport fallback diagnostics.
|
||||
transport.forceFallback?.("probe timeout/network error", err);
|
||||
if (i < 2) {
|
||||
const remainingAfterAttemptMs = resolveRemainingBudgetMs();
|
||||
if (remainingAfterAttemptMs <= 0) {
|
||||
|
||||
Reference in New Issue
Block a user