mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-22 10:02:41 +00:00
fix: release failed run-now claims
This commit is contained in:
parent
41ca15c579
commit
d107ded8cd
2 changed files with 57 additions and 2 deletions
|
|
@ -85,6 +85,54 @@ describe('shutdown wiring', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('manual Run Now lease cleanup', () => {
|
||||
it('releases the old holder when an owner edit fences cleanup after a preflight throw', async () => {
|
||||
const service = makeService(jest.fn(async (_userId: string) => []));
|
||||
const leased = {
|
||||
id: 's1',
|
||||
user: 'user-1',
|
||||
tenantId: 't1',
|
||||
name: 'Digest',
|
||||
prompt: 'Summarize',
|
||||
agent_id: 'agent-1',
|
||||
cadence: { frequency: 'daily', hour: 8, minute: 0 },
|
||||
timezone: 'America/New_York',
|
||||
target: 'new',
|
||||
enabled: true,
|
||||
claimToken: 'manual-token',
|
||||
leaseBy: 'manual-holder',
|
||||
runCount: 0,
|
||||
failureCount: 0,
|
||||
balanceSkipCount: 0,
|
||||
} as never;
|
||||
const methods = service.engineDeps.methods as unknown as {
|
||||
acquireManualRunLease: jest.Mock;
|
||||
releaseLease: jest.Mock;
|
||||
releaseLeaseByHolder: jest.Mock;
|
||||
};
|
||||
methods.acquireManualRunLease = jest.fn(async () => leased);
|
||||
// Simulates an edit rotating claimToken while deliberately preserving leaseBy.
|
||||
methods.releaseLease = jest.fn(async () => false);
|
||||
methods.releaseLeaseByHolder = jest.fn(async () => undefined);
|
||||
service.engineDeps.getUserContext = jest.fn(async () => {
|
||||
throw new Error('user lookup failed');
|
||||
});
|
||||
|
||||
await expect(
|
||||
service.fireScheduleNow(leased, {
|
||||
enabled: true,
|
||||
maxPerUser: 10,
|
||||
minIntervalMinutes: 60,
|
||||
autoDisableAfterFailures: 5,
|
||||
fireConcurrency: 5,
|
||||
}),
|
||||
).rejects.toThrow('user lookup failed');
|
||||
|
||||
expect(methods.releaseLease).toHaveBeenCalledWith('s1', 'manual-token');
|
||||
expect(methods.releaseLeaseByHolder).toHaveBeenCalledWith('s1', 'manual-holder');
|
||||
});
|
||||
});
|
||||
|
||||
describe('balance initialization', () => {
|
||||
const balanceConfig = {
|
||||
interfaceConfig: {},
|
||||
|
|
|
|||
|
|
@ -652,8 +652,15 @@ export function createSchedulesService(
|
|||
// window in between is reflected, so a stale prompt/agent is never dispatched.
|
||||
return await fireSchedule(engineDeps, leased, limits, new Date(), { manual: true });
|
||||
} catch (err) {
|
||||
if (claimToken != null) {
|
||||
await methods.releaseLease(schedule.id, claimToken).catch(() => undefined);
|
||||
const released =
|
||||
claimToken != null
|
||||
? await methods.releaseLease(schedule.id, claimToken).catch(() => false)
|
||||
: false;
|
||||
if (!released && leased.leaseBy != null) {
|
||||
// An owner edit during the failed preflight rotates the token while preserving
|
||||
// this unique holder. Clear only that holder; a takeover has a different value
|
||||
// and is therefore never stripped by this exception safety net.
|
||||
await methods.releaseLeaseByHolder(schedule.id, leased.leaseBy).catch(() => undefined);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue