@@ -822,87 +822,7 @@ func (c *Container) ExecResize(sessionID string, newSize resize.TerminalSize) er
822822}
823823
824824func (c * Container ) healthCheckExec (config * ExecConfig , timeout time.Duration , streams * define.AttachStreams ) (int , error ) {
825- unlock := true
826- if ! c .batched {
827- c .lock .Lock ()
828- defer func () {
829- if unlock {
830- c .lock .Unlock ()
831- }
832- }()
833-
834- if err := c .syncContainer (); err != nil {
835- return - 1 , err
836- }
837- }
838-
839- if err := c .verifyExecConfig (config ); err != nil {
840- return - 1 , err
841- }
842-
843- if ! c .ensureState (define .ContainerStateRunning ) {
844- return - 1 , fmt .Errorf ("can only create exec sessions on running containers: %w" , define .ErrCtrStateInvalid )
845- }
846-
847- session , err := c .createExecSession (config )
848- if err != nil {
849- return - 1 , err
850- }
851-
852- if c .state .ExecSessions == nil {
853- c .state .ExecSessions = make (map [string ]* ExecSession )
854- }
855- c .state .ExecSessions [session .ID ()] = session
856- defer delete (c .state .ExecSessions , session .ID ())
857-
858- opts , err := prepareForExec (c , session )
859- if err != nil {
860- return - 1 , err
861- }
862- defer func () {
863- // cleanupExecBundle MUST be called with the parent container locked.
864- if ! unlock && ! c .batched {
865- c .lock .Lock ()
866- unlock = true
867-
868- if err := c .syncContainer (); err != nil {
869- logrus .Errorf ("Error syncing container %s state: %v" , c .ID (), err )
870- // Normally we'd want to continue here, get rid of the exec directory.
871- // But the risk of proceeding into a function that can mutate state with a bad state is high.
872- // Lesser of two evils is to bail and leak a directory.
873- return
874- }
875- }
876- if err := c .cleanupExecBundle (session .ID ()); err != nil {
877- logrus .Errorf ("Container %s light exec session cleanup error: %v" , c .ID (), err )
878- }
879- }()
880-
881- pid , attachErrChan , err := c .ociRuntime .ExecContainer (c , session .ID (), opts , streams , nil )
882- if err != nil {
883- return - 1 , err
884- }
885- session .PID = pid
886- session .PIDData = getPidData (pid )
887-
888- if ! c .batched {
889- c .lock .Unlock ()
890- unlock = false
891- }
892-
893- select {
894- case err = <- attachErrChan :
895- if err != nil {
896- return - 1 , fmt .Errorf ("container %s light exec session with pid: %d error: %v" , c .ID (), pid , err )
897- }
898- case <- time .After (timeout ):
899- if err := c .ociRuntime .ExecStopContainer (c , session .ID (), 0 ); err != nil {
900- return - 1 , err
901- }
902- return - 1 , fmt .Errorf ("%v of %s" , define .ErrHealthCheckTimeout , c .HealthCheckConfig ().Timeout .String ())
903- }
904-
905- return c .readExecExitCode (session .ID ())
825+ return c .execLightweight (config , streams , timeout )
906826}
907827
908828func (c * Container ) Exec (config * ExecConfig , streams * define.AttachStreams , resize <- chan resize.TerminalSize ) (int , error ) {
@@ -1321,3 +1241,99 @@ func justWriteExecExitCode(c *Container, sessionID string, exitCode int, emitEve
13211241 // Finally, save our changes.
13221242 return c .save ()
13231243}
1244+
1245+ // execLightweight executes a command in a container without creating a persistent exec session.
1246+ // It is used by both ExecNoSession and healthCheckExec to avoid code duplication.
1247+ func (c * Container ) execLightweight (config * ExecConfig , streams * define.AttachStreams , timeout time.Duration ) (int , error ) {
1248+ if err := c .verifyExecConfig (config ); err != nil {
1249+ return - 1 , err
1250+ }
1251+
1252+ unlock := true
1253+ if ! c .batched {
1254+ c .lock .Lock ()
1255+ defer func () {
1256+ if unlock {
1257+ c .lock .Unlock ()
1258+ }
1259+ }()
1260+
1261+ if err := c .syncContainer (); err != nil {
1262+ return - 1 , err
1263+ }
1264+ }
1265+
1266+ if ! c .ensureState (define .ContainerStateRunning ) {
1267+ return - 1 , fmt .Errorf ("can only create exec sessions on running containers: %w" , define .ErrCtrStateInvalid )
1268+ }
1269+
1270+ session , err := c .createExecSession (config )
1271+ if err != nil {
1272+ return - 1 , err
1273+ }
1274+
1275+ if c .state .ExecSessions == nil {
1276+ c .state .ExecSessions = make (map [string ]* ExecSession )
1277+ }
1278+ c .state .ExecSessions [session .ID ()] = session
1279+ defer delete (c .state .ExecSessions , session .ID ())
1280+
1281+ opts , err := prepareForExec (c , session )
1282+ if err != nil {
1283+ return - 1 , err
1284+ }
1285+ defer func () {
1286+ if err := c .cleanupExecBundle (session .ID ()); err != nil {
1287+ logrus .Errorf ("Container %s light exec session cleanup error: %v" , c .ID (), err )
1288+ }
1289+ }()
1290+
1291+ pid , attachErrChan , err := c .ociRuntime .ExecContainer (c , session .ID (), opts , streams , nil )
1292+ if err != nil {
1293+ // Check if the error is command not found before returning
1294+ if exitCode := define .ExitCode (err ); exitCode == define .ExecErrorCodeNotFound {
1295+ return exitCode , err
1296+ }
1297+ return define .ExecErrorCodeGeneric , err
1298+ }
1299+ session .PID = pid
1300+ session .PIDData = getPidData (pid )
1301+
1302+ if ! c .batched {
1303+ c .lock .Unlock ()
1304+ unlock = false
1305+ }
1306+
1307+ // Handle timeout for health checks
1308+ if timeout > 0 {
1309+ select {
1310+ case err = <- attachErrChan :
1311+ if err != nil {
1312+ return - 1 , fmt .Errorf ("container %s light exec session with pid: %d error: %v" , c .ID (), pid , err )
1313+ }
1314+ case <- time .After (timeout ):
1315+ if err := c .ociRuntime .ExecStopContainer (c , session .ID (), 0 ); err != nil {
1316+ return - 1 , err
1317+ }
1318+ return - 1 , fmt .Errorf ("%v of %s" , define .ErrHealthCheckTimeout , timeout .String ())
1319+ }
1320+ } else {
1321+ // For no-session exec, wait for completion without timeout
1322+ err = <- attachErrChan
1323+ if err != nil && ! errors .Is (err , define .ErrDetach ) {
1324+ // Check if the error is command not found
1325+ if exitCode := define .ExitCode (err ); exitCode == define .ExecErrorCodeNotFound {
1326+ return exitCode , err
1327+ }
1328+ return define .ExecErrorCodeGeneric , err
1329+ }
1330+ }
1331+
1332+ return c .readExecExitCode (session .ID ())
1333+ }
1334+
1335+ // ExecNoSession executes a command in a container without creating a persistent exec session.
1336+ // It skips database operations and minimizes container locking for performance.
1337+ func (c * Container ) ExecNoSession (config * ExecConfig , streams * define.AttachStreams , _ <- chan resize.TerminalSize ) (int , error ) {
1338+ return c .execLightweight (config , streams , 0 )
1339+ }
0 commit comments