diff --git a/.changeset/poor-peaches-roll.md b/.changeset/poor-peaches-roll.md new file mode 100644 index 00000000000..de581340ede --- /dev/null +++ b/.changeset/poor-peaches-roll.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': patch +--- + +fix: correct running chores handling edge case diff --git a/packages/qwik/src/core/shared/scheduler-journal-block.unit.tsx b/packages/qwik/src/core/shared/scheduler-journal-block.unit.tsx index bfaefb80e3f..63e9e99c0e5 100644 --- a/packages/qwik/src/core/shared/scheduler-journal-block.unit.tsx +++ b/packages/qwik/src/core/shared/scheduler-journal-block.unit.tsx @@ -30,7 +30,7 @@ describe('should block journal flush during node-diff and component runs', () => let vA: ElementVNode = null!; let vAHost: VirtualVNode = null!; let choreQueue: ChoreArray; - let blockedChores: Set; + let blockedChores: ChoreArray; let runningChores: Set; async function waitForDrain() { @@ -44,7 +44,7 @@ describe('should block journal flush during node-diff and component runs', () => const container = getDomContainer(document.body); container.handleError = vi.fn(); choreQueue = new ChoreArray(); - blockedChores = new Set(); + blockedChores = new ChoreArray(); runningChores = new Set(); scheduler = createScheduler( container, diff --git a/packages/qwik/src/core/shared/scheduler-rules.ts b/packages/qwik/src/core/shared/scheduler-rules.ts index c2b83b22c18..96605399e8a 100644 --- a/packages/qwik/src/core/shared/scheduler-rules.ts +++ b/packages/qwik/src/core/shared/scheduler-rules.ts @@ -5,7 +5,7 @@ import { } from '../client/vnode'; import { Task, TaskFlags } from '../use/use-task'; import type { QRLInternal } from './qrl/qrl-class'; -import type { Chore } from './scheduler'; +import { ChoreState, type Chore } from './scheduler'; import type { Container, HostElement } from './types'; import { ChoreType } from './util-chore-type'; import { ELEMENT_SEQ } from './utils/markers'; @@ -147,7 +147,8 @@ function findAncestorBlockingChore(chore: Chore, type: ChoreSetType): Chore | nu blockingChore.$type$ < ChoreType.VISIBLE && blockingChore.$type$ !== ChoreType.TASK && blockingChore.$type$ !== ChoreType.QRL_RESOLVE && - blockingChore.$type$ !== ChoreType.RUN_QRL + blockingChore.$type$ !== ChoreType.RUN_QRL && + blockingChore.$state$ === ChoreState.NONE ) { return blockingChore; } @@ -161,7 +162,7 @@ function findAncestorBlockingChore(chore: Chore, type: ChoreSetType): Chore | nu export function findBlockingChore( chore: Chore, choreQueue: ChoreArray, - blockedChores: Set, + blockedChores: ChoreArray, runningChores: Set, container: Container ): Chore | null { @@ -185,20 +186,32 @@ export function findBlockingChore( // Check in choreQueue // TODO(perf): better to iterate in reverse order? for (const candidate of choreQueue) { - if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) { + if ( + candidate.$type$ === rule.blockingType && + rule.match(chore, candidate, container) && + candidate.$state$ === ChoreState.NONE + ) { return candidate; } } // Check in blockedChores for (const candidate of blockedChores) { - if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) { + if ( + candidate.$type$ === rule.blockingType && + rule.match(chore, candidate, container) && + candidate.$state$ === ChoreState.NONE + ) { return candidate; } } // Check in runningChores for (const candidate of runningChores) { - if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) { + if ( + candidate.$type$ === rule.blockingType && + rule.match(chore, candidate, container) && + candidate.$state$ !== ChoreState.FAILED + ) { return candidate; } } @@ -236,7 +249,11 @@ export function findBlockingChoreForVisible( } for (const candidate of runningChores) { - if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) { + if ( + candidate.$type$ === rule.blockingType && + rule.match(chore, candidate, container) && + candidate.$state$ !== ChoreState.FAILED + ) { return candidate; } } diff --git a/packages/qwik/src/core/shared/scheduler-rules.unit.tsx b/packages/qwik/src/core/shared/scheduler-rules.unit.tsx index 26eabd69489..2daafedd0f8 100644 --- a/packages/qwik/src/core/shared/scheduler-rules.unit.tsx +++ b/packages/qwik/src/core/shared/scheduler-rules.unit.tsx @@ -82,7 +82,7 @@ describe('findBlockingChore', () => { (blockedType) => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); let newChore; if (blockedType === ChoreType.VISIBLE || blockedType === ChoreType.TASK) { @@ -113,7 +113,7 @@ describe('findBlockingChore', () => { it('should NOT block on a different host with the same qrl', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.RUN_QRL, host2, 0, null, createMockQRL('qrl1')); @@ -130,7 +130,7 @@ describe('findBlockingChore', () => { it('should NOT block on a different host with a different qrl', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.RUN_QRL, host2, 0, null, createMockQRL('qrl2')); const result = findBlockingChore( @@ -146,7 +146,7 @@ describe('findBlockingChore', () => { it('should NOT block on a the same host with a different qrl', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.RUN_QRL, host1, 0, null, createMockQRL('qrl2')); const result = findBlockingChore( @@ -162,7 +162,7 @@ describe('findBlockingChore', () => { it('should find blocking chore in blockedChores set with the same qrl', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.RUN_QRL, host1, 0, null, createMockQRL('qrl1')); @@ -179,7 +179,7 @@ describe('findBlockingChore', () => { it('should find blocking chore in runningChores set with the same qrl', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.RUN_QRL, host1, 0, null, createMockQRL('qrl1')); @@ -196,7 +196,7 @@ describe('findBlockingChore', () => { it('should block VISIBLE on the same host with the same qrl', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore( ChoreType.VISIBLE, @@ -231,7 +231,7 @@ describe('findBlockingChore', () => { const blockingChore = createMockChore(blockingType, parentVNode); const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.VISIBLE, childVNode); @@ -252,7 +252,7 @@ describe('findBlockingChore', () => { const blockingChore = createMockChore(blockingType, childVNode); const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.VISIBLE, parentVNode); @@ -273,7 +273,7 @@ describe('findBlockingChore', () => { const blockingChore = createMockChore(blockingType, siblingVNode); const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.VISIBLE, childVNode); @@ -295,7 +295,7 @@ describe('findBlockingChore', () => { const blockingChore = createMockChore(blockingType, otherVNode); const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.VISIBLE, parentVNode); @@ -315,7 +315,7 @@ describe('findBlockingChore', () => { const blockingChore = createMockChore(ChoreType.COMPONENT, nonVNodeHost as any); const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.VISIBLE, parentVNode); @@ -339,7 +339,7 @@ describe('findBlockingChore', () => { (blockedType) => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(blockedType, host1); @@ -357,7 +357,7 @@ describe('findBlockingChore', () => { it('should NOT block on a different host', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.NODE_DIFF, host2); @@ -374,7 +374,7 @@ describe('findBlockingChore', () => { it('should find blocking chore in blockedChores set', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.NODE_DIFF, host1); @@ -391,7 +391,7 @@ describe('findBlockingChore', () => { it('should find blocking chore in runningChores set', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.NODE_DIFF, host1); @@ -425,7 +425,7 @@ describe('findBlockingChore', () => { it('should block a subsequent TASK on the same host', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.TASK, host1, 2, task2); @@ -441,7 +441,7 @@ describe('findBlockingChore', () => { it('should NOT block a preceding TASK on the same host', () => { const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); choreQueue.add(createMockChore(ChoreType.TASK, host1, 2, task2)); @@ -460,7 +460,7 @@ describe('findBlockingChore', () => { it('should NOT block a TASK on a different host', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.TASK, host2, 0, task3_otherHost); @@ -476,7 +476,8 @@ describe('findBlockingChore', () => { it('should find blocking TASK in blockedChores set', () => { const choreQueue = new ChoreArray(); - const blockedChores = new Set([blockingChore]); + const blockedChores = new ChoreArray(); + blockedChores.add(blockingChore); const runningChores = new Set(); const newChore = createMockChore(ChoreType.TASK, host1, 2, task2); @@ -492,7 +493,7 @@ describe('findBlockingChore', () => { it('should find blocking TASK in runningChores set', () => { const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set([blockingChore]); const newChore = createMockChore(ChoreType.TASK, host1, 2, task2); @@ -509,7 +510,7 @@ describe('findBlockingChore', () => { it('should not block if it is the first task (index 0)', () => { const choreQueue = new ChoreArray(); choreQueue.add(blockingChoreOtherHost); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const newChore = createMockChore(ChoreType.TASK, host1, 0, task1); @@ -528,7 +529,7 @@ describe('findBlockingChore', () => { it('should return null if no blocking chore is found', () => { const container = createMockContainer(new Map()); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); choreQueue.add(createMockChore(ChoreType.COMPONENT, host1)); const newChore = createMockChore(ChoreType.RUN_QRL, host1); // Not blocked by COMPONENT @@ -552,7 +553,7 @@ describe('findBlockingChore', () => { it('should block if an ancestor is in the choreQueue', () => { const choreQueue = new ChoreArray(); choreQueue.add(ancestorChore); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -566,7 +567,8 @@ describe('findBlockingChore', () => { it('should block if an ancestor is in the blockedChores', () => { const choreQueue = new ChoreArray(); - const blockedChores = new Set([ancestorChore]); + const blockedChores = new ChoreArray(); + blockedChores.add(ancestorChore); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -580,7 +582,7 @@ describe('findBlockingChore', () => { it('should block if an ancestor is in the runningChores', () => { const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set([ancestorChore]); const result = findBlockingChore( descendantChore, @@ -594,7 +596,7 @@ describe('findBlockingChore', () => { it('should not block if candidate is a descendant, not ancestor', () => { const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); choreQueue.add(descendantChore); const result = findBlockingChore( @@ -610,7 +612,7 @@ describe('findBlockingChore', () => { it('should not block for unrelated chores', () => { const unrelatedChore = createMockChore(ChoreType.NODE_DIFF, unrelatedVNode); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); choreQueue.add(unrelatedChore); const result = findBlockingChore( @@ -626,7 +628,7 @@ describe('findBlockingChore', () => { it('should not block if candidate chore type is VISIBLE or greater', () => { const ancestorVisibleChore = createMockChore(ChoreType.VISIBLE, parentVNode); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); choreQueue.add(ancestorVisibleChore); const result = findBlockingChore( @@ -644,7 +646,7 @@ describe('findBlockingChore', () => { (ancestorTaskChore.$host$ as VNode).chores = new ChoreArray(); (ancestorTaskChore.$host$ as VNode).chores?.add(ancestorTaskChore); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -661,7 +663,7 @@ describe('findBlockingChore', () => { (ancestorQrlResolveChore.$host$ as VNode).chores = new ChoreArray(); (ancestorQrlResolveChore.$host$ as VNode).chores?.add(ancestorQrlResolveChore); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -678,7 +680,7 @@ describe('findBlockingChore', () => { (ancestorRunQrlChore.$host$ as VNode).chores = new ChoreArray(); (ancestorRunQrlChore.$host$ as VNode).chores?.add(ancestorRunQrlChore); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -695,7 +697,7 @@ describe('findBlockingChore', () => { (ancestorNodeDiffChore.$host$ as VNode).chores = new ChoreArray(); (ancestorNodeDiffChore.$host$ as VNode).chores?.add(ancestorNodeDiffChore); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -712,7 +714,7 @@ describe('findBlockingChore', () => { (ancestorNodePropChore.$host$ as VNode).chores = new ChoreArray(); (ancestorNodePropChore.$host$ as VNode).chores?.add(ancestorNodePropChore); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -729,7 +731,7 @@ describe('findBlockingChore', () => { (ancestorComponentChore.$host$ as VNode).chores = new ChoreArray(); (ancestorComponentChore.$host$ as VNode).chores?.add(ancestorComponentChore); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -749,7 +751,7 @@ describe('findBlockingChore', () => { (ancestorRecomputeChore.$host$ as VNode).chores = new ChoreArray(); (ancestorRecomputeChore.$host$ as VNode).chores?.add(ancestorRecomputeChore); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( descendantChore, @@ -772,7 +774,7 @@ describe('findBlockingChore', () => { (ancestorProjectionChore.$host$ as VNode).chores?.add(ancestorProjectionChore); const descendantProjectionChore = createMockChore(ChoreType.COMPONENT, childProjectionVNode); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); choreQueue.add(ancestorProjectionChore); const result = findBlockingChore( @@ -802,7 +804,7 @@ describe('findBlockingChore', () => { const descendantChore = createMockChore(ChoreType.VISIBLE, childVNode); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( @@ -833,7 +835,7 @@ describe('findBlockingChore', () => { const descendantChore = createMockChore(ChoreType.VISIBLE, childVNode); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( @@ -862,7 +864,7 @@ describe('findBlockingChore', () => { const descendantChore = createMockChore(ChoreType.VISIBLE, childVNode); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); const result = findBlockingChore( @@ -1142,24 +1144,24 @@ describe('addBlockedChore', () => { it('should add blocked chore to blocking chore and blockedChores set', () => { const blockedChore = createMockChore(ChoreType.VISIBLE, { el: 'host1' }); const blockingChore = createMockChore(ChoreType.NODE_DIFF, { el: 'host2' }); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); addBlockedChore(blockedChore, blockingChore, blockedChores); expect(blockingChore.$blockedChores$).toContain(blockedChore); - expect(blockedChores.has(blockedChore)).toBe(true); + expect(blockedChores.includes(blockedChore)).toBe(true); }); it('should initialize $blockedChores$ array if it does not exist', () => { const blockedChore = createMockChore(ChoreType.VISIBLE, { el: 'host1' }); const blockingChore = createMockChore(ChoreType.NODE_DIFF, { el: 'host2' }); blockingChore.$blockedChores$ = null; - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); addBlockedChore(blockedChore, blockingChore, blockedChores); expect(blockingChore.$blockedChores$).toEqual([blockedChore]); - expect(blockedChores.has(blockedChore)).toBe(true); + expect(blockedChores.includes(blockedChore)).toBe(true); }); it('should append to existing $blockedChores$ array', () => { @@ -1168,19 +1170,20 @@ describe('addBlockedChore', () => { const blockingChore = createMockChore(ChoreType.NODE_DIFF, { el: 'host3' }); blockingChore.$blockedChores$ = new ChoreArray(); blockingChore.$blockedChores$.add(blockedChore1); - const blockedChores = new Set([blockedChore1]); + const blockedChores = new ChoreArray(); + blockedChores.add(blockedChore1); addBlockedChore(blockedChore2, blockingChore, blockedChores); expect(blockingChore.$blockedChores$).toEqual([blockedChore2, blockedChore1]); - expect(blockedChores.has(blockedChore1)).toBe(true); - expect(blockedChores.has(blockedChore2)).toBe(true); + expect(blockedChores.includes(blockedChore1)).toBe(true); + expect(blockedChores.includes(blockedChore2)).toBe(true); }); it('should not add duplicate blocked chores', () => { const blockedChore = createMockChore(ChoreType.VISIBLE, { el: 'host1' }); const blockingChore = createMockChore(ChoreType.NODE_DIFF, { el: 'host2' }); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); // Add the same blocked chore twice addBlockedChore(blockedChore, blockingChore, blockedChores); @@ -1189,27 +1192,27 @@ describe('addBlockedChore', () => { // Should only contain the chore once expect(blockingChore.$blockedChores$).toEqual([blockedChore]); expect(blockingChore.$blockedChores$?.length).toBe(1); - expect(blockedChores.has(blockedChore)).toBe(true); - expect(blockedChores.size).toBe(1); + expect(blockedChores.includes(blockedChore)).toBe(true); + expect(blockedChores.length).toBe(1); }); it('should not add blocked chore if it is the same as the blocking chore', () => { const chore = createMockChore(ChoreType.VISIBLE, { el: 'host1' }); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); addBlockedChore(chore, chore, blockedChores); - expect(blockedChores.size).toBe(0); + expect(blockedChores.length).toBe(0); }); it('should not add blocked chore if it looks the same as the blocking chore', () => { const host = { el: 'host1' }; const chore1 = createMockChore(ChoreType.VISIBLE, host); const chore2 = createMockChore(ChoreType.VISIBLE, host); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); addBlockedChore(chore1, chore2, blockedChores); - expect(blockedChores.size).toBe(0); + expect(blockedChores.length).toBe(0); }); }); diff --git a/packages/qwik/src/core/shared/scheduler.ts b/packages/qwik/src/core/shared/scheduler.ts index c2a5fd916d2..f7a81a79c8b 100644 --- a/packages/qwik/src/core/shared/scheduler.ts +++ b/packages/qwik/src/core/shared/scheduler.ts @@ -125,7 +125,7 @@ import { cleanupDestroyable } from '../use/utils/destroyable'; // Turn this on to get debug output of what the scheduler is doing. const DEBUG: boolean = false; -enum ChoreState { +export enum ChoreState { NONE = 0, RUNNING = 1, FAILED = 2, @@ -177,7 +177,7 @@ export const createScheduler = ( container: Container, journalFlush: () => void, choreQueue: ChoreArray, - blockedChores: Set, + blockedChores: ChoreArray, runningChores: Set ) => { let drainChore: Chore | null = null; @@ -355,6 +355,13 @@ This is often caused by modifying a signal in an already rendered component duri chore.$type$ !== ChoreType.QRL_RESOLVE && chore.$type$ !== ChoreType.RUN_QRL; if (shouldBlock) { + const runningChore = getRunningChore(chore); + if (runningChore) { + if (isResourceChore(runningChore)) { + addBlockedChore(chore, runningChore, blockedChores); + } + return chore; + } const blockingChore = findBlockingChore( chore, choreQueue, @@ -366,12 +373,6 @@ This is often caused by modifying a signal in an already rendered component duri addBlockedChore(chore, blockingChore, blockedChores); return chore; } - - const runningChore = getRunningChore(chore); - if (runningChore) { - addBlockedChore(chore, runningChore, blockedChores); - return chore; - } } addChoreAndIncrementBlockingCounter(chore, choreQueue); @@ -890,19 +891,20 @@ function vNodeAlreadyDeleted(chore: Chore): boolean { return !!(chore.$host$ && vnode_isVNode(chore.$host$) && chore.$host$.flags & VNodeFlags.Deleted); } +function isResourceChore(chore: Chore): boolean { + return ( + chore.$type$ === ChoreType.TASK && + !!chore.$payload$ && + !!((chore.$payload$ as Task).$flags$ & TaskFlags.RESOURCE) + ); +} + export function addBlockedChore( blockedChore: Chore, blockingChore: Chore, - blockedChores: Set + blockedChores: ChoreArray ): void { - if ( - !( - blockedChore.$type$ === ChoreType.TASK && - blockedChore.$payload$ && - (blockedChore.$payload$ as Task).$flags$ & TaskFlags.RESOURCE - ) && - choreComparator(blockedChore, blockingChore) === 0 - ) { + if (!isResourceChore(blockedChore) && choreComparator(blockedChore, blockingChore) === 0) { return; } DEBUG && @@ -973,7 +975,7 @@ function debugTrace( action: string, arg?: any | null, queue?: ChoreArray, - blockedChores?: Set + blockedChores?: ChoreArray ) { const lines: string[] = []; @@ -1043,9 +1045,9 @@ function debugTrace( } // Blocked chores section - if (blockedChores && blockedChores.size > 0) { + if (blockedChores && blockedChores.length > 0) { lines.push(''); - lines.push(`🚫 Blocked Chores (${blockedChores.size} items):`); + lines.push(`🚫 Blocked Chores (${blockedChores.length} items):`); Array.from(blockedChores).forEach((chore, index) => { const type = debugChoreTypeToString(chore.$type$); diff --git a/packages/qwik/src/core/shared/scheduler.unit.tsx b/packages/qwik/src/core/shared/scheduler.unit.tsx index d4119d06178..f19a857bcb3 100644 --- a/packages/qwik/src/core/shared/scheduler.unit.tsx +++ b/packages/qwik/src/core/shared/scheduler.unit.tsx @@ -42,7 +42,7 @@ describe('scheduler', () => { let vBHost2: VirtualVNode = null!; let handleError: (err: any, host: HostElement | null) => void; let choreQueue: ChoreArray; - let blockedChores: Set; + let blockedChores: ChoreArray; let runningChores: Set; async function waitForDrain() { @@ -57,7 +57,7 @@ describe('scheduler', () => { const container = getDomContainer(document.body); handleError = container.handleError = vi.fn(); choreQueue = new ChoreArray(); - blockedChores = new Set(); + blockedChores = new ChoreArray(); runningChores = new Set(); scheduler = createScheduler( container, @@ -256,7 +256,7 @@ describe('scheduler', () => { // schedule only first task expect(choreQueue.length).toBe(1); // block the rest - expect(blockedChores.size).toBe(2); + expect(blockedChores.length).toBe(2); await waitForDrain(); expect(testLog).toEqual(['b1.0', 'b1.1', 'b1.2', 'journalFlush']); }); @@ -280,7 +280,7 @@ describe('scheduler', () => { document.body.setAttribute(QContainerAttr, 'paused'); const container = getDomContainer(document.body); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); scheduler = createScheduler( container, @@ -428,7 +428,7 @@ describe('scheduler', () => { document.body.setAttribute(QContainerAttr, 'paused'); const container = getDomContainer(document.body); const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); scheduler = createScheduler( container, @@ -827,7 +827,7 @@ describe('scheduler', () => { describe('getRunningChore', () => { let scheduler: ReturnType = null!; let choreQueue: ChoreArray; - let blockedChores: Set; + let blockedChores: ChoreArray; let runningChores: Set; let vHost: VirtualVNode; @@ -838,7 +838,7 @@ describe('scheduler', () => { document.body.setAttribute(QContainerAttr, 'paused'); const container = getDomContainer(document.body); choreQueue = new ChoreArray(); - blockedChores = new Set(); + blockedChores = new ChoreArray(); runningChores = new Set(); // Mock nextTick to prevent automatic draining of chores @@ -867,7 +867,7 @@ describe('scheduler', () => { // The chore should be scheduled (not blocked) expect(choreQueue.length).toBe(1); - expect(blockedChores.size).toBe(0); + expect(blockedChores.length).toBe(0); }); it('should return false when running chores do not match', async () => { @@ -1006,9 +1006,9 @@ describe('scheduler', () => { // chore1 should be scheduled, chore2 blocked by chore1, chore3 blocked by chore2 expect(choreQueue.length).toBe(1); expect(choreQueue[0]).toBe(chore1); - expect(blockedChores.size).toBe(2); - expect(blockedChores.has(chore2!)).toBe(true); - expect(blockedChores.has(chore3!)).toBe(true); + expect(blockedChores.length).toBe(2); + expect(blockedChores.includes(chore2!)).toBe(true); + expect(blockedChores.includes(chore3!)).toBe(true); expect(vBHost1.blockedChores?.length).toBe(2); expect(vBHost1.blockedChores).toContain(chore2); expect(vBHost1.blockedChores).toContain(chore3); @@ -1036,7 +1036,7 @@ describe('scheduler', () => { expect(testLog).toEqual(['task1', 'task2', 'task3', 'journalFlush']); // After drain, everything should be clear - expect(blockedChores.size).toBe(0); + expect(blockedChores.length).toBe(0); expect(vBHost1.blockedChores?.length).toBe(0); }); @@ -1070,9 +1070,9 @@ describe('scheduler', () => { // Initial state: A1 and B1 scheduled (depth-first), A2 and B2 blocked expect(choreQueue.length).toBe(2); // A1, B1 - expect(blockedChores.size).toBe(2); // A2, B2 - expect(blockedChores.has(choreA2!)).toBe(true); - expect(blockedChores.has(choreB2!)).toBe(true); + expect(blockedChores.length).toBe(2); // A2, B2 + expect(blockedChores.includes(choreA2!)).toBe(true); + expect(blockedChores.includes(choreB2!)).toBe(true); // vnode blocked chores should match expect(vAHost.blockedChores?.length).toBe(1); @@ -1088,7 +1088,7 @@ describe('scheduler', () => { expect(testLog).toEqual(['taskA1', 'taskA2', 'taskB1', 'taskB2', 'journalFlush']); // After drain, everything should be clear - expect(blockedChores.size).toBe(0); + expect(blockedChores.length).toBe(0); expect(vAHost.blockedChores?.length).toBe(0); expect(vBHost1.blockedChores?.length).toBe(0); }); @@ -1120,7 +1120,7 @@ describe('scheduler', () => { const chore2 = scheduler(ChoreType.RUN_QRL, vAHost as HostElement, mockQrl, []); // chore2 should NOT be blocked - expect(blockedChores.has(chore2!)).toBe(false); + expect(blockedChores.includes(chore2!)).toBe(false); expect(chore1!.$blockedChores$).toBeNull(); // Verify no blocked chores for RUN_QRL type for (const chore of blockedChores) { @@ -1148,7 +1148,7 @@ describe('scheduler', () => { const chore2 = scheduler(ChoreType.QRL_RESOLVE, null, mockComputeQRL); // chore2 should NOT be blocked - expect(blockedChores.has(chore2!)).toBe(false); + expect(blockedChores.includes(chore2!)).toBe(false); expect(chore1!.$blockedChores$).toBeNull(); // Verify no blocked chores for QRL_RESOLVE type for (const chore of blockedChores) { @@ -1159,6 +1159,26 @@ describe('scheduler', () => { nextTickSpy.mockRestore(); }); }); + + it('should early return if chore is already running', async () => { + const mockHost = vnode_newVirtual(); + mockHost.setProp('q:id', 'test-host'); + const mockQrl = { $hash$: 'test-qrl-hash' } as any; + + // Create and start a chore + const chore1 = scheduler(ChoreType.COMPONENT, mockHost as any, mockQrl, null); + runningChores.add(chore1!); + choreQueue.length = 0; + expect(choreQueue.length).toBe(0); + + // Schedule the same chore again + scheduler(ChoreType.COMPONENT, mockHost as any, mockQrl, null); + + // still 0 + expect(choreQueue.length).toBe(0); + expect(blockedChores.length).toBe(0); + expect(runningChores.size).toBe(1); + }); }); function mockTask(host: VNode, opts: { index?: number; qrl?: QRL; visible?: boolean }): Task { diff --git a/packages/qwik/src/core/shared/shared-container.ts b/packages/qwik/src/core/shared/shared-container.ts index 49a4883fe9b..48a5d9bde19 100644 --- a/packages/qwik/src/core/shared/shared-container.ts +++ b/packages/qwik/src/core/shared/shared-container.ts @@ -37,7 +37,7 @@ export abstract class _SharedContainer implements Container { }; const choreQueue = new ChoreArray(); - const blockedChores = new Set(); + const blockedChores = new ChoreArray(); const runningChores = new Set(); this.$scheduler$ = createScheduler( this,