@@ -411,6 +411,185 @@ describe('scheduler', () => {
411411 } ) ;
412412 } ) ;
413413
414+ describe ( 'deadline-based async flushing' , ( ) => {
415+ let scheduler : ReturnType < typeof createScheduler > = null ! ;
416+ let document : ReturnType < typeof createDocument > = null ! ;
417+ let vHost : VirtualVNode = null ! ;
418+
419+ async function waitForDrain ( ) {
420+ await scheduler ( ChoreType . WAIT_FOR_QUEUE ) . $returnValue$ ;
421+ }
422+
423+ beforeEach ( ( ) => {
424+ vi . clearAllMocks ( ) ;
425+ ( globalThis as any ) . testLog = [ ] ;
426+ vi . useFakeTimers ( ) ;
427+ document = createDocument ( ) ;
428+ document . body . setAttribute ( QContainerAttr , 'paused' ) ;
429+ const container = getDomContainer ( document . body ) ;
430+ const choreQueue = new ChoreArray ( ) ;
431+ const blockedChores = new Set < Chore > ( ) ;
432+ const runningChores = new Set < Chore > ( ) ;
433+ scheduler = createScheduler (
434+ container ,
435+ ( ) => testLog . push ( 'journalFlush' ) ,
436+ choreQueue ,
437+ blockedChores ,
438+ runningChores
439+ ) ;
440+ vnode_newUnMaterializedElement ( document . body ) ;
441+ vHost = vnode_newVirtual ( ) ;
442+ vHost . setProp ( 'q:id' , 'host' ) ;
443+ } ) ;
444+
445+ afterEach ( ( ) => {
446+ vi . useRealTimers ( ) ;
447+ vi . unstubAllGlobals ( ) ;
448+ } ) ;
449+
450+ it ( 'fast async (<16ms) + long async (>16ms): flush at ~16ms while long runs' , async ( ) => {
451+ const FREQUENCY_MS = Math . floor ( 1000 / 60 ) ;
452+ // Fast async (5ms)
453+ scheduler (
454+ ChoreType . TASK ,
455+ mockTask ( vHost , {
456+ index : 0 ,
457+ qrl : $ (
458+ ( ) =>
459+ new Promise < void > ( ( resolve ) =>
460+ setTimeout ( ( ) => {
461+ testLog . push ( 'fastAsync' ) ;
462+ resolve ( ) ;
463+ } , 5 )
464+ )
465+ ) ,
466+ } )
467+ ) ;
468+ // Long async (1000ms)
469+ scheduler (
470+ ChoreType . TASK ,
471+ mockTask ( vHost , {
472+ index : 1 ,
473+ qrl : $ (
474+ ( ) =>
475+ new Promise < void > ( ( resolve ) =>
476+ setTimeout ( ( ) => {
477+ testLog . push ( 'longAsync' ) ;
478+ resolve ( ) ;
479+ } , 1000 )
480+ )
481+ ) ,
482+ } )
483+ ) ;
484+
485+ // Advance to 5ms: fast async resolves
486+ await vi . advanceTimersByTimeAsync ( 5 ) ;
487+ expect ( testLog ) . toEqual ( [
488+ // end of queue flush
489+ 'journalFlush' ,
490+ // task execution
491+ 'fastAsync' ,
492+ ] ) ;
493+
494+ await vi . advanceTimersByTimeAsync ( FREQUENCY_MS - 5 ) ;
495+
496+ // Flush should have occurred before longAsync finishes
497+ expect ( testLog ) . toEqual ( [
498+ // end of queue flush
499+ 'journalFlush' ,
500+ // task execution
501+ 'fastAsync' ,
502+ // after task execution flush
503+ 'journalFlush' ,
504+ ] ) ;
505+
506+ // Finish long async
507+ await vi . advanceTimersByTimeAsync ( 1000 - FREQUENCY_MS ) ;
508+
509+ // Now long async completes and a final flush happens at end of drain
510+ const drainPromise = waitForDrain ( ) ;
511+ // Need to advance timers to process the nextTick that waitForDrain schedules
512+ await vi . advanceTimersByTimeAsync ( 0 ) ;
513+ await drainPromise ;
514+
515+ expect ( testLog ) . toEqual ( [
516+ // end of queue flush
517+ 'journalFlush' ,
518+ // task execution
519+ 'fastAsync' ,
520+ // after task execution flush
521+ 'journalFlush' ,
522+ 'longAsync' ,
523+ 'journalFlush' ,
524+ // TODO: not sure why this is here, but seems related to the vi.advanceTimersByTimeAsync(0) above
525+ 'journalFlush' ,
526+ ] ) ;
527+ } ) ;
528+
529+ it ( 'multiple fast async (<16ms total): do not flush between, only after' , async ( ) => {
530+ const FREQUENCY_MS = Math . floor ( 1000 / 60 ) ;
531+ // Two fast async chores: 5ms and 6ms (total 11ms < 16ms)
532+ scheduler (
533+ ChoreType . TASK ,
534+ mockTask ( vHost , {
535+ index : 0 ,
536+ qrl : $ (
537+ ( ) =>
538+ new Promise < void > ( ( resolve ) =>
539+ setTimeout ( ( ) => {
540+ testLog . push ( 'fast1' ) ;
541+ resolve ( ) ;
542+ } , 5 )
543+ )
544+ ) ,
545+ } )
546+ ) ;
547+ scheduler (
548+ ChoreType . TASK ,
549+ mockTask ( vHost , {
550+ index : 1 ,
551+ qrl : $ (
552+ ( ) =>
553+ new Promise < void > ( ( resolve ) =>
554+ setTimeout ( ( ) => {
555+ testLog . push ( 'fast2' ) ;
556+ resolve ( ) ;
557+ } , 6 )
558+ )
559+ ) ,
560+ } )
561+ ) ;
562+
563+ // First resolves at 5ms
564+ await vi . advanceTimersByTimeAsync ( 5 ) ;
565+ expect ( testLog ) . toEqual ( [
566+ // end of queue flush
567+ 'journalFlush' ,
568+ 'fast1' ,
569+ ] ) ;
570+
571+ // Second resolves at 11ms
572+ await vi . advanceTimersByTimeAsync ( 6 ) ;
573+ expect ( testLog ) . toEqual ( [
574+ // end of queue flush
575+ 'journalFlush' ,
576+ 'fast1' ,
577+ 'fast2' ,
578+ ] ) ;
579+
580+ await vi . advanceTimersByTimeAsync ( FREQUENCY_MS - 11 ) ;
581+
582+ expect ( testLog ) . toEqual ( [
583+ // end of queue flush
584+ 'journalFlush' ,
585+ 'fast1' ,
586+ 'fast2' ,
587+ // journal flush after fast1/fast2 chore
588+ 'journalFlush' ,
589+ ] ) ;
590+ } ) ;
591+ } ) ;
592+
414593 describe ( 'addChore' , ( ) => {
415594 let choreArray : ChoreArray ;
416595 let vHost : VirtualVNode ;
0 commit comments