fix: ensure scheduling by default only handles default queue, add allQueues config to autoRun (#13395)
By default, `payload.jobs.run` only runs jobs from the `default` queue (since https://github.com/payloadcms/payload/pull/12799). It exposes an `allQueues` property to run jobs from all queues. For handling schedules (`payload.jobs.handleSchedules` and `config.jobs.autoRun`), this behaves differently - jobs are run from all queues by default, and no `allQueues` property exists. This PR adds an `allQueues` property to scheduling, as well as changes the default behavior to only handle schedules for the `default` queue. That way, the behavior of running and scheduling jobs matches. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210982048221260
This commit is contained in:
@@ -14,6 +14,7 @@ export const baseIDField: TextField = {
|
|||||||
defaultValue: () => new ObjectId().toHexString(),
|
defaultValue: () => new ObjectId().toHexString(),
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeChange: [({ value }) => value || new ObjectId().toHexString()],
|
beforeChange: [({ value }) => value || new ObjectId().toHexString()],
|
||||||
|
// ID field values for arrays and blocks need to be unique when duplicating, as on postgres they are stored on the same table as primary keys.
|
||||||
beforeDuplicate: [() => new ObjectId().toHexString()],
|
beforeDuplicate: [() => new ObjectId().toHexString()],
|
||||||
},
|
},
|
||||||
label: 'ID',
|
label: 'ID',
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ export const promise = async <T>({
|
|||||||
let fieldData = siblingDoc?.[field.name!]
|
let fieldData = siblingDoc?.[field.name!]
|
||||||
const fieldIsLocalized = localization && fieldShouldBeLocalized({ field, parentIsLocalized })
|
const fieldIsLocalized = localization && fieldShouldBeLocalized({ field, parentIsLocalized })
|
||||||
|
|
||||||
// Run field beforeDuplicate hooks
|
// Run field beforeDuplicate hooks.
|
||||||
|
// These hooks are responsible for resetting the `id` field values of array and block rows. See `baseIDField`.
|
||||||
if (Array.isArray(field.hooks?.beforeDuplicate)) {
|
if (Array.isArray(field.hooks?.beforeDuplicate)) {
|
||||||
if (fieldIsLocalized) {
|
if (fieldIsLocalized) {
|
||||||
const localeData: JsonObject = {}
|
const localeData: JsonObject = {}
|
||||||
|
|||||||
@@ -873,6 +873,7 @@ export class BasePayload {
|
|||||||
this.config.jobs.scheduling
|
this.config.jobs.scheduling
|
||||||
) {
|
) {
|
||||||
await this.jobs.handleSchedules({
|
await this.jobs.handleSchedules({
|
||||||
|
allQueues: cronConfig.allQueues,
|
||||||
queue: cronConfig.queue,
|
queue: cronConfig.queue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -891,6 +892,7 @@ export class BasePayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.jobs.run({
|
await this.jobs.run({
|
||||||
|
allQueues: cronConfig.allQueues,
|
||||||
limit: cronConfig.limit ?? DEFAULT_LIMIT,
|
limit: cronConfig.limit ?? DEFAULT_LIMIT,
|
||||||
queue: cronConfig.queue,
|
queue: cronConfig.queue,
|
||||||
silent: cronConfig.silent,
|
silent: cronConfig.silent,
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ import type { TaskConfig } from './taskTypes.js'
|
|||||||
import type { WorkflowConfig } from './workflowTypes.js'
|
import type { WorkflowConfig } from './workflowTypes.js'
|
||||||
|
|
||||||
export type AutorunCronConfig = {
|
export type AutorunCronConfig = {
|
||||||
|
/**
|
||||||
|
* If you want to autoRUn jobs from all queues, set this to true.
|
||||||
|
* If you set this to true, the `queue` property will be ignored.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
allQueues?: boolean
|
||||||
/**
|
/**
|
||||||
* The cron schedule for the job.
|
* The cron schedule for the job.
|
||||||
* @default '* * * * *' (every minute).
|
* @default '* * * * *' (every minute).
|
||||||
@@ -43,6 +50,8 @@ export type AutorunCronConfig = {
|
|||||||
limit?: number
|
limit?: number
|
||||||
/**
|
/**
|
||||||
* The queue name for the job.
|
* The queue name for the job.
|
||||||
|
*
|
||||||
|
* @default 'default'
|
||||||
*/
|
*/
|
||||||
queue?: string
|
queue?: string
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -45,11 +45,18 @@ export const handleSchedulesJobsEndpoint: Endpoint = {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { queue } = req.query as {
|
const { allQueues, queue } = req.query as {
|
||||||
|
allQueues?: 'false' | 'true'
|
||||||
queue?: string
|
queue?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const { errored, queued, skipped } = await handleSchedules({ queue, req })
|
const runAllQueues = allQueues && !(typeof allQueues === 'string' && allQueues === 'false')
|
||||||
|
|
||||||
|
const { errored, queued, skipped } = await handleSchedules({
|
||||||
|
allQueues: runAllQueues,
|
||||||
|
queue,
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const runJobsEndpoint: Endpoint = {
|
|||||||
|
|
||||||
if (shouldHandleSchedules && jobsConfig.scheduling) {
|
if (shouldHandleSchedules && jobsConfig.scheduling) {
|
||||||
// If should handle schedules and schedules are defined
|
// If should handle schedules and schedules are defined
|
||||||
await req.payload.jobs.handleSchedules({ queue: runAllQueues ? undefined : queue, req })
|
await req.payload.jobs.handleSchedules({ allQueues: runAllQueues, queue, req })
|
||||||
}
|
}
|
||||||
|
|
||||||
const runJobsArgs: RunJobsArgs = {
|
const runJobsArgs: RunJobsArgs = {
|
||||||
|
|||||||
@@ -22,13 +22,20 @@ export type RunJobsSilent =
|
|||||||
| boolean
|
| boolean
|
||||||
export const getJobsLocalAPI = (payload: Payload) => ({
|
export const getJobsLocalAPI = (payload: Payload) => ({
|
||||||
handleSchedules: async (args?: {
|
handleSchedules: async (args?: {
|
||||||
|
/**
|
||||||
|
* If you want to schedule jobs from all queues, set this to true.
|
||||||
|
* If you set this to true, the `queue` property will be ignored.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
allQueues?: boolean
|
||||||
// By default, schedule all queues - only scheduling jobs scheduled to be added to the `default` queue would not make sense
|
// By default, schedule all queues - only scheduling jobs scheduled to be added to the `default` queue would not make sense
|
||||||
// here, as you'd usually specify a different queue than `default` here, especially if this is used in combination with autorun.
|
// here, as you'd usually specify a different queue than `default` here, especially if this is used in combination with autorun.
|
||||||
// The `queue` property for setting up schedules is required, and not optional.
|
// The `queue` property for setting up schedules is required, and not optional.
|
||||||
/**
|
/**
|
||||||
* If you want to only schedule jobs that are set to schedule in a specific queue, set this to the queue name.
|
* If you want to only schedule jobs that are set to schedule in a specific queue, set this to the queue name.
|
||||||
*
|
*
|
||||||
* @default all jobs for all queues will be scheduled.
|
* @default jobs from the `default` queue will be executed.
|
||||||
*/
|
*/
|
||||||
queue?: string
|
queue?: string
|
||||||
req?: PayloadRequest
|
req?: PayloadRequest
|
||||||
@@ -36,6 +43,7 @@ export const getJobsLocalAPI = (payload: Payload) => ({
|
|||||||
const newReq: PayloadRequest = args?.req ?? (await createLocalReq({}, payload))
|
const newReq: PayloadRequest = args?.req ?? (await createLocalReq({}, payload))
|
||||||
|
|
||||||
return await handleSchedules({
|
return await handleSchedules({
|
||||||
|
allQueues: args?.allQueues,
|
||||||
queue: args?.queue,
|
queue: args?.queue,
|
||||||
req: newReq,
|
req: newReq,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,17 +23,26 @@ export type HandleSchedulesResult = {
|
|||||||
* after they are scheduled
|
* after they are scheduled
|
||||||
*/
|
*/
|
||||||
export async function handleSchedules({
|
export async function handleSchedules({
|
||||||
queue,
|
allQueues = false,
|
||||||
|
queue: _queue,
|
||||||
req,
|
req,
|
||||||
}: {
|
}: {
|
||||||
|
/**
|
||||||
|
* If you want to schedule jobs from all queues, set this to true.
|
||||||
|
* If you set this to true, the `queue` property will be ignored.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
allQueues?: boolean
|
||||||
/**
|
/**
|
||||||
* If you want to only schedule jobs that are set to schedule in a specific queue, set this to the queue name.
|
* If you want to only schedule jobs that are set to schedule in a specific queue, set this to the queue name.
|
||||||
*
|
*
|
||||||
* @default all jobs for all queues will be scheduled.
|
* @default jobs from the `default` queue will be executed.
|
||||||
*/
|
*/
|
||||||
queue?: string
|
queue?: string
|
||||||
req: PayloadRequest
|
req: PayloadRequest
|
||||||
}): Promise<HandleSchedulesResult> {
|
}): Promise<HandleSchedulesResult> {
|
||||||
|
const queue = _queue ?? 'default'
|
||||||
const jobsConfig = req.payload.config.jobs
|
const jobsConfig = req.payload.config.jobs
|
||||||
const queuesWithSchedules = getQueuesWithSchedules({
|
const queuesWithSchedules = getQueuesWithSchedules({
|
||||||
jobsConfig,
|
jobsConfig,
|
||||||
@@ -53,7 +62,7 @@ export async function handleSchedules({
|
|||||||
// Need to know when that particular job was last scheduled in that particular queue
|
// Need to know when that particular job was last scheduled in that particular queue
|
||||||
|
|
||||||
for (const [queueName, { schedules }] of Object.entries(queuesWithSchedules)) {
|
for (const [queueName, { schedules }] of Object.entries(queuesWithSchedules)) {
|
||||||
if (queue && queueName !== queue) {
|
if (!allQueues && queueName !== queue) {
|
||||||
// If a queue is specified, only schedule jobs for that queue
|
// If a queue is specified, only schedule jobs for that queue
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
|||||||
|
|
||||||
it('can auto-schedule through local API and autorun jobs', async () => {
|
it('can auto-schedule through local API and autorun jobs', async () => {
|
||||||
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||||
await payload.jobs.handleSchedules()
|
await payload.jobs.handleSchedules({ queue: 'autorunSecond' })
|
||||||
|
|
||||||
// Do not call payload.jobs.run{silent: true})
|
// Do not call payload.jobs.run{silent: true})
|
||||||
|
|
||||||
@@ -88,9 +88,50 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
|||||||
expect(allSimples?.docs?.[0]?.title).toBe('This task runs every second')
|
expect(allSimples?.docs?.[0]?.title).toBe('This task runs every second')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('can auto-schedule through local API and autorun jobs when passing allQueues', async () => {
|
||||||
|
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||||
|
await payload.jobs.handleSchedules({ queue: 'autorunSecond', allQueues: true })
|
||||||
|
|
||||||
|
// Do not call payload.jobs.run{silent: true})
|
||||||
|
|
||||||
|
await waitUntilAutorunIsDone({
|
||||||
|
payload,
|
||||||
|
queue: 'autorunSecond',
|
||||||
|
onlyScheduled: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const allSimples = await payload.find({
|
||||||
|
collection: 'simple',
|
||||||
|
limit: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(allSimples.totalDocs).toBe(1)
|
||||||
|
expect(allSimples?.docs?.[0]?.title).toBe('This task runs every second')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not auto-schedule through local API and autorun jobs when not passing queue and schedule is not set on the default queue', async () => {
|
||||||
|
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||||
|
await payload.jobs.handleSchedules()
|
||||||
|
|
||||||
|
// Do not call payload.jobs.run{silent: true})
|
||||||
|
|
||||||
|
await waitUntilAutorunIsDone({
|
||||||
|
payload,
|
||||||
|
queue: 'autorunSecond',
|
||||||
|
onlyScheduled: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const allSimples = await payload.find({
|
||||||
|
collection: 'simple',
|
||||||
|
limit: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(allSimples.totalDocs).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
it('can auto-schedule through handleSchedules REST API and autorun jobs', async () => {
|
it('can auto-schedule through handleSchedules REST API and autorun jobs', async () => {
|
||||||
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||||
await restClient.GET('/payload-jobs/handle-schedules', {
|
await restClient.GET('/payload-jobs/handle-schedules?queue=autorunSecond', {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `JWT ${token}`,
|
Authorization: `JWT ${token}`,
|
||||||
},
|
},
|
||||||
@@ -115,7 +156,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
|||||||
|
|
||||||
it('can auto-schedule through run REST API and autorun jobs', async () => {
|
it('can auto-schedule through run REST API and autorun jobs', async () => {
|
||||||
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||||
await restClient.GET('/payload-jobs/run?silent=true', {
|
await restClient.GET('/payload-jobs/run?silent=true&allQueues=true', {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `JWT ${token}`,
|
Authorization: `JWT ${token}`,
|
||||||
},
|
},
|
||||||
@@ -161,7 +202,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
|||||||
it('ensure scheduler does not schedule more jobs than needed if executed sequentially', async () => {
|
it('ensure scheduler does not schedule more jobs than needed if executed sequentially', async () => {
|
||||||
await withoutAutoRun(async () => {
|
await withoutAutoRun(async () => {
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
await payload.jobs.handleSchedules()
|
await payload.jobs.handleSchedules({ allQueues: true })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -192,7 +233,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
await payload.jobs.handleSchedules()
|
await payload.jobs.handleSchedules({ allQueues: true })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -271,8 +312,8 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
|||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
await withoutAutoRun(async () => {
|
await withoutAutoRun(async () => {
|
||||||
// Call it twice to test that it only schedules one
|
// Call it twice to test that it only schedules one
|
||||||
await payload.jobs.handleSchedules()
|
await payload.jobs.handleSchedules({ allQueues: true })
|
||||||
await payload.jobs.handleSchedules()
|
await payload.jobs.handleSchedules({ allQueues: true })
|
||||||
})
|
})
|
||||||
// Advance time to satisfy the waitUntil of newly scheduled jobs
|
// Advance time to satisfy the waitUntil of newly scheduled jobs
|
||||||
timeTravel(20)
|
timeTravel(20)
|
||||||
|
|||||||
Reference in New Issue
Block a user