]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(scheduler): ensure recursive jobs can't be queued twice (#11955)
authorskirtle <65301168+skirtles-code@users.noreply.github.com>
Fri, 20 Sep 2024 08:43:35 +0000 (09:43 +0100)
committerGitHub <noreply@github.com>
Fri, 20 Sep 2024 08:43:35 +0000 (16:43 +0800)
packages/runtime-core/__tests__/scheduler.spec.ts
packages/runtime-core/src/scheduler.ts

index 5c5b04673abad4fb858b869422868bb71a928bbf..cf961667e683f7c427e8eb79ce4cdab143d783dd 100644 (file)
@@ -517,6 +517,45 @@ describe('scheduler', () => {
     await nextTick()
   })
 
+  test('jobs can be re-queued after an error', async () => {
+    const err = new Error('test')
+    let shouldThrow = true
+
+    const job1: SchedulerJob = vi.fn(() => {
+      if (shouldThrow) {
+        shouldThrow = false
+        throw err
+      }
+    })
+    job1.id = 1
+
+    const job2: SchedulerJob = vi.fn()
+    job2.id = 2
+
+    queueJob(job1)
+    queueJob(job2)
+
+    try {
+      await nextTick()
+    } catch (e: any) {
+      expect(e).toBe(err)
+    }
+    expect(
+      `Unhandled error during execution of scheduler flush`,
+    ).toHaveBeenWarned()
+
+    expect(job1).toHaveBeenCalledTimes(1)
+    expect(job2).toHaveBeenCalledTimes(0)
+
+    queueJob(job1)
+    queueJob(job2)
+
+    await nextTick()
+
+    expect(job1).toHaveBeenCalledTimes(2)
+    expect(job2).toHaveBeenCalledTimes(1)
+  })
+
   test('should prevent self-triggering jobs by default', async () => {
     let count = 0
     const job = () => {
@@ -558,6 +597,113 @@ describe('scheduler', () => {
     expect(count).toBe(5)
   })
 
+  test('recursive jobs can only be queued once non-recursively', async () => {
+    const job: SchedulerJob = vi.fn()
+    job.id = 1
+    job.flags = SchedulerJobFlags.ALLOW_RECURSE
+
+    queueJob(job)
+    queueJob(job)
+
+    await nextTick()
+
+    expect(job).toHaveBeenCalledTimes(1)
+  })
+
+  test('recursive jobs can only be queued once recursively', async () => {
+    let recurse = true
+
+    const job: SchedulerJob = vi.fn(() => {
+      if (recurse) {
+        queueJob(job)
+        queueJob(job)
+        recurse = false
+      }
+    })
+    job.id = 1
+    job.flags = SchedulerJobFlags.ALLOW_RECURSE
+
+    queueJob(job)
+
+    await nextTick()
+
+    expect(job).toHaveBeenCalledTimes(2)
+  })
+
+  test(`recursive jobs can't be re-queued by other jobs`, async () => {
+    let recurse = true
+
+    const job1: SchedulerJob = () => {
+      if (recurse) {
+        // job2 is already queued, so this shouldn't do anything
+        queueJob(job2)
+        recurse = false
+      }
+    }
+    job1.id = 1
+
+    const job2: SchedulerJob = vi.fn(() => {
+      if (recurse) {
+        queueJob(job1)
+        queueJob(job2)
+      }
+    })
+    job2.id = 2
+    job2.flags = SchedulerJobFlags.ALLOW_RECURSE
+
+    queueJob(job2)
+
+    await nextTick()
+
+    expect(job2).toHaveBeenCalledTimes(2)
+  })
+
+  test('jobs are de-duplicated correctly when calling flushPreFlushCbs', async () => {
+    let recurse = true
+
+    const job1: SchedulerJob = vi.fn(() => {
+      queueJob(job3)
+      queueJob(job3)
+      flushPreFlushCbs()
+    })
+    job1.id = 1
+    job1.flags = SchedulerJobFlags.PRE
+
+    const job2: SchedulerJob = vi.fn(() => {
+      if (recurse) {
+        // job2 does not allow recurse, so this shouldn't do anything
+        queueJob(job2)
+
+        // job3 is already queued, so this shouldn't do anything
+        queueJob(job3)
+        recurse = false
+      }
+    })
+    job2.id = 2
+    job2.flags = SchedulerJobFlags.PRE
+
+    const job3: SchedulerJob = vi.fn(() => {
+      if (recurse) {
+        queueJob(job2)
+        queueJob(job3)
+
+        // The jobs are already queued, so these should have no effect
+        queueJob(job2)
+        queueJob(job3)
+      }
+    })
+    job3.id = 3
+    job3.flags = SchedulerJobFlags.ALLOW_RECURSE | SchedulerJobFlags.PRE
+
+    queueJob(job1)
+
+    await nextTick()
+
+    expect(job1).toHaveBeenCalledTimes(1)
+    expect(job2).toHaveBeenCalledTimes(1)
+    expect(job3).toHaveBeenCalledTimes(2)
+  })
+
   // #1947 flushPostFlushCbs should handle nested calls
   // e.g. app.mount inside app.mount
   test('flushPostFlushCbs', async () => {
index 1eac06e5bf01385787193a79f28cf7589889c10b..798f850b7de05edcc1032db16054d08b2b516861 100644 (file)
@@ -162,7 +162,9 @@ export function flushPreFlushCbs(
         cb.flags! &= ~SchedulerJobFlags.QUEUED
       }
       cb()
-      cb.flags! &= ~SchedulerJobFlags.QUEUED
+      if (!(cb.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
+        cb.flags! &= ~SchedulerJobFlags.QUEUED
+      }
     }
   }
 }
@@ -239,7 +241,9 @@ function flushJobs(seen?: CountMap) {
           job.i,
           job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
         )
-        job.flags! &= ~SchedulerJobFlags.QUEUED
+        if (!(job.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
+          job.flags! &= ~SchedulerJobFlags.QUEUED
+        }
       }
     }
   } finally {