]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(scheduler): job ordering when the post queue is flushing (#12090)
authorskirtle <65301168+skirtles-code@users.noreply.github.com>
Thu, 3 Oct 2024 15:21:31 +0000 (16:21 +0100)
committerGitHub <noreply@github.com>
Thu, 3 Oct 2024 15:21:31 +0000 (23:21 +0800)
packages/runtime-core/__tests__/scheduler.spec.ts
packages/runtime-core/src/scheduler.ts

index cf961667e683f7c427e8eb79ce4cdab143d783dd..3e454aec02378a98ce4b8d301edea651f5b6e70a 100644 (file)
@@ -441,6 +441,29 @@ describe('scheduler', () => {
       await nextTick()
       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 () => {
@@ -758,6 +781,37 @@ describe('scheduler', () => {
     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 () => {
     const fn = vi.fn(() => {
       return 1
index 798f850b7de05edcc1032db16054d08b2b516861..b40c31d395211325bef0dcc965c1189ed8a58e24 100644 (file)
@@ -40,11 +40,8 @@ export interface SchedulerJob extends Function {
 
 export type SchedulerJobs = SchedulerJob | SchedulerJob[]
 
-let isFlushing = false
-let isFlushPending = false
-
 const queue: SchedulerJob[] = []
-let flushIndex = 0
+let flushIndex = -1
 
 const pendingPostFlushCbs: SchedulerJob[] = []
 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
 // watchers to be skipped if the component is unmounted by the parent update.
 function findInsertionIndex(id: number) {
-  let start = isFlushing ? flushIndex + 1 : 0
+  let start = flushIndex + 1
   let end = queue.length
 
   while (start < end) {
@@ -115,8 +112,7 @@ export function queueJob(job: SchedulerJob): void {
 }
 
 function queueFlush() {
-  if (!isFlushing && !isFlushPending) {
-    isFlushPending = true
+  if (!currentFlushPromise) {
     currentFlushPromise = resolvedPromise.then(flushJobs)
   }
 }
@@ -141,8 +137,8 @@ export function queuePostFlushCb(cb: SchedulerJobs): void {
 export function flushPreFlushCbs(
   instance?: ComponentInternalInstance,
   seen?: CountMap,
-  // if currently flushing, skip the current job itself
-  i: number = isFlushing ? flushIndex + 1 : 0,
+  // skip the current job
+  i: number = flushIndex + 1,
 ): void {
   if (__DEV__) {
     seen = seen || new Map()
@@ -211,8 +207,6 @@ const getId = (job: SchedulerJob): number =>
   job.id == null ? (job.flags! & SchedulerJobFlags.PRE ? -1 : Infinity) : job.id
 
 function flushJobs(seen?: CountMap) {
-  isFlushPending = false
-  isFlushing = true
   if (__DEV__) {
     seen = seen || new Map()
   }
@@ -255,15 +249,13 @@ function flushJobs(seen?: CountMap) {
       }
     }
 
-    flushIndex = 0
+    flushIndex = -1
     queue.length = 0
 
     flushPostFlushCbs(seen)
 
-    isFlushing = false
     currentFlushPromise = null
-    // some postFlushCb queued jobs!
-    // keep flushing until it drains.
+    // If new jobs have been added to either queue, keep flushing
     if (queue.length || pendingPostFlushCbs.length) {
       flushJobs(seen)
     }