1414import de .tum .cit .aet .hephaestus .agent .sandbox .docker .interactive .InteractiveSandboxRegistry ;
1515import de .tum .cit .aet .hephaestus .agent .sandbox .docker .interactive .StdinWriteWatchdog ;
1616import de .tum .cit .aet .hephaestus .agent .sandbox .spi .InteractiveSandboxService ;
17+ import de .tum .cit .aet .hephaestus .agent .sandbox .spi .ResourceLimits ;
1718import de .tum .cit .aet .hephaestus .agent .sandbox .spi .SandboxException ;
1819import de .tum .cit .aet .hephaestus .agent .sandbox .spi .SandboxManager ;
1920import de .tum .cit .aet .hephaestus .core .runtime .RuntimeRole ;
2728import java .util .concurrent .ThreadPoolExecutor ;
2829import org .slf4j .Logger ;
2930import org .slf4j .LoggerFactory ;
31+ import org .springframework .beans .factory .annotation .Qualifier ;
3032import org .springframework .beans .factory .annotation .Value ;
3133import org .springframework .boot .autoconfigure .condition .ConditionalOnClass ;
3234import org .springframework .boot .autoconfigure .condition .ConditionalOnProperty ;
@@ -55,16 +57,55 @@ public class DockerSandboxConfiguration {
5557
5658 private static final Logger log = LoggerFactory .getLogger (DockerSandboxConfiguration .class );
5759
58- /** Connections per container: create/start, wait, logs/ copy. */
59- private static final int CONNECTIONS_PER_CONTAINER = 3 ;
60+ /** RPC connections per container: create/start, logs, and a copy-out lease held while it is read . */
61+ private static final int RPC_CONNECTIONS_PER_CONTAINER = 3 ;
6062
6163 private static final Duration HTTP_CONNECTION_TIMEOUT = Duration .ofSeconds (5 );
6264
63- /** docker wait/logs can block for the full container lifetime. */
64- private static final Duration HTTP_RESPONSE_TIMEOUT = Duration .ofMinutes (30 );
65+ /**
66+ * Idle timeout, not a deadline: Apache installs responseTimeout as the socket timeout for the
67+ * whole exchange, so it bounds the gap between reads and a call that keeps producing bytes runs
68+ * as long as it likes. Every RPC call carries its own budget, so this only has to reclaim the
69+ * connection when the daemon goes silent — generous enough that a slow image layer or a large
70+ * archive upload never trips it.
71+ */
72+ static final Duration HTTP_RESPONSE_TIMEOUT = Duration .ofMinutes (30 );
73+
74+ /**
75+ * `docker wait` sends nothing until the container exits, so for it the idle timeout is a ceiling
76+ * on the container's life. Sitting above {@link ResourceLimits#MAX_RUNTIME} it can never cut a
77+ * legitimate wait short, while still reclaiming a connection the daemon has abandoned —
78+ * docker-java's reader thread only ever gets an interrupt, which a blocking read ignores.
79+ */
80+ static final Duration HTTP_STREAMING_RESPONSE_TIMEOUT = ResourceLimits .MAX_RUNTIME .plusMinutes (10 );
81+
82+ /** Calls whose response body is the stream. One wait per container, and nothing else. */
83+ @ Bean (name = "dockerStreamingClient" , destroyMethod = "close" )
84+ public DockerClient dockerStreamingClient (SandboxProperties properties ) {
85+ return buildClient (
86+ properties ,
87+ HTTP_STREAMING_RESPONSE_TIMEOUT ,
88+ properties .maxConcurrentContainers (),
89+ "streaming"
90+ );
91+ }
6592
6693 @ Bean (destroyMethod = "close" )
6794 public DockerClient dockerClient (SandboxProperties properties ) {
95+ return buildClient (
96+ properties ,
97+ HTTP_RESPONSE_TIMEOUT ,
98+ properties .maxConcurrentContainers () * RPC_CONNECTIONS_PER_CONTAINER ,
99+ "rpc"
100+ );
101+ }
102+
103+ private DockerClient buildClient (
104+ SandboxProperties properties ,
105+ Duration responseTimeout ,
106+ int maxConnections ,
107+ String kind
108+ ) {
68109 var configBuilder = DefaultDockerClientConfig .createDefaultConfigBuilder ()
69110 .withDockerHost (properties .dockerHost ())
70111 .withDockerTlsVerify (properties .tlsVerify ());
@@ -78,24 +119,30 @@ public DockerClient dockerClient(SandboxProperties properties) {
78119 var httpClient = new ApacheDockerHttpClient .Builder ()
79120 .dockerHost (config .getDockerHost ())
80121 .sslConfig (config .getSSLConfig ())
81- .maxConnections (properties . maxConcurrentContainers () * CONNECTIONS_PER_CONTAINER )
122+ .maxConnections (maxConnections )
82123 .connectionTimeout (HTTP_CONNECTION_TIMEOUT )
83- .responseTimeout (HTTP_RESPONSE_TIMEOUT )
124+ .responseTimeout (responseTimeout )
84125 .build ();
85126
86127 DockerClient client = DockerClientImpl .getInstance (config , httpClient );
87128 log .info (
88- "Docker sandbox client configured: host={}, tlsVerify={}" ,
129+ "Docker sandbox client configured: kind={}, host={}, tlsVerify={}, responseTimeout={}, maxConnections={}" ,
130+ kind ,
89131 properties .dockerHost (),
90- properties .tlsVerify ()
132+ properties .tlsVerify (),
133+ responseTimeout ,
134+ maxConnections
91135 );
92136
93137 return client ;
94138 }
95139
96140 @ Bean
97- public DockerClientOperations dockerClientOperations (DockerClient dockerClient ) {
98- return new DockerClientOperations (dockerClient );
141+ public DockerClientOperations dockerClientOperations (
142+ DockerClient dockerClient ,
143+ @ Qualifier ("dockerStreamingClient" ) DockerClient dockerStreamingClient
144+ ) {
145+ return new DockerClientOperations (dockerClient , dockerStreamingClient );
99146 }
100147
101148 @ Bean
0 commit comments