@@ -525,37 +525,58 @@ private async Task StartContainerAsync(IExecutionContext executionContext, Conta
525525 container . MountVolumes . Add ( new MountVolume ( taskKeyFile , container . TranslateToContainerPath ( taskKeyFile ) ) ) ;
526526 }
527527
528+ bool useNode20ToStartContainer = AgentKnobs . UseNode20ToStartContainer . GetValue ( executionContext ) . AsBoolean ( ) ;
529+ bool useAgentNode = false ;
530+
531+ string labelContainerStartupUsingNode20 = "container-startup-using-node-20" ;
532+ string labelContainerStartupUsingNode16 = "container-startup-using-node-16" ;
533+ string labelContainerStartupFailed = "container-startup-failed" ;
534+
535+ string containerNodePath ( string nodeFolder )
536+ {
537+ return container . TranslateToContainerPath ( Path . Combine ( HostContext . GetDirectory ( WellKnownDirectory . Externals ) , nodeFolder , "bin" , $ "node{ IOUtil . ExeExtension } ") ) ;
538+ }
539+
540+ string nodeContainerPath = containerNodePath ( NodeHandler . NodeFolder ) ;
541+ string node16ContainerPath = containerNodePath ( NodeHandler . Node16Folder ) ;
542+ string node20ContainerPath = containerNodePath ( NodeHandler . Node20_1Folder ) ;
543+
528544 if ( container . IsJobContainer )
529545 {
530546 // See if this container brings its own Node.js
531547 container . CustomNodePath = await _dockerManger . DockerInspect ( context : executionContext ,
532548 dockerObject : container . ContainerImage ,
533549 options : $ "--format=\" {{{{index .Config.Labels \\ \" { _nodeJsPathLabel } \\ \" }}}}\" ") ;
534550
535- string node ;
551+ string nodeSetInterval ( string node )
552+ {
553+ return $ "'{ node } ' -e 'setInterval(function(){{}}, 24 * 60 * 60 * 1000);'";
554+ }
555+
556+ string useDoubleQuotes ( string value )
557+ {
558+ return value . Replace ( '\' ' , '"' ) ;
559+ }
560+
536561 if ( ! string . IsNullOrEmpty ( container . CustomNodePath ) )
537562 {
538- node = container . CustomNodePath ;
563+ container . ContainerCommand = useDoubleQuotes ( nodeSetInterval ( container . CustomNodePath ) ) ;
564+ container . ResultNodePath = container . CustomNodePath ;
565+ }
566+ else if ( PlatformUtil . RunningOnMacOS || ( PlatformUtil . RunningOnWindows && container . ImageOS == PlatformUtil . OS . Linux ) )
567+ {
568+ // require container to have node if running on macOS, or if running on Windows and attempting to run Linux container
569+ container . CustomNodePath = "node" ;
570+ container . ContainerCommand = useDoubleQuotes ( nodeSetInterval ( container . CustomNodePath ) ) ;
571+ container . ResultNodePath = container . CustomNodePath ;
539572 }
540573 else
541574 {
542- node = container . TranslateToContainerPath ( Path . Combine ( HostContext . GetDirectory ( WellKnownDirectory . Externals ) , AgentKnobs . UseNode20ToStartContainer . GetValue ( executionContext ) . AsBoolean ( ) ? NodeHandler . Node20_1Folder : NodeHandler . NodeFolder , "bin" , $ "node{ IOUtil . ExeExtension } ") ) ;
543-
544- // if on Mac OS X, require container to have node
545- if ( PlatformUtil . RunningOnMacOS )
546- {
547- container . CustomNodePath = "node" ;
548- node = container . CustomNodePath ;
549- }
550- // if running on Windows, and attempting to run linux container, require container to have node
551- else if ( PlatformUtil . RunningOnWindows && container . ImageOS == PlatformUtil . OS . Linux )
552- {
553- container . CustomNodePath = "node" ;
554- node = container . CustomNodePath ;
555- }
575+ useAgentNode = true ;
576+ string sleepCommand = useNode20ToStartContainer ? $ "'{ node20ContainerPath } ' --version && echo '{ labelContainerStartupUsingNode20 } ' && { nodeSetInterval ( node20ContainerPath ) } || '{ node16ContainerPath } ' --version && echo '{ labelContainerStartupUsingNode16 } ' && { nodeSetInterval ( node16ContainerPath ) } || echo '{ labelContainerStartupFailed } '" : nodeSetInterval ( nodeContainerPath ) ;
577+ container . ContainerCommand = PlatformUtil . RunningOnWindows ? $ "cmd.exe /c call { useDoubleQuotes ( sleepCommand ) } " : $ "bash -c \" { sleepCommand } \" ";
578+ container . ResultNodePath = nodeContainerPath ;
556579 }
557- string sleepCommand = $ "\" { node } \" -e \" setInterval(function(){{}}, 24 * 60 * 60 * 1000);\" ";
558- container . ContainerCommand = sleepCommand ;
559580 }
560581
561582 container . ContainerId = await _dockerManger . DockerCreate ( executionContext , container ) ;
@@ -588,6 +609,56 @@ private async Task StartContainerAsync(IExecutionContext executionContext, Conta
588609
589610 executionContext . Warning ( $ "Docker container { container . ContainerId } is not in running state.") ;
590611 }
612+ else if ( useAgentNode && useNode20ToStartContainer )
613+ {
614+ bool containerStartupCompleted = false ;
615+ int containerStartupTimeoutInMilliseconds = 10000 ;
616+ int delayInMilliseconds = 100 ;
617+ int checksCount = 0 ;
618+
619+ while ( true )
620+ {
621+ List < string > containerLogs = await _dockerManger . GetDockerLogs ( executionContext , container . ContainerId ) ;
622+
623+ foreach ( string logLine in containerLogs )
624+ {
625+ if ( logLine . Contains ( labelContainerStartupUsingNode20 ) )
626+ {
627+ executionContext . Debug ( "Using Node 20 for container startup." ) ;
628+ containerStartupCompleted = true ;
629+ container . ResultNodePath = node20ContainerPath ;
630+ break ;
631+ }
632+ else if ( logLine . Contains ( labelContainerStartupUsingNode16 ) )
633+ {
634+ executionContext . Warning ( "Can not run Node 20 in container. Falling back to Node 16 for container startup." ) ;
635+ containerStartupCompleted = true ;
636+ container . ResultNodePath = node16ContainerPath ;
637+ break ;
638+ }
639+ else if ( logLine . Contains ( labelContainerStartupFailed ) )
640+ {
641+ executionContext . Error ( "Can not run both Node 20 and Node 16 in container. Container startup failed." ) ;
642+ containerStartupCompleted = true ;
643+ break ;
644+ }
645+ }
646+
647+ if ( containerStartupCompleted )
648+ {
649+ break ;
650+ }
651+
652+ checksCount ++ ;
653+ if ( checksCount * delayInMilliseconds > containerStartupTimeoutInMilliseconds )
654+ {
655+ executionContext . Warning ( "Can not get startup status from container." ) ;
656+ break ;
657+ }
658+
659+ await Task . Delay ( delayInMilliseconds ) ;
660+ }
661+ }
591662 }
592663 catch ( Exception ex )
593664 {
0 commit comments