diff --git a/.changeset/dry-yaks-type.md b/.changeset/dry-yaks-type.md new file mode 100644 index 0000000000..3e9cdef474 --- /dev/null +++ b/.changeset/dry-yaks-type.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-tasks': patch +--- + +When starting a task that existed before, with a faster schedule than it +previously had, the task will now correctly obey the faster schedule +immediately. Before this fix, the new schedule was only obeyed after the next +pending (according to the old schedule) run had completed. diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts index 70be2bf415..71d02274e2 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -330,6 +330,48 @@ describe('TaskWorker', () => { const before = fn1.mock.calls.length; await promise2; expect(fn1.mock.calls.length).toBeGreaterThan(before); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + 'next_run_start_at is always the min between schedule changes, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + const settings: TaskSettingsV2 = { + version: 2, + cadence: '*/15 * * * *', + initialDelayDuration: 'PT2M', + timeoutAfterDuration: 'PT1M', + }; + + const worker = new TaskWorker('task99', fn, knex, logger); + await worker.persistTask(settings); + const row1 = (await knex(DB_TASKS_TABLE))[0]; + + const settings2 = { + ...settings, + cadence: '*/2 * * * *', + initialDelayDuration: 'PT1M', + }; + await worker.persistTask(settings2); + const row2 = (await knex(DB_TASKS_TABLE))[0]; + + expect(row2.next_run_start_at).not.toStrictEqual(row1.next_run_start_at); + + const settings3 = { ...settings }; + await worker.persistTask(settings3); + const row3 = (await knex(DB_TASKS_TABLE))[0]; + + expect(row3.next_run_start_at).toStrictEqual(row2.next_run_start_at); + + await knex.destroy(); }, ); }); diff --git a/packages/backend-tasks/src/tasks/TaskWorker.ts b/packages/backend-tasks/src/tasks/TaskWorker.ts index a03d1ec058..c1007b4964 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.ts @@ -192,14 +192,36 @@ export class TaskWorker { // It's OK if the task already exists; if it does, just replace its // settings with the new value and start the loop as usual. + const settingsJson = JSON.stringify(settings); await this.knex(DB_TASKS_TABLE) .insert({ id: this.taskId, - settings_json: JSON.stringify(settings), + settings_json: settingsJson, next_run_start_at: startAt, }) .onConflict('id') - .merge(['settings_json']); + .merge( + this.knex.client.config.client.includes('mysql') + ? { + settings_json: settingsJson, + next_run_start_at: this.knex.raw( + `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`, + [startAt, 'next_run_start_at', startAt, 'next_run_start_at'], + ), + } + : { + settings_json: this.knex.ref('excluded.settings_json'), + next_run_start_at: this.knex.raw( + `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`, + [ + 'excluded.next_run_start_at', + `${DB_TASKS_TABLE}.next_run_start_at`, + 'excluded.next_run_start_at', + `${DB_TASKS_TABLE}.next_run_start_at`, + ], + ), + }, + ); } /**