4141import java .util .Optional ;
4242import java .util .concurrent .CompletableFuture ;
4343import java .util .concurrent .TimeUnit ;
44+ import java .util .concurrent .locks .Lock ;
45+ import java .util .concurrent .locks .ReentrantLock ;
4446
4547class ContainerRunner implements CallableTask {
4648
47- private static final DefaultDockerClientConfig DEFAULT_CONFIG =
48- DefaultDockerClientConfig .createDefaultConfigBuilder ().build ();
49-
50- private static class DockerClientHolder {
51- private static final DockerClient dockerClient =
52- DockerClientImpl .getInstance (
53- DEFAULT_CONFIG ,
54- new ApacheDockerHttpClient .Builder ()
55- .dockerHost (DEFAULT_CONFIG .getDockerHost ())
56- .build ());
57- }
58-
5949 private final Collection <ContainerPropertySetter > propertySetters ;
6050 private final Optional <WorkflowValueResolver <Duration >> timeout ;
6151 private final ContainerCleanupPolicy policy ;
@@ -82,7 +72,8 @@ public CompletableFuture<WorkflowModel> apply(
8272
8373 private WorkflowModel startSync (
8474 WorkflowContext workflowContext , TaskContext taskContext , WorkflowModel input ) {
85- Integer exit = executeContainer (workflowContext , taskContext , input );
75+ Integer exit =
76+ executeContainer (workflowContext , taskContext , input , DockerClientHolder .client ());
8677 if (exit == null || exit == 0 ) {
8778 return input ;
8879 } else {
@@ -91,14 +82,20 @@ private WorkflowModel startSync(
9182 }
9283
9384 private Integer executeContainer (
94- WorkflowContext workflowContext , TaskContext taskContext , WorkflowModel input ) {
85+ WorkflowContext workflowContext ,
86+ TaskContext taskContext ,
87+ WorkflowModel input ,
88+ DockerClient dockerClient ) {
9589 try {
96- pullImageIfNeeded (containerImage );
97- CreateContainerCmd containerCommand =
98- DockerClientHolder .dockerClient .createContainerCmd (containerImage );
90+ pullImageIfNeeded (containerImage , dockerClient );
91+ CreateContainerCmd containerCommand = dockerClient .createContainerCmd (containerImage );
9992 propertySetters .forEach (p -> p .accept (containerCommand , workflowContext , taskContext , input ));
10093 return waitAccordingToLifetime (
101- createAndStartContainer (containerCommand ), workflowContext , taskContext , input );
94+ createAndStartContainer (containerCommand , dockerClient ),
95+ workflowContext ,
96+ taskContext ,
97+ input ,
98+ dockerClient );
10299 } catch (InterruptedException ie ) {
103100 Thread .currentThread ().interrupt ();
104101 throw failed ("Container execution failed with exit code " + ie .getMessage ());
@@ -107,43 +104,46 @@ private Integer executeContainer(
107104 }
108105 }
109106
110- private void pullImageIfNeeded (String imageRef ) throws InterruptedException {
107+ private void pullImageIfNeeded (String imageRef , DockerClient dockerClient )
108+ throws InterruptedException {
111109 NameParser .ReposTag rt = NameParser .parseRepositoryTag (imageRef );
112- DockerClientHolder . dockerClient
110+ dockerClient
113111 .pullImageCmd (NameParser .resolveRepositoryName (imageRef ).reposName )
114112 .withTag (WorkflowUtils .isValid (rt .tag ) ? rt .tag : "latest" )
115113 .start ()
116114 .awaitCompletion ();
117115 }
118116
119- private String createAndStartContainer (CreateContainerCmd containerCommand ) {
117+ private String createAndStartContainer (
118+ CreateContainerCmd containerCommand , DockerClient dockerClient ) {
120119 CreateContainerResponse resp = containerCommand .exec ();
121120 String id = resp .getId ();
122121 if (!isValid (id )) {
123122 throw new IllegalStateException ("Container creation failed: empty ID" );
124123 }
125- DockerClientHolder . dockerClient .startContainerCmd (id ).exec ();
124+ dockerClient .startContainerCmd (id ).exec ();
126125 return id ;
127126 }
128127
129128 private Integer waitAccordingToLifetime (
130- String id , WorkflowContext workflowContext , TaskContext taskContext , WorkflowModel input )
129+ String id ,
130+ WorkflowContext workflowContext ,
131+ TaskContext taskContext ,
132+ WorkflowModel input ,
133+ DockerClient dockerClient )
131134 throws IOException {
132- try (var cb =
133- DockerClientHolder .dockerClient
134- .waitContainerCmd (id )
135- .exec (new WaitContainerResultCallback ())) {
135+ try (var cb = dockerClient .waitContainerCmd (id ).exec (new WaitContainerResultCallback ())) {
136136 if (policy == ContainerCleanupPolicy .EVENTUALLY ) {
137137 Duration timeout =
138138 this .timeout
139139 .map (t -> t .apply (workflowContext , taskContext , input ))
140140 .orElse (Duration .ZERO );
141141 try {
142142 Integer exit = cb .awaitStatusCode (timeout .toMillis (), TimeUnit .MILLISECONDS );
143- safeStop (id );
143+ safeStop (id , dockerClient );
144144 return exit ;
145145 } catch (DockerClientException timeoutOrOther ) {
146- safeStop (id );
146+ safeStop (id , dockerClient );
147147 }
148148 } else {
149149 return cb .awaitStatusCode ();
@@ -154,47 +154,41 @@ private Integer waitAccordingToLifetime(
154154 return 0 ;
155155 }
156156
157- private boolean isRunning (String id ) {
157+ private boolean isRunning (String id , DockerClient dockerClient ) {
158158 try {
159- var st = DockerClientHolder . dockerClient .inspectContainerCmd (id ).exec ().getState ();
159+ var st = dockerClient .inspectContainerCmd (id ).exec ().getState ();
160160 return st != null && Boolean .TRUE .equals (st .getRunning ());
161161 } catch (Exception e ) {
162162 return false ; // must be already removed
163163 }
164164 }
165165
166- private void safeStop (String id ) {
167- if (isRunning (id )) {
168- safeStop (id , Duration .ofSeconds (10 ));
169- try (var cb2 =
170- DockerClientHolder .dockerClient
171- .waitContainerCmd (id )
172- .exec (new WaitContainerResultCallback ())) {
166+ private void safeStop (String id , DockerClient dockerClient ) {
167+ if (isRunning (id , dockerClient )) {
168+ safeStop (id , Duration .ofSeconds (10 ), dockerClient );
169+ try (var cb2 = dockerClient .waitContainerCmd (id ).exec (new WaitContainerResultCallback ())) {
173170 cb2 .awaitStatusCode ();
174- safeRemove (id );
171+ safeRemove (id , dockerClient );
175172 } catch (Exception ignore ) {
176173 // we can ignore this
177174 }
178175 } else {
179- safeRemove (id );
176+ safeRemove (id , dockerClient );
180177 }
181178 }
182179
183- private void safeStop (String id , Duration timeout ) {
180+ private void safeStop (String id , Duration timeout , DockerClient dockerClient ) {
184181 try {
185- DockerClientHolder .dockerClient
186- .stopContainerCmd (id )
187- .withTimeout ((int ) Math .max (1 , timeout .toSeconds ()))
188- .exec ();
182+ dockerClient .stopContainerCmd (id ).withTimeout ((int ) Math .max (1 , timeout .toSeconds ())).exec ();
189183 } catch (Exception ignore ) {
190184 // we can ignore this
191185 }
192186 }
193187
194188 // must be removed because of withAutoRemove(true), but just in case
195- private void safeRemove (String id ) {
189+ private void safeRemove (String id , DockerClient dockerClient ) {
196190 try {
197- DockerClientHolder . dockerClient .removeContainerCmd (id ).withForce (true ).exec ();
191+ dockerClient .removeContainerCmd (id ).withForce (true ).exec ();
198192 } catch (Exception ignore ) {
199193 // we can ignore this
200194 }
@@ -217,4 +211,30 @@ private static RuntimeException mapExitCode(int exit) {
217211 private static RuntimeException failed (String message ) {
218212 return new RuntimeException (message );
219213 }
214+
215+ private static class DockerClientHolder {
216+ private static volatile DockerClient client ;
217+ private static final Lock dockerLock = new ReentrantLock ();
218+
219+ private static DockerClient client () {
220+ if (client == null ) {
221+ dockerLock .lock ();
222+ try {
223+ if (client == null ) {
224+ DefaultDockerClientConfig config =
225+ DefaultDockerClientConfig .createDefaultConfigBuilder ().build ();
226+ client =
227+ DockerClientImpl .getInstance (
228+ config ,
229+ new ApacheDockerHttpClient .Builder ()
230+ .dockerHost (config .getDockerHost ())
231+ .build ());
232+ }
233+ } finally {
234+ dockerLock .unlock ();
235+ }
236+ }
237+ return client ;
238+ }
239+ }
220240}
0 commit comments