2020import static org .apache .beam .vendor .guava .v32_1_2_jre .com .google .common .base .Preconditions .checkNotNull ;
2121
2222import java .util .Map ;
23+ import java .util .Set ;
24+ import java .util .concurrent .ConcurrentHashMap ;
2325import org .apache .beam .runners .core .metrics .MetricsPusher ;
2426import org .apache .beam .sdk .Pipeline ;
2527import org .apache .beam .sdk .PipelineResult ;
2931import org .apache .flink .api .common .JobExecutionResult ;
3032import org .apache .flink .api .common .RuntimeExecutionMode ;
3133import org .apache .flink .api .java .ExecutionEnvironment ;
34+ import org .apache .flink .api .java .LocalEnvironment ;
3235import org .apache .flink .core .execution .JobClient ;
3336import org .apache .flink .runtime .jobgraph .JobGraph ;
37+ import org .apache .flink .streaming .api .environment .LocalStreamEnvironment ;
3438import org .apache .flink .streaming .api .environment .StreamExecutionEnvironment ;
3539import org .apache .flink .streaming .api .graph .StreamGraph ;
3640import org .slf4j .Logger ;
@@ -52,6 +56,8 @@ class FlinkPipelineExecutionEnvironment {
5256 private static final Logger LOG =
5357 LoggerFactory .getLogger (FlinkPipelineExecutionEnvironment .class );
5458
59+ private static final Set <ThreadGroup > protectedThreadGroups = ConcurrentHashMap .newKeySet ();
60+
5561 private final FlinkPipelineOptions options ;
5662
5763 /**
@@ -143,6 +149,7 @@ public PipelineResult executePipeline() throws Exception {
143149 if (flinkBatchEnv != null ) {
144150 if (options .getAttachedMode ()) {
145151 JobExecutionResult jobExecutionResult = flinkBatchEnv .execute (jobName );
152+ ensureFlinkCleanupComplete (flinkBatchEnv );
146153 return createAttachedPipelineResult (jobExecutionResult );
147154 } else {
148155 JobClient jobClient = flinkBatchEnv .executeAsync (jobName );
@@ -151,6 +158,7 @@ public PipelineResult executePipeline() throws Exception {
151158 } else if (flinkStreamEnv != null ) {
152159 if (options .getAttachedMode ()) {
153160 JobExecutionResult jobExecutionResult = flinkStreamEnv .execute (jobName );
161+ ensureFlinkCleanupComplete (flinkStreamEnv );
154162 return createAttachedPipelineResult (jobExecutionResult );
155163 } else {
156164 JobClient jobClient = flinkStreamEnv .executeAsync (jobName );
@@ -161,6 +169,41 @@ public PipelineResult executePipeline() throws Exception {
161169 }
162170 }
163171
172+ /** Prevents ThreadGroup destruction while Flink cleanup threads are still running. */
173+ private void ensureFlinkCleanupComplete (Object executionEnv ) {
174+ String javaVersion = System .getProperty ("java.version" );
175+ if (javaVersion == null || !javaVersion .startsWith ("1.8" )) {
176+ return ;
177+ }
178+
179+ if (!(executionEnv instanceof LocalStreamEnvironment
180+ || executionEnv instanceof LocalEnvironment )) {
181+ return ;
182+ }
183+
184+ ThreadGroup currentThreadGroup = Thread .currentThread ().getThreadGroup ();
185+ if (currentThreadGroup == null ) {
186+ return ;
187+ }
188+
189+ protectedThreadGroups .add (currentThreadGroup );
190+
191+ Thread cleanupReleaser =
192+ new Thread (
193+ () -> {
194+ try {
195+ Thread .sleep (2000 ); // 2 seconds should be enough for Flink cleanup
196+ } catch (InterruptedException e ) {
197+ Thread .currentThread ().interrupt ();
198+ } finally {
199+ protectedThreadGroups .remove (currentThreadGroup );
200+ }
201+ },
202+ "FlinkCleanupReleaser" );
203+ cleanupReleaser .setDaemon (true );
204+ cleanupReleaser .start ();
205+ }
206+
164207 private FlinkDetachedRunnerResult createDetachedPipelineResult (
165208 JobClient jobClient , FlinkPipelineOptions options ) {
166209 LOG .info ("Pipeline submitted in detached mode" );
0 commit comments