Skip to content

Commit 7b838d7

Browse files
committed
fix(server): a reset that leaves the hub cooldown in place keeps threads waiting
1 parent 377603c commit 7b838d7

2 files changed

Lines changed: 21 additions & 4 deletions

File tree

‎apps/server/src/usage/ResetCredits.test.ts‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const provider = (id: string, email: string, extra: Partial<ServerProvider> = {}
4040
const layerRedeeming = (input: {
4141
readonly outcome: ProviderConsumeResetCreditOutcome;
4242
readonly limits?: LimitsAfter;
43+
readonly warning?: string;
4344
/** Runs once the provider has spent the credit, before it answers. */
4445
readonly afterSpend?: Effect.Effect<void>;
4546
readonly recordLimitReset: Orchestrator.OrchestratorV2["Service"]["recordLimitReset"];
@@ -96,7 +97,12 @@ const layerRedeeming = (input: {
9697
]),
9798
}),
9899
Layer.mock(UsageLimitSources.UsageLimitSources)({
99-
consumeResetCredit: () => consume.pipe(Effect.map((outcome) => ({ outcome }))),
100+
consumeResetCredit: () =>
101+
consume.pipe(
102+
Effect.map((outcome) =>
103+
input.warning === undefined ? { outcome } : { outcome, warning: input.warning },
104+
),
105+
),
100106
current: usageLimits.pipe(
101107
Effect.map((usageLimits) => [
102108
{
@@ -134,6 +140,7 @@ it.effect.each([
134140
{ on: "hub", outcome: "reset", limits: "weeklyUsedUp", ends: "beforeWeeklyReset" },
135141
{ on: "instance", outcome: "reset", limits: "notReadAgain", ends: "none" },
136142
{ on: "hub", outcome: "reset", limits: "notReadAgain", ends: "none" },
143+
{ on: "hub", outcome: "reset", warning: "Cooldown not cleared.", ends: "none" },
137144
{ on: "instance", outcome: "nothingToReset", ends: "none" },
138145
{ on: "instance", outcome: "noCredit", ends: "none" },
139146
// The provider does not say whether this request's credit refilled anything.
@@ -150,6 +157,7 @@ it.effect.each([
150157
readonly on: "instance" | "hub";
151158
readonly outcome: ProviderConsumeResetCreditOutcome;
152159
readonly limits?: LimitsAfter;
160+
readonly warning?: string;
153161
readonly ends: "all" | "beforeWeeklyReset" | "none";
154162
readonly recordFails?: boolean;
155163
}) =>
@@ -168,7 +176,12 @@ it.effect.each([
168176
),
169177
);
170178
// The credit is spent either way, so the redemption reports its outcome.
171-
assert.deepEqual(result, { outcome: redemption.outcome });
179+
assert.deepEqual(
180+
result,
181+
redemption.warning === undefined
182+
? { outcome: redemption.outcome }
183+
: { outcome: redemption.outcome, warning: redemption.warning },
184+
);
172185
assert.deepEqual(
173186
yield* Queue.clear(resets),
174187
ends === "none"

‎apps/server/src/usage/ResetCredits.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ const make = Effect.gen(function* () {
7070
),
7171
);
7272
// The registry hears of the redemption's own limits read only later.
73-
return { result: { outcome }, redeemedOn: yield* instance.snapshot.getSnapshot };
73+
const result: ProviderConsumeResetCreditResult = { outcome };
74+
return { result, redeemedOn: yield* instance.snapshot.getSnapshot };
7475
});
7576

7677
// The credit is spent by now, so a failure here must not fail its redemption.
@@ -110,8 +111,11 @@ const make = Effect.gen(function* () {
110111
function* (input) {
111112
const startedAt = yield* DateTime.now;
112113
return yield* consume(input).pipe(
114+
// A warning means the hub may still refuse until its cooldown ends.
113115
Effect.tap(({ result, redeemedOn }) =>
114-
result.outcome === "reset" ? endLimitWaits(input, startedAt, redeemedOn) : Effect.void,
116+
result.outcome === "reset" && result.warning === undefined
117+
? endLimitWaits(input, startedAt, redeemedOn)
118+
: Effect.void,
115119
),
116120
Effect.map(({ result }) => result),
117121
// The credit may be spent before the caller hears of it. A caller that goes away must

0 commit comments

Comments
 (0)