Skip to content

Commit db9da85

Browse files
author
Kindrat
committed
#266 Concurrent DockerCmdExecFactory.getDefaultDockerCmdExecFactory fails on reload
- test & fix
1 parent 9366a9d commit db9da85

File tree

2 files changed

+116
-11
lines changed

2 files changed

+116
-11
lines changed

src/main/java/com/github/dockerjava/core/DockerClientBuilder.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
package com.github.dockerjava.core;
22

3-
import java.util.ServiceLoader;
4-
53
import com.github.dockerjava.api.DockerClient;
64
import com.github.dockerjava.api.command.DockerCmdExecFactory;
75
import com.github.dockerjava.core.DockerClientConfig.DockerClientConfigBuilder;
86

7+
import java.util.Iterator;
8+
import java.util.ServiceLoader;
9+
910
public class DockerClientBuilder {
1011

12+
private static Class<? extends DockerCmdExecFactory> factoryClass;
1113
private static ServiceLoader<DockerCmdExecFactory> serviceLoader = ServiceLoader.load(DockerCmdExecFactory.class);
1214

15+
static {
16+
serviceLoader.reload();
17+
Iterator<DockerCmdExecFactory> iterator = serviceLoader.iterator();
18+
if (!iterator.hasNext()) {
19+
throw new RuntimeException("Fatal: Can't find any implementation of '"
20+
+ DockerCmdExecFactory.class.getName() + "' in the current classpath.");
21+
}
22+
23+
factoryClass = iterator.next().getClass();
24+
}
25+
1326
private DockerClientImpl dockerClient = null;
1427

1528
private DockerCmdExecFactory dockerCmdExecFactory = null;
@@ -35,15 +48,14 @@ public static DockerClientBuilder getInstance(String serverUrl) {
3548
}
3649

3750
public static DockerCmdExecFactory getDefaultDockerCmdExecFactory() {
38-
// clearing the cache is needed because otherwise we will get
39-
// the same DockerCmdExecFactory instance each time
40-
serviceLoader.reload();
41-
if (!serviceLoader.iterator().hasNext()) {
42-
throw new RuntimeException("Fatal: Can't find any implementation of '"
43-
+ DockerCmdExecFactory.class.getName() + "' in the current classpath.");
44-
}
45-
46-
return serviceLoader.iterator().next();
51+
try
52+
{
53+
return factoryClass.newInstance();
54+
}
55+
catch (InstantiationException | IllegalAccessException e)
56+
{
57+
throw new RuntimeException("Fatal: Can't create new instance of '" + factoryClass.getName() + "'");
58+
}
4759
}
4860

4961
public DockerClientBuilder withDockerCmdExecFactory(DockerCmdExecFactory dockerCmdExecFactory) {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.github.dockerjava.core;
2+
3+
import com.github.dockerjava.api.command.DockerCmdExecFactory;
4+
import org.testng.annotations.Test;
5+
6+
import java.util.*;
7+
8+
import static org.testng.Assert.assertEquals;
9+
import static org.testng.Assert.assertFalse;
10+
import static org.testng.Assert.assertNotNull;
11+
12+
public class DockerClientBuilderTest
13+
{
14+
// Amount of instances created in test
15+
private static final int AMOUNT = 100;
16+
17+
@Test
18+
public void testConcurrentClientBuilding() throws Exception
19+
{
20+
// we use it to check instance uniqueness
21+
final Set<DockerCmdExecFactory> instances = Collections.synchronizedSet(new HashSet<DockerCmdExecFactory>());
22+
23+
Runnable runnable = new Runnable()
24+
{
25+
@Override
26+
public void run()
27+
{
28+
DockerCmdExecFactory factory = DockerClientBuilder.getDefaultDockerCmdExecFactory();
29+
// factory created
30+
assertNotNull(factory);
31+
// and is unique
32+
assertFalse(instances.contains(factory));
33+
instances.add(factory);
34+
}
35+
};
36+
37+
parallel(AMOUNT, runnable);
38+
// set contains all required unique instances
39+
assertEquals(instances.size(), AMOUNT);
40+
}
41+
42+
public static void parallel(int threads, final Runnable task) throws Exception
43+
{
44+
final ExceptionListener exceptionListener = new ExceptionListener();
45+
Runnable runnable = new Runnable()
46+
{
47+
@Override
48+
public void run()
49+
{
50+
try
51+
{
52+
task.run();
53+
}
54+
catch (Throwable e)
55+
{
56+
exceptionListener.onException(e);
57+
}
58+
}
59+
};
60+
61+
List<Thread> threadList = new ArrayList<>(threads);
62+
for (int i = 0; i < threads; i++)
63+
{
64+
Thread thread = new Thread(runnable);
65+
thread.start();
66+
threadList.add(thread);
67+
}
68+
for (Thread thread : threadList)
69+
{
70+
thread.join();
71+
}
72+
Throwable exception = exceptionListener.getException();
73+
if (exception != null)
74+
{
75+
throw new RuntimeException(exception);
76+
}
77+
}
78+
79+
private static class ExceptionListener
80+
{
81+
private Throwable exception;
82+
83+
private synchronized void onException(Throwable e)
84+
{
85+
exception = e;
86+
}
87+
88+
private synchronized Throwable getException()
89+
{
90+
return exception;
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)