fix(scheduler): job ordering when the post queue is flushing (#12090)

This commit is contained in:
skirtle 2024-10-03 16:21:31 +01:00 committed by GitHub
parent 3a55c3e421
commit 577edca8e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 61 additions and 15 deletions

View File

@ -441,6 +441,29 @@ describe('scheduler', () => {
await nextTick() await nextTick()
expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2']) expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2'])
}) })
test('jobs added during post flush are ordered correctly', async () => {
const calls: string[] = []
const job1: SchedulerJob = () => {
calls.push('job1')
}
job1.id = 1
const job2: SchedulerJob = () => {
calls.push('job2')
}
job2.id = 2
queuePostFlushCb(() => {
queueJob(job2)
queueJob(job1)
})
await nextTick()
expect(calls).toEqual(['job1', 'job2'])
})
}) })
test('sort job based on id', async () => { test('sort job based on id', async () => {
@ -758,6 +781,37 @@ describe('scheduler', () => {
expect(spy).toHaveBeenCalledTimes(1) expect(spy).toHaveBeenCalledTimes(1)
}) })
test('flushPreFlushCbs inside a post job', async () => {
const calls: string[] = []
const callsAfterFlush: string[] = []
const job1: SchedulerJob = () => {
calls.push('job1')
}
job1.id = 1
job1.flags! |= SchedulerJobFlags.PRE
const job2: SchedulerJob = () => {
calls.push('job2')
}
job2.id = 2
job2.flags! |= SchedulerJobFlags.PRE
queuePostFlushCb(() => {
queueJob(job2)
queueJob(job1)
// e.g. nested app.mount() call
flushPreFlushCbs()
callsAfterFlush.push(...calls)
})
await nextTick()
expect(callsAfterFlush).toEqual(['job1', 'job2'])
expect(calls).toEqual(['job1', 'job2'])
})
it('nextTick should return promise', async () => { it('nextTick should return promise', async () => {
const fn = vi.fn(() => { const fn = vi.fn(() => {
return 1 return 1

View File

@ -40,11 +40,8 @@ export interface SchedulerJob extends Function {
export type SchedulerJobs = SchedulerJob | SchedulerJob[] export type SchedulerJobs = SchedulerJob | SchedulerJob[]
let isFlushing = false
let isFlushPending = false
const queue: SchedulerJob[] = [] const queue: SchedulerJob[] = []
let flushIndex = 0 let flushIndex = -1
const pendingPostFlushCbs: SchedulerJob[] = [] const pendingPostFlushCbs: SchedulerJob[] = []
let activePostFlushCbs: SchedulerJob[] | null = null let activePostFlushCbs: SchedulerJob[] | null = null
@ -74,7 +71,7 @@ export function nextTick<T = void, R = void>(
// watcher should be inserted immediately before the update job. This allows // watcher should be inserted immediately before the update job. This allows
// watchers to be skipped if the component is unmounted by the parent update. // watchers to be skipped if the component is unmounted by the parent update.
function findInsertionIndex(id: number) { function findInsertionIndex(id: number) {
let start = isFlushing ? flushIndex + 1 : 0 let start = flushIndex + 1
let end = queue.length let end = queue.length
while (start < end) { while (start < end) {
@ -115,8 +112,7 @@ export function queueJob(job: SchedulerJob): void {
} }
function queueFlush() { function queueFlush() {
if (!isFlushing && !isFlushPending) { if (!currentFlushPromise) {
isFlushPending = true
currentFlushPromise = resolvedPromise.then(flushJobs) currentFlushPromise = resolvedPromise.then(flushJobs)
} }
} }
@ -141,8 +137,8 @@ export function queuePostFlushCb(cb: SchedulerJobs): void {
export function flushPreFlushCbs( export function flushPreFlushCbs(
instance?: ComponentInternalInstance, instance?: ComponentInternalInstance,
seen?: CountMap, seen?: CountMap,
// if currently flushing, skip the current job itself // skip the current job
i: number = isFlushing ? flushIndex + 1 : 0, i: number = flushIndex + 1,
): void { ): void {
if (__DEV__) { if (__DEV__) {
seen = seen || new Map() seen = seen || new Map()
@ -211,8 +207,6 @@ const getId = (job: SchedulerJob): number =>
job.id == null ? (job.flags! & SchedulerJobFlags.PRE ? -1 : Infinity) : job.id job.id == null ? (job.flags! & SchedulerJobFlags.PRE ? -1 : Infinity) : job.id
function flushJobs(seen?: CountMap) { function flushJobs(seen?: CountMap) {
isFlushPending = false
isFlushing = true
if (__DEV__) { if (__DEV__) {
seen = seen || new Map() seen = seen || new Map()
} }
@ -255,15 +249,13 @@ function flushJobs(seen?: CountMap) {
} }
} }
flushIndex = 0 flushIndex = -1
queue.length = 0 queue.length = 0
flushPostFlushCbs(seen) flushPostFlushCbs(seen)
isFlushing = false
currentFlushPromise = null currentFlushPromise = null
// some postFlushCb queued jobs! // If new jobs have been added to either queue, keep flushing
// keep flushing until it drains.
if (queue.length || pendingPostFlushCbs.length) { if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen) flushJobs(seen)
} }