@@ -4,6 +4,7 @@ import logger from './Logger';
44import NativePatchCore from './NativePatchCore' ;
55import type { UpdateContext } from './UpdateContext' ;
66import { isSafePathComponent } from './PathUtils' ;
7+ import { monotonicNowMs } from './MonotonicClock' ;
78
89// 原生冷启动检测(NATIVE_CHECKUPDATE_DESIGN §10):每进程一次,getBundleUrl
910// 后延迟数秒运行,完全不依赖 app bundle——坏热更把 JS 砸挂后,下次启动仍能
@@ -409,13 +410,37 @@ function normalizeEndpointBase(base: string): string {
409410 return base . replace ( / \/ + $ / , '' ) ;
410411}
411412
413+ async function runWithinDeadline (
414+ start : ( ) => Promise < void > ,
415+ deadlineUptimeMs : number ,
416+ ) : Promise < void > {
417+ const remainingMs = deadlineUptimeMs - monotonicNowMs ( ) ;
418+ if ( remainingMs <= 0 ) {
419+ throw Error ( 'Download phase deadline expired before start' ) ;
420+ }
421+ let deadlineTimer = 0 ;
422+ const deadlinePromise = new Promise < void > ( ( _ , reject ) => {
423+ deadlineTimer = setTimeout ( ( ) => {
424+ reject ( Error ( 'Download phase deadline exceeded' ) ) ;
425+ } , remainingMs ) ;
426+ } ) ;
427+ try {
428+ // This bounds queueing, HTTP, decompression and native hpatch work from
429+ // the orchestrator's perspective. The serialized task may still finish
430+ // later, but it can no longer prevent the response cache from settling.
431+ await Promise . race ( [ start ( ) , deadlinePromise ] ) ;
432+ } finally {
433+ clearTimeout ( deadlineTimer ) ;
434+ }
435+ }
436+
412437async function performAttempts (
413438 context : UpdateContext ,
414439 attempts : DecisionAttempt [ ] ,
415440 hash : string ,
416441 originHash : string ,
417442) : Promise < boolean > {
418- const incrementalDeadline = Date . now ( ) + DOWNLOAD_PHASE_TIMEOUT_MS ;
443+ const incrementalDeadline = monotonicNowMs ( ) + DOWNLOAD_PHASE_TIMEOUT_MS ;
419444 let fullDeadline = 0 ;
420445 for ( const attempt of attempts ) {
421446 const type = attempt . type ?? '' ;
@@ -428,26 +453,35 @@ async function performAttempts(
428453 if ( isFullAttempt && fullDeadline === 0 ) {
429454 // Preserve a full 10min rescue budget even when diff/pdiff exhausted
430455 // their own phase window.
431- fullDeadline = Date . now ( ) + DOWNLOAD_PHASE_TIMEOUT_MS ;
456+ fullDeadline = monotonicNowMs ( ) + DOWNLOAD_PHASE_TIMEOUT_MS ;
432457 }
433458 const deadline = isFullAttempt ? fullDeadline : incrementalDeadline ;
434459 for ( const url of attempt . urls ?? [ ] ) {
435460 if ( ! url ) {
436461 continue ;
437462 }
438- if ( Date . now ( ) >= deadline ) {
463+ if ( monotonicNowMs ( ) >= deadline ) {
439464 if ( isFullAttempt ) {
440465 return false ;
441466 }
442467 break ;
443468 }
444469 try {
445470 if ( type === DOWNLOAD_TYPE_DIFF ) {
446- await context . downloadPatchFromPpk ( url , hash , originHash , deadline ) ;
471+ await runWithinDeadline (
472+ ( ) => context . downloadPatchFromPpk ( url , hash , originHash , deadline ) ,
473+ deadline ,
474+ ) ;
447475 } else if ( type === DOWNLOAD_TYPE_PDIFF ) {
448- await context . downloadPatchFromPackage ( url , hash , deadline ) ;
476+ await runWithinDeadline (
477+ ( ) => context . downloadPatchFromPackage ( url , hash , deadline ) ,
478+ deadline ,
479+ ) ;
449480 } else {
450- await context . downloadFullUpdate ( url , hash , deadline ) ;
481+ await runWithinDeadline (
482+ ( ) => context . downloadFullUpdate ( url , hash , deadline ) ,
483+ deadline ,
484+ ) ;
451485 }
452486 return true ;
453487 } catch ( e ) {
0 commit comments