Skip to content

refactor(ymax-resolver): update resolver retry helper#12676

Merged
mergify[bot] merged 8 commits into
masterfrom
rs-refactor-resolver-retry-helper
May 25, 2026
Merged

refactor(ymax-resolver): update resolver retry helper#12676
mergify[bot] merged 8 commits into
masterfrom
rs-refactor-resolver-retry-helper

Conversation

@rabi-siddique
Copy link
Copy Markdown
Contributor

@rabi-siddique rabi-siddique commented May 21, 2026

closes: https://linear.app/agoric/issue/AGO-487/follow-up-for-12626-feedback
refs: #12626

Description

Follow-up to PR #12626 addressing @gibson042 review feedback.

Upgrade Considerations

  • New planner deployment.

@rabi-siddique rabi-siddique requested a review from gibson042 May 21, 2026 06:00
Copy link
Copy Markdown
Member

@gibson042 gibson042 left a comment

Choose a reason for hiding this comment

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

Pretty good! I'm just calling for a handful of tweaks.

Comment thread services/ymax-planner/src/retries.ts Outdated
@@ -0,0 +1,132 @@
/**
* Retry helper for chain submissions (resolver settlements, plan submissions).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see anything in this file that is specific to chain submissions.

Suggested change
* Retry helper for chain submissions (resolver settlements, plan submissions).
* Helpers for automatic retries with exponential backoff.

Comment thread services/ymax-planner/src/retries.ts
* Ops alerting filters match on this prefix.
*/
alertingPrefix: string;
signal?: AbortSignal;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

signal is generic and should be in WithRetriesOpts for consumption by withRetries as well (even though withRetriesForAlerting will probably handle it directly rather than passing it along).

Comment thread services/ymax-planner/src/retries.ts
Comment thread services/ymax-planner/src/retries.ts Outdated
Comment on lines +114 to +116
const elapsedMs = now() - start;
const shouldAlert = now() - lastAlertAt >= alertIntervalMs;
if (shouldAlert) lastAlertAt = now();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
const elapsedMs = now() - start;
const shouldAlert = now() - lastAlertAt >= alertIntervalMs;
if (shouldAlert) lastAlertAt = now();
const t = now();
const elapsedMs = t - start;
const shouldAlert = t - lastAlertAt >= alertIntervalMs;
if (shouldAlert) lastAlertAt = t;

Comment thread services/ymax-planner/src/retries.ts Outdated
Comment on lines +3 to +11
*
* Unbounded retries with exponential backoff. Every failed attempt is logged,
* but only the first failure and then one every `alertIntervalMs` carry the
* caller-supplied `errorCode` prefix — the ops alerting filter matches on that
* code, so this in-code throttling keeps pages to one per interval while the
* submission stays stuck.
*
* Each retry is a fresh call through the sequencing wallet's queue, so other
* in-flight submissions interleave normally between attempts.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
*
* Unbounded retries with exponential backoff. Every failed attempt is logged,
* but only the first failure and then one every `alertIntervalMs` carry the
* caller-supplied `errorCode` prefix the ops alerting filter matches on that
* code, so this in-code throttling keeps pages to one per interval while the
* submission stays stuck.
*
* Each retry is a fresh call through the sequencing wallet's queue, so other
* in-flight submissions interleave normally between attempts.

Comment on lines +13 to +35
const makeHarness = () => {
const logs: unknown[][] = [];
const sleeps: number[] = [];
let nowMs = 1_000_000;
const log = (...args: unknown[]) => logs.push(args);
const setTimeout = ((fn: () => void, ms: number) => {
sleeps.push(ms);
nowMs += ms;
queueMicrotask(fn);
return 0 as unknown as NodeJS.Timeout;
}) as typeof globalThis.setTimeout;
const now = () => nowMs;
return { logs, sleeps, log, setTimeout, now };
};

const baseOpts = (
h: ReturnType<typeof makeHarness>,
): WithRetriesForAlertingOpts => ({
log: h.log,
setTimeout: h.setTimeout,
now: h.now,
alertingPrefix: TEST_ALERTING_PREFIX,
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AFAICT, baseOpts is only called with the output from makeHarness... let's just combine them.

Suggested change
const makeHarness = () => {
const logs: unknown[][] = [];
const sleeps: number[] = [];
let nowMs = 1_000_000;
const log = (...args: unknown[]) => logs.push(args);
const setTimeout = ((fn: () => void, ms: number) => {
sleeps.push(ms);
nowMs += ms;
queueMicrotask(fn);
return 0 as unknown as NodeJS.Timeout;
}) as typeof globalThis.setTimeout;
const now = () => nowMs;
return { logs, sleeps, log, setTimeout, now };
};
const baseOpts = (
h: ReturnType<typeof makeHarness>,
): WithRetriesForAlertingOpts => ({
log: h.log,
setTimeout: h.setTimeout,
now: h.now,
alertingPrefix: TEST_ALERTING_PREFIX,
});
const makeHarness = () => {
const logs: unknown[][] = [];
const sleeps: number[] = [];
let nowMs = 1_000_000;
const log = (...args: unknown[]) => logs.push(args);
const setTimeout = ((fn: () => void, ms: number) => {
sleeps.push(ms);
nowMs += ms;
queueMicrotask(fn);
return 0 as unknown as NodeJS.Timeout;
}) as typeof globalThis.setTimeout;
const now = () => nowMs;
const baseOpts: WithRetriesForAlertingOpts = {
log,
setTimeout,
now,
alertingPrefix: TEST_ALERTING_PREFIX,
};
return { logs, sleeps, log, setTimeout, now, baseOpts };
};

Comment on lines +43 to +46
expectCalls: number;
expectResult: unknown;
expectSleeps: number[];
expectLogs: unknown[][];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: expectSleeps and expectLogs are used for t.deepEqual, implying that expectCalls would be as well. Let's rename it to expectCallCount.

Suggested change
expectCalls: number;
expectResult: unknown;
expectSleeps: number[];
expectLogs: unknown[][];
expectCallCount: number;
expectResult: unknown;
expectSleeps: number[];
expectLogs: unknown[][];

Comment on lines +191 to +199
expectLogs: [
[
`${TEST_ALERTING_PREFIX} [tx4] settlement attempt 1 failed after 0ms, retrying in 5000ms`,
failure,
],
[
'[tx4] settlement attempt 2 failed after 5000ms, retrying in 10000ms',
failure,
],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These tests would be easier to read if they used makeExpectedLog as well.

]);
});

test('withRetries: returns the callback result on first success', async t => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please move the withRetries tests up to the top; I initially commented on them being redundant.

@rabi-siddique rabi-siddique requested a review from gibson042 May 22, 2026 05:06
@rabi-siddique rabi-siddique force-pushed the rs-refactor-resolver-retry-helper branch from 5cb3784 to 514c7d0 Compare May 22, 2026 05:15
Comment on lines +13 to +29
const makeExpectedLogFor =
(label: string) =>
(
attempt: number,
elapsedMs: number,
nextDelayMs?: number,
errLevel?: 'quiet' | 'noisy',
) => {
if (nextDelayMs === undefined) {
return [`${label} succeeded after ${attempt} attempt(s)`];
}
const baseMsg = `${label} attempt ${attempt} failed after ${elapsedMs}ms, retrying in ${nextDelayMs}ms`;
return [
errLevel === 'noisy' ? `${TEST_ALERTING_PREFIX} ${baseMsg}` : baseMsg,
failure,
];
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Readability suggestion:

Suggested change
const makeExpectedLogFor =
(label: string) =>
(
attempt: number,
elapsedMs: number,
nextDelayMs?: number,
errLevel?: 'quiet' | 'noisy',
) => {
if (nextDelayMs === undefined) {
return [`${label} succeeded after ${attempt} attempt(s)`];
}
const baseMsg = `${label} attempt ${attempt} failed after ${elapsedMs}ms, retrying in ${nextDelayMs}ms`;
return [
errLevel === 'noisy' ? `${TEST_ALERTING_PREFIX} ${baseMsg}` : baseMsg,
failure,
];
};
const makeExpectLog = (label: string) => {
const expectLog = (
attempt: number,
elapsedMs: number,
nextDelayMs?: number,
errLevel?: 'quiet' | 'noisy',
) => {
if (nextDelayMs === undefined) {
return [`${label} succeeded after ${attempt} attempt(s)`];
}
const baseMsg = `${label} attempt ${attempt} failed after ${elapsedMs}ms, retrying in ${nextDelayMs}ms`;
return [
errLevel === 'noisy' ? `${TEST_ALERTING_PREFIX} ${baseMsg}` : baseMsg,
failure,
];
};
return expectLog;
};

@rabi-siddique rabi-siddique force-pushed the rs-refactor-resolver-retry-helper branch from 514c7d0 to ebe608a Compare May 25, 2026 03:33
@rabi-siddique rabi-siddique added automerge:rebase Automatically rebase updates, then merge bypass:integration Prevent integration tests from running on PR labels May 25, 2026
@mergify mergify Bot merged commit b0cb8c0 into master May 25, 2026
115 of 118 checks passed
@mergify mergify Bot deleted the rs-refactor-resolver-retry-helper branch May 25, 2026 04:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge:rebase Automatically rebase updates, then merge bypass:integration Prevent integration tests from running on PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants