@@ -534,7 +534,8 @@ public actor Orchestrator {
534534        progressHandler:  ProgressUpdateHandler ?   =  nil , 
535535        pullPolicy:  PullPolicy  =  . missing, 
536536        wait:  Bool  =  false , 
537-         waitTimeoutSeconds:  Int ?   =  nil 
537+         waitTimeoutSeconds:  Int ?   =  nil , 
538+         disableHealthcheck:  Bool  =  false 
538539    )  async  throws  { 
539540        log. info ( " Starting project ' \( project. name) ' " ) 
540541
@@ -595,14 +596,15 @@ public actor Orchestrator {
595596            noRecreate:  noRecreate, 
596597            removeOnExit:  removeOnExit, 
597598            progressHandler:  progressHandler, 
598-             pullPolicy:  pullPolicy
599+             pullPolicy:  pullPolicy, 
600+             disableHealthcheck:  disableHealthcheck
599601        ) 
600602
601603        // If --wait is set, wait for selected services to be healthy/running
602604        if  wait { 
603605            let  timeout  =  waitTimeoutSeconds ??  300 
604606            for  (name,  svc)    in  targetServices { 
605-                 if  svc. healthCheck !=  nil  { 
607+                 if  !disableHealthcheck ,   svc. healthCheck !=  nil  { 
606608                    try   await  waitUntilHealthy ( project:  project,  serviceName:  name,  service:  svc) 
607609                }  else  { 
608610                    let  cid  =  svc. containerName ??  " \( project. name) _ \( name) " 
@@ -667,7 +669,8 @@ public actor Orchestrator {
667669        noRecreate:  Bool , 
668670        removeOnExit:  Bool , 
669671        progressHandler:  ProgressUpdateHandler ? , 
670-         pullPolicy:  PullPolicy 
672+         pullPolicy:  PullPolicy , 
673+         disableHealthcheck:  Bool 
671674    )  async  throws  { 
672675        // Sort services by dependencies
673676        let  resolution  =  try   DependencyResolver . resolve ( services:  services) 
@@ -678,7 +681,7 @@ public actor Orchestrator {
678681
679682            do  { 
680683                // Wait for dependency conditions before starting this service
681-                 try   await  waitForDependencyConditions ( project:  project,  serviceName:  serviceName,  services:  services) 
684+                 try   await  waitForDependencyConditions ( project:  project,  serviceName:  serviceName,  services:  services,  disableHealthcheck :  disableHealthcheck ) 
682685
683686                try   await  createAndStartContainer ( 
684687                    project:  project, 
@@ -692,12 +695,8 @@ public actor Orchestrator {
692695                    pullPolicy:  pullPolicy
693696                ) 
694697
695-                 // Optionally kick health monitor for the service
696-                 if  service. healthCheck !=  nil  { 
697-                     Task  {  [ weak self]  in 
698-                         _ =  try ?   await  self ? . runHealthCheckOnce ( project:  project,  serviceName:  serviceName,  service:  service) 
699-                     } 
700-                 } 
698+                 // Do not run background health probes by default.
699+                 // Health checks are evaluated only when --wait is explicitly requested.
701700            }  catch  { 
702701                log. error ( " Failed to start service ' \( serviceName) ':  \( error) " ) 
703702                throw  error
@@ -706,7 +705,7 @@ public actor Orchestrator {
706705    } 
707706
708707    /// Wait for dependencies according to compose depends_on conditions
709-     private  func  waitForDependencyConditions( project:  Project ,  serviceName:  String ,  services:  [ String :  Service ] )  async  throws  { 
708+     private  func  waitForDependencyConditions( project:  Project ,  serviceName:  String ,  services:  [ String :  Service ] ,  disableHealthcheck :   Bool )  async  throws  { 
710709        guard  let  svc =  services [ serviceName]  else  {  return  } 
711710
712711        // Wait for service_started
@@ -715,7 +714,7 @@ public actor Orchestrator {
715714            try   await  waitUntilContainerRunning ( containerId:  depId,  timeoutSeconds:  120 ) 
716715        } 
717716        // Wait for service_healthy
718-         for  dep  in  svc. dependsOnHealthy { 
717+         for  dep  in  svc. dependsOnHealthy where  !disableHealthcheck  { 
719718            if  let  depSvc =  services [ dep]  { 
720719                try   await  waitUntilHealthy ( project:  project,  serviceName:  dep,  service:  depSvc) 
721720            } 
@@ -903,16 +902,24 @@ public actor Orchestrator {
903902
904903        // Recreate the container
905904        log. info ( " Recreating existing container ' \( existing. id) ' for service ' \( serviceName) ' " ) 
906-         // Stop with a longer timeout and wait until it's fully stopped before deleting 
905+         // 1) Ask it to stop gracefully (SIGTERM) with longer timeout 
907906        do  {  try   await  existing. stop ( opts:  ContainerStopOptions ( timeoutInSeconds:  15 ,  signal:  SIGTERM) )  } 
908907        catch  {  log. warning ( " failed to stop  \( existing. id) :  \( error) " )  } 
909-         do  {  try   await  waitUntilContainerStopped ( containerId:  existing. id,  timeoutSeconds:  20 )  } 
910-         catch  {  log. warning ( " timeout waiting for  \( existing. id)  to stop:  \( error) " )  } 
908+         // 2) Wait until it is actually stopped; if not, escalate to SIGKILL and wait briefly
909+         do  { 
910+             try   await  waitUntilContainerStopped ( containerId:  existing. id,  timeoutSeconds:  20 ) 
911+         }  catch  { 
912+             log. warning ( " timeout waiting for  \( existing. id)  to stop:  \( error) ; sending SIGKILL " ) 
913+             do  {  try   await  existing. kill ( SIGKILL)  }  catch  {  log. warning ( " failed to SIGKILL  \( existing. id) :  \( error) " )  } 
914+             // small wait after SIGKILL
915+             try ?   await  Task . sleep ( nanoseconds:  700_000_000 ) 
916+         } 
917+         // 3) Try to delete (force on retry)
911918        do  {  try   await  existing. delete ( )  } 
912919        catch  { 
913-             log. warning ( " failed to delete  \( existing. id) :  \( error) ; retrying once  after short delay " ) 
920+             log. warning ( " failed to delete  \( existing. id) :  \( error) ; retrying forced delete  after short delay " ) 
914921            try ?   await  Task . sleep ( nanoseconds:  700_000_000 ) 
915-             do  {  try   await  existing. delete ( )  }  catch  {  log. warning ( " second  delete attempt failed for \( existing. id) :  \( error) " )  } 
922+             do  {  try   await  existing. delete ( force :   true )  }  catch  {  log. warning ( " forced  delete attempt failed for \( existing. id) :  \( error) " )  } 
916923        } 
917924        projectState [ project. name] ? . containers. removeValue ( forKey:  serviceName) 
918925    } 
@@ -1128,12 +1135,25 @@ public actor Orchestrator {
11281135        // Add volume mounts (ensure named/anonymous volumes exist and use their host paths)
11291136        config. mounts =  try   await  resolveComposeMounts ( project:  project,  serviceName:  serviceName,  mounts:  service. volumes) 
11301137
1131-         // Add resource limits
1138+         // Add resource limits (compose-style parsing for memory like "2g", "2048MB"). 
11321139        if  let  cpus =  service. cpus { 
11331140            config. resources. cpus =  Int ( cpus)  ??  4 
11341141        } 
1135-         if  let  memory =  service. memory { 
1136-             config. resources. memoryInBytes =  UInt64 ( memory)  ??  1024 . mib ( ) 
1142+         if  let  memStr =  service. memory,  !memStr. isEmpty { 
1143+             do  { 
1144+                 if  memStr. lowercased ( )  ==  " max "  { 
1145+                     // Treat "max" as no override: keep the runtime/default value (set below if needed).
1146+                     // Intentionally do nothing here.
1147+                 }  else  { 
1148+                     let  res  =  try   Parser . resources ( cpus:  nil ,  memory:  memStr) 
1149+                     if  let  bytes =  res. memoryInBytes as  UInt64 ?   {  config. resources. memoryInBytes =  bytes } 
1150+                 } 
1151+             }  catch  { 
1152+                 log. warning ( " Invalid memory value ' \\ (memStr)'; using default. Error:  \\ (error) " ) 
1153+             } 
1154+         }  else  { 
1155+             // Safer default for dev servers (was 1 GiB)
1156+             config. resources. memoryInBytes =  2048 . mib ( ) 
11371157        } 
11381158
11391159        // TTY support from compose service
@@ -1670,7 +1690,8 @@ public actor Orchestrator {
16701690        services:  [ String ]  =  [ ] , 
16711691        follow:  Bool  =  false , 
16721692        tail:  Int ?   =  nil , 
1673-         timestamps:  Bool  =  false 
1693+         timestamps:  Bool  =  false , 
1694+         includeBoot:  Bool  =  false 
16741695    )  async  throws  ->  AsyncThrowingStream < LogEntry ,  Error >  { 
16751696        // Resolve target services
16761697        let  selected  =  services. isEmpty ?  Set ( project. services. keys)  :  Set ( services) 
@@ -1702,6 +1723,8 @@ public actor Orchestrator {
17021723
17031724            final  class  Emitter :  @unchecked   Sendable  { 
17041725                let  cont :  AsyncThrowingStream < LogEntry ,  Error > . Continuation 
1726+                 // Strongly retain file handles so readabilityHandler keeps firing.
1727+                 private  var  retained :  [ FileHandle ]  =  [ ] 
17051728                init ( _ c:  AsyncThrowingStream < LogEntry ,  Error > . Continuation )  {  self . cont =  c } 
17061729                func  emit( _ svc:  String ,  _ containerName:  String ,  _ stream:  LogEntry . LogStream ,  data:  Data )  { 
17071730                    guard  !data. isEmpty else  {  return  } 
@@ -1711,13 +1734,11 @@ public actor Orchestrator {
17111734                        } 
17121735                    } 
17131736                } 
1737+                 func  retain( _ fh:  FileHandle )  {  retained. append ( fh)  } 
17141738                func  finish( )  {  cont. finish ( )  } 
17151739                func  fail( _ error:  Error )  {  cont. yield ( with:  . failure( error) )  } 
17161740            } 
17171741            let  emitter  =  Emitter ( continuation) 
1718-             // Retain FileHandles so readabilityHandler continues firing while the stream is active
1719-             actor  HandleRetainer  {  var  fhs :  [ FileHandle ]  =  [ ] ; func  add( _ fh:  FileHandle ) {  fhs. append ( fh)  }  } 
1720-             let  retainer  =  HandleRetainer ( ) 
17211742            actor  Counter  {  var  value :  Int ; init ( _ v:  Int ) {  value =  v }  ;  func  dec( )  ->  Int  {  value -=  1 ; return  value }  } 
17221743            let  counter  =  Counter ( targets. count) 
17231744
@@ -1732,20 +1753,20 @@ public actor Orchestrator {
17321753                                fh. readabilityHandler =  {  handle in 
17331754                                    emitter. emit ( svc,  container. id,  . stdout,  data:  handle. availableData) 
17341755                                } 
1735-                                 await  retainer . add ( fh) 
1756+                                 emitter . retain ( fh) 
17361757                            } 
1737-                             if  fds. indices. contains ( 1 )  { 
1758+                             if  includeBoot ,   fds. indices. contains ( 1 )  { 
17381759                                let  fh  =  fds [ 1 ] 
17391760                                fh. readabilityHandler =  {  handle in 
17401761                                    emitter. emit ( svc,  container. id,  . stderr,  data:  handle. availableData) 
17411762                                } 
1742-                                 await  retainer . add ( fh) 
1763+                                 emitter . retain ( fh) 
17431764                            } 
17441765                        }  else  { 
17451766                            if  fds. indices. contains ( 0 )  { 
17461767                                emitter. emit ( svc,  container. id,  . stdout,  data:  fds [ 0 ] . readDataToEndOfFile ( ) ) 
17471768                            } 
1748-                             if  fds. indices. contains ( 1 )  { 
1769+                             if  includeBoot ,   fds. indices. contains ( 1 )  { 
17491770                                emitter. emit ( svc,  container. id,  . stderr,  data:  fds [ 1 ] . readDataToEndOfFile ( ) ) 
17501771                            } 
17511772                            let  left  =  await  counter. dec ( ) 
@@ -1765,10 +1786,11 @@ public actor Orchestrator {
17651786    public  func  start( 
17661787        project:  Project , 
17671788        services:  [ String ]  =  [ ] , 
1789+         disableHealthcheck:  Bool  =  false , 
17681790        progressHandler:  ProgressUpdateHandler ?   =  nil 
17691791    )  async  throws  { 
1770-         // For build functionality, we just call up 
1771-         try   await  up ( project:  project,  services:  services,  progressHandler:  progressHandler) 
1792+         // Reuse up() path with defaults 
1793+         try   await  up ( project:  project,  services:  services,  progressHandler:  progressHandler,  disableHealthcheck :  disableHealthcheck ) 
17721794    } 
17731795
17741796
@@ -1789,10 +1811,11 @@ public actor Orchestrator {
17891811        project:  Project , 
17901812        services:  [ String ]  =  [ ] , 
17911813        timeout:  Int  =  10 , 
1814+         disableHealthcheck:  Bool  =  false , 
17921815        progressHandler:  ProgressUpdateHandler ?   =  nil 
17931816    )  async  throws  { 
17941817        _ =  try   await  down ( project:  project,  progressHandler:  progressHandler) 
1795-         try   await  up ( project:  project,  services:  services,  progressHandler:  progressHandler) 
1818+         try   await  up ( project:  project,  services:  services,  progressHandler:  progressHandler,  disableHealthcheck :  disableHealthcheck ) 
17961819    } 
17971820
17981821    /// Execute command in a service
0 commit comments