From 45fae9d308e8cb9fe3304d4ca03c373ce63b2e62 Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Thu, 25 Feb 2021 22:37:25 +0800 Subject: [PATCH] fix(scheduler): ensure updates are always inserted in ascending id order (#3184) fix #2768, fix #2829 --- .../runtime-core/__tests__/scheduler.spec.ts | 32 +++++++++++++++++++ packages/runtime-core/src/scheduler.ts | 26 ++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/scheduler.spec.ts b/packages/runtime-core/__tests__/scheduler.spec.ts index 39f4de981a4..3d95be5edb3 100644 --- a/packages/runtime-core/__tests__/scheduler.spec.ts +++ b/packages/runtime-core/__tests__/scheduler.spec.ts @@ -44,6 +44,38 @@ describe('scheduler', () => { expect(calls).toEqual(['job1', 'job2']) }) + it("should insert jobs in ascending order of job's id when flushing", async () => { + const calls: string[] = [] + const job1 = () => { + calls.push('job1') + + queueJob(job2) + queueJob(job3) + queueJob(job4) + } + + const job2 = () => { + calls.push('job2') + } + job2.id = 10 + + const job3 = () => { + calls.push('job3') + } + job3.id = 1 + + // job4 gets the Infinity as it's id + const job4 = () => { + calls.push('job4') + } + + queueJob(job1) + + expect(calls).toEqual([]) + await nextTick() + expect(calls).toEqual(['job1', 'job3', 'job2', 'job4']) + }) + it('should dedupe queued jobs', async () => { const calls: string[] = [] const job1 = () => { diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index e2781f97e4d..6feb2603e76 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -57,6 +57,25 @@ export function nextTick( return fn ? p.then(this ? fn.bind(this) : fn) : p } +// #2768 +// Use binary-search to find a suitable position in the queue, +// so that the queue maintains the increasing order of job's id, +// which can prevent the job from being skipped and also can avoid repeated patching. +function findInsertionIndex(job: SchedulerJob) { + // the start index should be `flushIndex + 1` + let start = flushIndex + 1 + let end = queue.length + const jobId = getId(job) + + while (start < end) { + const middle = (start + end) >>> 1 + const middleJobId = getId(queue[middle]) + middleJobId < jobId ? (start = middle + 1) : (end = middle) + } + + return start +} + export function queueJob(job: SchedulerJob) { // the dedupe search uses the startIndex argument of Array.includes() // by default the search index includes the current job that is being run @@ -72,7 +91,12 @@ export function queueJob(job: SchedulerJob) { )) && job !== currentPreFlushParentJob ) { - queue.push(job) + const pos = findInsertionIndex(job) + if (pos > -1) { + queue.splice(pos, 0, job) + } else { + queue.push(job) + } queueFlush() } }