Skip to content

Commit 7c0ea2e

Browse files
authored
[bugfix] [Zeta] Fix the problem of class loader not releasing when using REST API to submit jobs (#6477)
1 parent bccf9a1 commit 7c0ea2e

File tree

17 files changed

+168
-33
lines changed

17 files changed

+168
-33
lines changed

Diff for: seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/java/org/apache/seatunnel/engine/e2e/classloader/ClassLoaderITBase.java

+116-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import org.apache.seatunnel.common.utils.FileUtils;
2121
import org.apache.seatunnel.e2e.common.util.ContainerUtil;
2222
import org.apache.seatunnel.engine.e2e.SeaTunnelContainer;
23+
import org.apache.seatunnel.engine.server.rest.RestConstant;
2324

25+
import org.awaitility.Awaitility;
26+
import org.junit.jupiter.api.AfterEach;
2427
import org.junit.jupiter.api.Assertions;
25-
import org.junit.jupiter.api.BeforeAll;
28+
import org.junit.jupiter.api.BeforeEach;
2629
import org.junit.jupiter.api.Test;
2730
import org.testcontainers.containers.Container;
2831
import org.testcontainers.containers.GenericContainer;
@@ -31,22 +34,36 @@
3134
import org.testcontainers.utility.DockerLoggerFactory;
3235
import org.testcontainers.utility.MountableFile;
3336

37+
import io.restassured.response.Response;
38+
3439
import java.io.File;
3540
import java.io.IOException;
3641
import java.net.URL;
42+
import java.nio.file.Path;
3743
import java.nio.file.Paths;
3844
import java.util.Collections;
3945
import java.util.List;
4046
import java.util.Map;
47+
import java.util.concurrent.TimeUnit;
4148

49+
import static io.restassured.RestAssured.given;
4250
import static org.apache.seatunnel.e2e.common.util.ContainerUtil.PROJECT_ROOT_PATH;
51+
import static org.hamcrest.Matchers.equalTo;
4352

4453
public abstract class ClassLoaderITBase extends SeaTunnelContainer {
4554

4655
private static final String CONF_FILE = "/classloader/fake_to_inmemory.conf";
4756

57+
private static final String http = "http://";
58+
59+
private static final String colon = ":";
60+
4861
abstract boolean cacheMode();
4962

63+
private static final Path config = Paths.get(SEATUNNEL_HOME, "config");
64+
65+
private static final Path binPath = Paths.get(SEATUNNEL_HOME, "bin", SERVER_SHELL);
66+
5067
abstract String seatunnelConfigFileName();
5168

5269
@Test
@@ -65,6 +82,96 @@ public void testFakeSourceToInMemorySink() throws IOException, InterruptedExcept
6582
}
6683
}
6784

85+
@Test
86+
public void testFakeSourceToInMemorySinkForRestApi() throws IOException, InterruptedException {
87+
LOG.info("test classloader with cache mode: {}", cacheMode());
88+
ContainerUtil.copyConnectorJarToContainer(
89+
server,
90+
CONF_FILE,
91+
getConnectorModulePath(),
92+
getConnectorNamePrefix(),
93+
getConnectorType(),
94+
SEATUNNEL_HOME);
95+
Awaitility.await()
96+
.atMost(2, TimeUnit.MINUTES)
97+
.untilAsserted(
98+
() -> {
99+
Response response =
100+
given().get(
101+
http
102+
+ server.getHost()
103+
+ colon
104+
+ server.getFirstMappedPort()
105+
+ "/hazelcast/rest/cluster");
106+
response.then().statusCode(200);
107+
Thread.sleep(10000);
108+
Assertions.assertEquals(
109+
1, response.jsonPath().getList("members").size());
110+
});
111+
for (int i = 0; i < 10; i++) {
112+
// load in memory sink which already leak thread with classloader
113+
given().body(
114+
"{\n"
115+
+ "\t\"env\": {\n"
116+
+ "\t\t\"parallelism\": 10,\n"
117+
+ "\t\t\"job.mode\": \"BATCH\"\n"
118+
+ "\t},\n"
119+
+ "\t\"source\": [\n"
120+
+ "\t\t{\n"
121+
+ "\t\t\t\"plugin_name\": \"FakeSource\",\n"
122+
+ "\t\t\t\"result_table_name\": \"fake\",\n"
123+
+ "\t\t\t\"parallelism\": 10,\n"
124+
+ "\t\t\t\"schema\": {\n"
125+
+ "\t\t\t\t\"fields\": {\n"
126+
+ "\t\t\t\t\t\"name\": \"string\",\n"
127+
+ "\t\t\t\t\t\"age\": \"int\",\n"
128+
+ "\t\t\t\t\t\"score\": \"double\"\n"
129+
+ "\t\t\t\t}\n"
130+
+ "\t\t\t}\n"
131+
+ "\t\t}\n"
132+
+ "\t],\n"
133+
+ "\t\"transform\": [],\n"
134+
+ "\t\"sink\": [\n"
135+
+ "\t\t{\n"
136+
+ "\t\t\t\"plugin_name\": \"InMemory\",\n"
137+
+ "\t\t\t\"source_table_name\": \"fake\"\n"
138+
+ "\t\t}\n"
139+
+ "\t]\n"
140+
+ "}")
141+
.header("Content-Type", "application/json; charset=utf-8")
142+
.post(
143+
http
144+
+ server.getHost()
145+
+ colon
146+
+ server.getFirstMappedPort()
147+
+ RestConstant.SUBMIT_JOB_URL)
148+
.then()
149+
.statusCode(200);
150+
151+
Awaitility.await()
152+
.atMost(2, TimeUnit.MINUTES)
153+
.untilAsserted(
154+
() ->
155+
given().get(
156+
http
157+
+ server.getHost()
158+
+ colon
159+
+ server.getFirstMappedPort()
160+
+ RestConstant.FINISHED_JOBS_INFO
161+
+ "/FINISHED")
162+
.then()
163+
.statusCode(200)
164+
.body("[0].jobStatus", equalTo("FINISHED")));
165+
Thread.sleep(5000);
166+
Assertions.assertTrue(containsDaemonThread());
167+
if (cacheMode()) {
168+
Assertions.assertEquals(3, getClassLoaderCount());
169+
} else {
170+
Assertions.assertEquals(2 + i, getClassLoaderCount());
171+
}
172+
}
173+
}
174+
68175
private int getClassLoaderCount() throws IOException, InterruptedException {
69176
Map<String, Integer> objects = ContainerUtil.getJVMLiveObject(server);
70177
String className =
@@ -79,7 +186,7 @@ private boolean containsDaemonThread() throws IOException, InterruptedException
79186
}
80187

81188
@Override
82-
@BeforeAll
189+
@BeforeEach
83190
public void startUp() throws Exception {
84191
server =
85192
new GenericContainer<>(getDockerImage())
@@ -96,7 +203,7 @@ public void startUp() throws Exception {
96203
"seatunnel-engine:" + JDK_DOCKER_IMAGE)))
97204
.waitingFor(Wait.forListeningPort());
98205
copySeaTunnelStarterToContainer(server);
99-
server.setPortBindings(Collections.singletonList("5801:5801"));
206+
server.setExposedPorts(Collections.singletonList(5801));
100207

101208
server.withCopyFileToContainer(
102209
MountableFile.forHostPath(
@@ -148,4 +255,10 @@ public void startUp() throws Exception {
148255
+ "/seatunnel-e2e/seatunnel-engine-e2e/connector-seatunnel-e2e-base/src/test/resources/classloader/plugin-mapping.properties"),
149256
Paths.get(SEATUNNEL_HOME, "connectors", "plugin-mapping.properties").toString());
150257
}
258+
259+
@AfterEach
260+
@Override
261+
public void tearDown() throws Exception {
262+
super.tearDown();
263+
}
151264
}

Diff for: seatunnel-engine/seatunnel-engine-client/src/main/java/org/apache/seatunnel/engine/client/job/ClientJobExecutionEnvironment.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ protected MultipleTableJobConfigParser getJobConfigParser() {
8585

8686
@Override
8787
protected LogicalDag getLogicalDag() {
88-
ImmutablePair<List<Action>, Set<URL>> immutablePair = getJobConfigParser().parse();
88+
ImmutablePair<List<Action>, Set<URL>> immutablePair = getJobConfigParser().parse(null);
8989
actions.addAll(immutablePair.getLeft());
9090
// Enable upload connector jar package to engine server, automatically upload connector Jar
9191
// packages and dependent third-party Jar packages to the server before job execution.

Diff for: seatunnel-engine/seatunnel-engine-client/src/test/java/org/apache/seatunnel/engine/client/LogicalDagGeneratorTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void testLogicalGenerator() {
4949

5050
IdGenerator idGenerator = new IdGenerator();
5151
ImmutablePair<List<Action>, Set<URL>> immutablePair =
52-
new MultipleTableJobConfigParser(filePath, idGenerator, jobConfig).parse();
52+
new MultipleTableJobConfigParser(filePath, idGenerator, jobConfig).parse(null);
5353

5454
LogicalDagGenerator logicalDagGenerator =
5555
new LogicalDagGenerator(immutablePair.getLeft(), jobConfig, idGenerator);

Diff for: seatunnel-engine/seatunnel-engine-client/src/test/java/org/apache/seatunnel/engine/client/MultipleTableJobConfigParserTest.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void testSimpleJobParse() {
5151
jobConfig.setJobContext(new JobContext());
5252
MultipleTableJobConfigParser jobConfigParser =
5353
new MultipleTableJobConfigParser(filePath, new IdGenerator(), jobConfig);
54-
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse();
54+
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse(null);
5555
List<Action> actions = parse.getLeft();
5656
Assertions.assertEquals(1, actions.size());
5757
Assertions.assertEquals("Sink[0]-LocalFile-MultiTableSink", actions.get(0).getName());
@@ -71,7 +71,7 @@ public void testComplexJobParse() {
7171
jobConfig.setJobContext(new JobContext());
7272
MultipleTableJobConfigParser jobConfigParser =
7373
new MultipleTableJobConfigParser(filePath, new IdGenerator(), jobConfig);
74-
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse();
74+
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse(null);
7575
List<Action> actions = parse.getLeft();
7676
Assertions.assertEquals(1, actions.size());
7777

@@ -102,7 +102,7 @@ public void testMultipleSinkName() {
102102
jobConfig.setJobContext(new JobContext());
103103
MultipleTableJobConfigParser jobConfigParser =
104104
new MultipleTableJobConfigParser(filePath, new IdGenerator(), jobConfig);
105-
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse();
105+
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse(null);
106106
List<Action> actions = parse.getLeft();
107107
Assertions.assertEquals(2, actions.size());
108108

@@ -122,7 +122,7 @@ public void testMultipleTableSourceWithMultiTableSinkParse() throws IOException
122122
Config config = ConfigBuilder.of(Paths.get(filePath));
123123
MultipleTableJobConfigParser jobConfigParser =
124124
new MultipleTableJobConfigParser(config, new IdGenerator(), jobConfig);
125-
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse();
125+
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse(null);
126126
List<Action> actions = parse.getLeft();
127127
Assertions.assertEquals(1, actions.size());
128128
Assertions.assertEquals("Sink[0]-console-MultiTableSink", actions.get(0).getName());
@@ -142,7 +142,7 @@ public void testDuplicatedTransformInOnePipeline() {
142142
Config config = ConfigBuilder.of(Paths.get(filePath));
143143
MultipleTableJobConfigParser jobConfigParser =
144144
new MultipleTableJobConfigParser(config, new IdGenerator(), jobConfig);
145-
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse();
145+
ImmutablePair<List<Action>, Set<URL>> parse = jobConfigParser.parse(null);
146146
List<Action> actions = parse.getLeft();
147147
Assertions.assertEquals("Transform[0]-sql", actions.get(0).getUpstream().get(0).getName());
148148
Assertions.assertEquals("Transform[1]-sql", actions.get(1).getUpstream().get(0).getName());
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
package org.apache.seatunnel.engine.server.service.classloader;
18+
package org.apache.seatunnel.engine.core.classloader;
1919

2020
import java.net.URL;
2121
import java.util.Collection;
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
package org.apache.seatunnel.engine.server.service.classloader;
18+
package org.apache.seatunnel.engine.core.classloader;
1919

2020
import org.apache.seatunnel.engine.common.loader.SeaTunnelChildFirstClassLoader;
2121

Diff for: seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/parse/MultipleTableJobConfigParser.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
import org.apache.seatunnel.core.starter.utils.ConfigBuilder;
4747
import org.apache.seatunnel.engine.common.config.JobConfig;
4848
import org.apache.seatunnel.engine.common.exception.JobDefineCheckException;
49+
import org.apache.seatunnel.engine.common.loader.ClassLoaderUtil;
4950
import org.apache.seatunnel.engine.common.loader.SeaTunnelChildFirstClassLoader;
5051
import org.apache.seatunnel.engine.common.utils.IdGenerator;
52+
import org.apache.seatunnel.engine.core.classloader.ClassLoaderService;
5153
import org.apache.seatunnel.engine.core.dag.actions.Action;
5254
import org.apache.seatunnel.engine.core.dag.actions.SinkAction;
5355
import org.apache.seatunnel.engine.core.dag.actions.SinkConfig;
@@ -149,7 +151,7 @@ public MultipleTableJobConfigParser(
149151
new JobConfigParser(idGenerator, commonPluginJars, isStartWithSavePoint);
150152
}
151153

152-
public ImmutablePair<List<Action>, Set<URL>> parse() {
154+
public ImmutablePair<List<Action>, Set<URL>> parse(ClassLoaderService classLoaderService) {
153155
List<? extends Config> sourceConfigs =
154156
TypesafeConfigUtils.getConfigList(
155157
seaTunnelJobConfig, "source", Collections.emptyList());
@@ -165,8 +167,15 @@ public ImmutablePair<List<Action>, Set<URL>> parse() {
165167
connectorJars.addAll(commonPluginJars);
166168
}
167169
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
168-
ClassLoader classLoader =
169-
new SeaTunnelChildFirstClassLoader(connectorJars, parentClassLoader);
170+
171+
ClassLoader classLoader;
172+
if (classLoaderService == null) {
173+
classLoader = new SeaTunnelChildFirstClassLoader(connectorJars, parentClassLoader);
174+
} else {
175+
classLoader =
176+
classLoaderService.getClassLoader(
177+
Long.parseLong(jobConfig.getJobContext().getJobId()), connectorJars);
178+
}
170179
try {
171180
Thread.currentThread().setContextClassLoader(classLoader);
172181
ConfigParserUtil.checkGraph(sourceConfigs, transformConfigs, sinkConfigs);
@@ -196,6 +205,11 @@ public ImmutablePair<List<Action>, Set<URL>> parse() {
196205
return new ImmutablePair<>(sinkActions, factoryUrls);
197206
} finally {
198207
Thread.currentThread().setContextClassLoader(parentClassLoader);
208+
if (classLoaderService != null) {
209+
classLoaderService.releaseClassLoader(
210+
Long.parseLong(jobConfig.getJobContext().getJobId()), connectorJars);
211+
}
212+
ClassLoaderUtil.recycleClassLoaderFromThread(classLoader);
199213
}
200214
}
201215

Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515
* limitations under the License.
1616
*/
1717

18-
package org.apache.seatunnel.engine.server.service.classloader;
18+
package org.apache.seatunnel.engine.core.classloader;
1919

2020
import org.apache.seatunnel.engine.common.loader.SeaTunnelChildFirstClassLoader;
2121

22-
import org.apache.curator.shaded.com.google.common.collect.Lists;
23-
2422
import org.junit.jupiter.api.AfterEach;
2523
import org.junit.jupiter.api.Assertions;
2624
import org.junit.jupiter.api.BeforeEach;
2725
import org.junit.jupiter.api.Test;
2826

27+
import com.google.common.collect.Lists;
28+
2929
import java.net.MalformedURLException;
3030
import java.net.URL;
3131
import java.util.Collections;
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
* limitations under the License.
1616
*/
1717

18-
package org.apache.seatunnel.engine.server.service.classloader;
19-
20-
import org.apache.curator.shaded.com.google.common.collect.Lists;
18+
package org.apache.seatunnel.engine.core.classloader;
2119

2220
import org.junit.jupiter.api.Assertions;
2321
import org.junit.jupiter.api.Test;
2422

23+
import com.google.common.collect.Lists;
24+
2525
import java.net.MalformedURLException;
2626
import java.net.URL;
2727

Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
* limitations under the License.
1616
*/
1717

18-
package org.apache.seatunnel.engine.server.service.classloader;
19-
20-
import org.apache.curator.shaded.com.google.common.collect.Lists;
18+
package org.apache.seatunnel.engine.core.classloader;
2119

2220
import org.junit.jupiter.api.Assertions;
2321
import org.junit.jupiter.api.Test;
2422

23+
import com.google.common.collect.Lists;
24+
2525
import java.net.MalformedURLException;
2626
import java.net.URL;
2727

Diff for: seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/SeaTunnelServer.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import org.apache.seatunnel.engine.common.Constant;
2222
import org.apache.seatunnel.engine.common.config.SeaTunnelConfig;
2323
import org.apache.seatunnel.engine.common.exception.SeaTunnelEngineException;
24+
import org.apache.seatunnel.engine.core.classloader.ClassLoaderService;
25+
import org.apache.seatunnel.engine.core.classloader.DefaultClassLoaderService;
2426
import org.apache.seatunnel.engine.server.execution.ExecutionState;
2527
import org.apache.seatunnel.engine.server.execution.TaskGroupLocation;
26-
import org.apache.seatunnel.engine.server.service.classloader.ClassLoaderService;
27-
import org.apache.seatunnel.engine.server.service.classloader.DefaultClassLoaderService;
2828
import org.apache.seatunnel.engine.server.service.jar.ConnectorPackageService;
2929
import org.apache.seatunnel.engine.server.service.slot.DefaultSlotService;
3030
import org.apache.seatunnel.engine.server.service.slot.SlotService;

Diff for: seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.seatunnel.engine.common.exception.JobNotFoundException;
2828
import org.apache.seatunnel.engine.common.loader.ClassLoaderUtil;
2929
import org.apache.seatunnel.engine.common.utils.PassiveCompletableFuture;
30+
import org.apache.seatunnel.engine.core.classloader.ClassLoaderService;
3031
import org.apache.seatunnel.engine.core.job.ConnectorJarIdentifier;
3132
import org.apache.seatunnel.engine.server.exception.TaskGroupContextNotFoundException;
3233
import org.apache.seatunnel.engine.server.execution.ExecutionState;
@@ -42,7 +43,6 @@
4243
import org.apache.seatunnel.engine.server.execution.TaskLocation;
4344
import org.apache.seatunnel.engine.server.execution.TaskTracker;
4445
import org.apache.seatunnel.engine.server.metrics.SeaTunnelMetricsContext;
45-
import org.apache.seatunnel.engine.server.service.classloader.ClassLoaderService;
4646
import org.apache.seatunnel.engine.server.service.jar.ServerConnectorPackageClient;
4747
import org.apache.seatunnel.engine.server.task.SeaTunnelTask;
4848
import org.apache.seatunnel.engine.server.task.TaskGroupImmutableInformation;

0 commit comments

Comments
 (0)