Skip to content

Commit d2ce3dc

Browse files
author
Paulo Pereira
committed
Fix load of models that depend on non thread-safe dependencies
A problem was detected when loading TensorFlow models in different threads inside the same JVM. That happened after load a TensorFlow model and then try to import a new TensorFlow model. This was caused by a dependency of TensorFlow (protobuf) that was being reloaded but it already existed in the JVM (through the 1st thread). The workaround was to share the problematic module (Tensorflow) across the sub-interpreters of Python. This is a workaround for the issues with CPython extensions.
1 parent ca9b434 commit d2ce3dc

File tree

10 files changed

+360
-7
lines changed

10 files changed

+360
-7
lines changed

openml-python-common/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,15 @@ export PATH=$ANACONDA_PATH/envs/myenv/bin:$PATH
4040
export LD_LIBRARY_PATH=$ANACONDA_PATH/envs/myenv/lib/python3.6/site-packages/jep:$LD_LIBRARY_PATH
4141
export LD_PRELOAD=$ANACONDA_PATH/envs/myenv/lib/libpython3.6m.so
4242
```
43+
44+
7. If you need to share Python modules across sub-interpreters, you will need to create a "python-packages.xml" file where you define the modules to be shared. By default the provider is already sharing the "numpy" and "tensorflow" modules. This is a workaround for the issues with CPython extensions.
45+
- Remember that this file should be added to the classpath of your program.
46+
47+
```
48+
<?xml version="1.0"?>
49+
<python>
50+
<package>my_package_1</package>
51+
<package>my_package_2</package>
52+
</python>
53+
54+
```

openml-python-common/pom.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@
4242
<groupId>com.feedzai</groupId>
4343
<artifactId>openml-utils</artifactId>
4444
</dependency>
45+
<dependency>
46+
<groupId>com.fasterxml.jackson.core</groupId>
47+
<artifactId>jackson-databind</artifactId>
48+
<scope>provided</scope>
49+
</dependency>
4550
<dependency>
4651
<groupId>com.feedzai</groupId>
4752
<artifactId>openml-utils</artifactId>
4853
<scope>test</scope>
4954
<type>test-jar</type>
5055
</dependency>
51-
<dependency>
52-
<groupId>com.fasterxml.jackson.core</groupId>
53-
<artifactId>jackson-databind</artifactId>
54-
<scope>provided</scope>
55-
</dependency>
5656
</dependencies>
5757
</project>

openml-python-common/src/main/java/com/feedzai/openml/python/jep/instance/JepInstance.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
*/
1616
package com.feedzai.openml.python.jep.instance;
1717

18+
import com.feedzai.openml.python.modules.SharedModulesParser;
19+
import com.google.common.collect.ImmutableSet;
1820
import com.google.common.util.concurrent.Uninterruptibles;
1921
import jep.Jep;
22+
import jep.JepConfig;
2023
import jep.JepException;
2124
import org.slf4j.Logger;
2225
import org.slf4j.LoggerFactory;
2326

27+
import java.util.Set;
2428
import java.util.concurrent.BlockingQueue;
2529
import java.util.concurrent.LinkedBlockingQueue;
2630
import java.util.concurrent.TimeUnit;
@@ -97,8 +101,18 @@ public void stop() {
97101
*/
98102
@Override
99103
public void run() {
100-
101-
try (final Jep jep = new Jep(false)) {
104+
final Set<String> sharedModules = ImmutableSet.<String>builder()
105+
.add("tensorflow")
106+
.add("numpy")
107+
.addAll(new SharedModulesParser().getSharedModules())
108+
.build();
109+
logger.debug("Python modules to be shared: {}", String.join(",", sharedModules.toString()));
110+
111+
final JepConfig jepConfig = new JepConfig()
112+
.addSharedModules(sharedModules.toArray(new String[0]))
113+
.setInteractive(false);
114+
115+
try (final Jep jep = new Jep(jepConfig)) {
102116
while (this.running) {
103117
this.evaluationQueue.take().evaluate(jep);
104118
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (c) 2018 Feedzai
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.feedzai.openml.python.modules;
18+
19+
import com.google.common.collect.ImmutableSet;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
import org.w3c.dom.Document;
23+
import org.w3c.dom.NodeList;
24+
25+
import javax.xml.parsers.DocumentBuilder;
26+
import javax.xml.parsers.DocumentBuilderFactory;
27+
import java.io.InputStream;
28+
import java.util.Set;
29+
30+
/**
31+
* Class responsible for parsing a XML file in order to retrieve the name of the Python modules to be shared across
32+
* sub-interpreters.
33+
*
34+
* @author Paulo Pereira (paulo.pereira@feedzai.com)
35+
* @since 0.1.5
36+
*/
37+
public class SharedModulesParser {
38+
39+
/**
40+
* Logger.
41+
*/
42+
private static final Logger logger = LoggerFactory.getLogger(SharedModulesParser.class);
43+
44+
/**
45+
* Default value for {@link #xmlFile}.
46+
*/
47+
private static final String DEFAULT_XML_FILE = "python-packages.xml";
48+
49+
/**
50+
* The filename of a XML file with the name of the Python modules to be shared across sub-interpreters.
51+
*/
52+
private final String xmlFile;
53+
54+
/**
55+
* Constructor.
56+
*
57+
* @param xmlFileName The name of a XML file.
58+
*/
59+
public SharedModulesParser(final String xmlFileName) {
60+
this.xmlFile = xmlFileName;
61+
}
62+
63+
/**
64+
* Constructor.
65+
*/
66+
public SharedModulesParser() {
67+
this(DEFAULT_XML_FILE);
68+
}
69+
70+
/**
71+
* Gets the {@link InputStream} of {@link #xmlFile} that exists in the current classpath.
72+
*
73+
* @return The {@link InputStream} of {@link #xmlFile}.
74+
*/
75+
private InputStream getXMLInputStream() {
76+
final ClassLoader classLoader = getClass().getClassLoader();
77+
return classLoader.getResourceAsStream(this.xmlFile);
78+
}
79+
80+
/**
81+
* Parses the {@link #xmlFile} to retrieve the name of the Python modules to be shared across sub-interpreters.
82+
*
83+
* @return A {@link Set} with the name of the Python modules to be shared across sub-interpreters.
84+
* @throws Exception If there is an error while parsing {@link #xmlFile}.
85+
*/
86+
private Set<String> parseXMLFile() throws Exception {
87+
final ImmutableSet.Builder<String> sharedModulesBuilder = ImmutableSet.builder();
88+
final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
89+
final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
90+
91+
try (final InputStream xmlFile = getXMLInputStream()) {
92+
final Document doc = dBuilder.parse(xmlFile);
93+
doc.getDocumentElement().normalize();
94+
95+
final NodeList nList = doc.getElementsByTagName("package");
96+
for (int i = 0; i < nList.getLength(); i++) {
97+
sharedModulesBuilder.add(nList.item(i).getFirstChild().getNodeValue());
98+
}
99+
}
100+
return sharedModulesBuilder.build();
101+
}
102+
103+
/**
104+
* Retrieves a {@link Set} with the name of the Python modules to be shared across sub-interpreters.
105+
*
106+
* @return The name of the Python modules to be shared across sub-interpreters.
107+
*/
108+
public Set<String> getSharedModules() {
109+
Set<String> sharedModules = ImmutableSet.of();
110+
try {
111+
sharedModules = parseXMLFile();
112+
} catch (final Exception e) {
113+
logger.warn("Problem while getting the XML file with the Python modules to be shared.", e);
114+
}
115+
return sharedModules;
116+
}
117+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) 2018 Feedzai
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* This package contains logic for parsing XML files to retrieve the name of the Python modules to be shared across
19+
* sub-interpreters.
20+
*
21+
* @since 0.1.5
22+
*/
23+
package com.feedzai.openml.python.modules;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2018 Feedzai
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.feedzai.openml.python.jep.instance;
18+
19+
import org.junit.After;
20+
import org.junit.Before;
21+
import org.junit.Test;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
/**
26+
* Contains the tests for the wrapper for the Jep object.
27+
*
28+
* @author Paulo Pereira (paulo.pereira@feedzai.com)
29+
* @since 0.1.5
30+
*/
31+
public class JepInstanceTest {
32+
33+
/**
34+
* The wrapper for the Jep object used in the tests.
35+
*/
36+
private JepInstance jepInstance;
37+
38+
/**
39+
* Initializes an instance of {@link JepInstance}.
40+
*/
41+
@Before
42+
public void setUp() {
43+
this.jepInstance = new JepInstance();
44+
this.jepInstance.start();
45+
}
46+
47+
/**
48+
* Tears downs the instance of {@link JepInstance}.
49+
*/
50+
@After
51+
public void tearDown() {
52+
this.jepInstance.stop();
53+
}
54+
55+
/**
56+
* Tests the submission of evaluations on a {@link JepInstance}.
57+
*
58+
* @throws Exception If there is a problem while getting the result.
59+
*/
60+
@Test
61+
public void submitEvaluationTest() throws Exception {
62+
final String result = this.jepInstance.submitEvaluation((jep) -> jep.getValue("1 + 2")).get().toString();
63+
assertThat(result)
64+
.as("The result of the evaluation")
65+
.isEqualTo("3");
66+
}
67+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2018 Feedzai
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* This package contains the unit-tests for {@link com.feedzai.openml.python.jep.instance}.
19+
*
20+
* @since 0.1.5
21+
*/
22+
package com.feedzai.openml.python.jep.instance;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (c) 2018 Feedzai
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.feedzai.openml.python.modules;
18+
19+
import org.assertj.core.util.Files;
20+
import org.junit.Test;
21+
22+
import java.io.File;
23+
import java.util.Set;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
/**
28+
* Tests the retrieving of the modules to be shared across sub-interpreters from a XML file.
29+
*
30+
* @author Paulo Pereira (paulo.pereira@feedzai.com)
31+
* @since 0.1.5
32+
*/
33+
public class SharedModulesParserTest {
34+
35+
/**
36+
* Tests the retrieving of the shared modules from a valid XML file with name of two modules to be shared.
37+
*/
38+
@Test
39+
public void validXMLFileTest() {
40+
final Set<String> sharedPythonPackages = new SharedModulesParser().getSharedModules();
41+
assertThat(sharedPythonPackages)
42+
.as("Set of shared modules.")
43+
.hasSize(2)
44+
.contains("my_package_1", "my_package_2");
45+
}
46+
47+
/**
48+
* Tests the retrieving of the shared modules from an empty file.
49+
*/
50+
@Test
51+
public void emptyFileTest() {
52+
final File file = Files.newTemporaryFile();
53+
file.deleteOnExit();
54+
55+
final SharedModulesParser sharedModulesParser = new SharedModulesParser(file.getAbsolutePath());
56+
assertThat(sharedModulesParser.getSharedModules())
57+
.as("Set of shared modules.")
58+
.hasSize(0);
59+
}
60+
61+
/**
62+
* Tests the retrieving of the shared modules from a non existing file.
63+
*/
64+
@Test
65+
public void nonExistingFileTest() {
66+
final SharedModulesParser sharedModulesParser = new SharedModulesParser("non_existing_file");
67+
assertThat(sharedModulesParser.getSharedModules())
68+
.as("Set of shared modules.")
69+
.hasSize(0);
70+
}
71+
}

0 commit comments

Comments
 (0)