Skip to content

Commit ad0b4ee

Browse files
authored
fix: Use daemon threads for grpc-gcp background executors by default (#231)
Switch internal grpc-gcp executors to use a shared daemon-aware ThreadFactory to avoid keeping the JVM alive when only background threads remain. Add a system property (com.google.cloud.grpc.use_daemon_threads) to opt out and preserve legacy non-daemon behavior.
1 parent e83f297 commit ad0b4ee

File tree

5 files changed

+56
-7
lines changed

5 files changed

+56
-7
lines changed

grpc-gcp/src/main/java/com/google/cloud/grpc/GcpManagedChannel.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.google.cloud.grpc.proto.MethodConfig;
2929
import com.google.common.annotations.VisibleForTesting;
3030
import com.google.common.base.Joiner;
31-
import com.google.common.util.concurrent.ThreadFactoryBuilder;
3231
import com.google.errorprone.annotations.concurrent.GuardedBy;
3332
import com.google.protobuf.Descriptors.FieldDescriptor;
3433
import com.google.protobuf.MessageOrBuilder;
@@ -139,7 +138,7 @@ public class GcpManagedChannel extends ManagedChannel {
139138

140139
private final ExecutorService stateNotificationExecutor =
141140
Executors.newCachedThreadPool(
142-
new ThreadFactoryBuilder().setNameFormat("gcp-mc-state-notifications-%d").build());
141+
GcpThreadFactory.newThreadFactory("gcp-mc-state-notifications-%d"));
143142

144143
// Callbacks to call when state changes.
145144
@GuardedBy("this")
@@ -177,7 +176,7 @@ public class GcpManagedChannel extends ManagedChannel {
177176
String.format("pool-%d", channelPoolIndex.incrementAndGet());
178177
private final Map<String, Long> cumulativeMetricValues = new ConcurrentHashMap<>();
179178
private final ScheduledExecutorService backgroundService =
180-
Executors.newSingleThreadScheduledExecutor();
179+
Executors.newSingleThreadScheduledExecutor(GcpThreadFactory.newThreadFactory("gcp-mc-bg-%d"));
181180

182181
// Metrics counters.
183182
private final AtomicInteger readyChannels = new AtomicInteger();

grpc-gcp/src/main/java/com/google/cloud/grpc/GcpMultiEndpointChannel.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ public class GcpMultiEndpointChannel extends ManagedChannel {
168168
@GuardedBy("this")
169169
private final Set<String> currentEndpoints = new HashSet<>();
170170

171-
private final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1);
171+
private final ScheduledExecutorService executor =
172+
new ScheduledThreadPoolExecutor(1, GcpThreadFactory.newThreadFactory("gcp-me-%d"));
172173

173174
/**
174175
* Constructor for {@link GcpMultiEndpointChannel}.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2026 Google LLC
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+
* https://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.google.cloud.grpc;
18+
19+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
20+
import java.util.concurrent.ThreadFactory;
21+
22+
/** Thread factory helper for grpc-gcp background executors. */
23+
public final class GcpThreadFactory {
24+
/** System property to control daemon threads for grpc-gcp background executors. */
25+
public static final String USE_DAEMON_THREADS_PROPERTY =
26+
"com.google.cloud.grpc.use_daemon_threads";
27+
28+
private GcpThreadFactory() {}
29+
30+
/**
31+
* Creates a {@link ThreadFactory} that names threads and honors the daemon-thread setting.
32+
*
33+
* <p>Defaults to daemon threads to avoid keeping the JVM alive when only background work remains.
34+
* Set {@code -Dcom.google.cloud.grpc.use_daemon_threads=false} to use non-daemon threads.
35+
*/
36+
public static ThreadFactory newThreadFactory(String nameFormat) {
37+
boolean useDaemon = true;
38+
String prop = System.getProperty(USE_DAEMON_THREADS_PROPERTY);
39+
if (prop != null) {
40+
useDaemon = Boolean.parseBoolean(prop);
41+
}
42+
return new ThreadFactoryBuilder().setNameFormat(nameFormat).setDaemon(useDaemon).build();
43+
}
44+
}

grpc-gcp/src/main/java/com/google/cloud/grpc/fallback/GcpFallbackChannel.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2121
import static java.util.concurrent.TimeUnit.NANOSECONDS;
2222

23+
import com.google.cloud.grpc.GcpThreadFactory;
2324
import com.google.common.annotations.VisibleForTesting;
2425
import io.grpc.CallOptions;
2526
import io.grpc.Channel;
@@ -84,7 +85,8 @@ public GcpFallbackChannel(
8485
if (execService != null) {
8586
this.execService = execService;
8687
} else {
87-
this.execService = Executors.newScheduledThreadPool(3);
88+
this.execService =
89+
Executors.newScheduledThreadPool(3, GcpThreadFactory.newThreadFactory("gcp-fallback-%d"));
8890
}
8991
this.options = options;
9092
if (options.getGcpOpenTelemetry() != null) {
@@ -150,7 +152,8 @@ public GcpFallbackChannel(
150152
if (execService != null) {
151153
this.execService = execService;
152154
} else {
153-
this.execService = Executors.newScheduledThreadPool(3);
155+
this.execService =
156+
Executors.newScheduledThreadPool(3, GcpThreadFactory.newThreadFactory("gcp-fallback-%d"));
154157
}
155158
this.options = options;
156159
if (options.getGcpOpenTelemetry() != null) {

grpc-gcp/src/main/java/com/google/cloud/grpc/multiendpoint/MultiEndpoint.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static java.util.Comparator.comparingInt;
2020
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2121

22+
import com.google.cloud.grpc.GcpThreadFactory;
2223
import com.google.cloud.grpc.multiendpoint.Endpoint.EndpointState;
2324
import com.google.common.base.Preconditions;
2425
import com.google.errorprone.annotations.CanIgnoreReturnValue;
@@ -78,7 +79,8 @@ public final class MultiEndpoint {
7879
private long recoverCnt = 0;
7980
private long replaceCnt = 0;
8081

81-
final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1);
82+
final ScheduledExecutorService executor =
83+
new ScheduledThreadPoolExecutor(1, GcpThreadFactory.newThreadFactory("gcp-me-core-%d"));
8284

8385
private MultiEndpoint(Builder builder) {
8486
this.recoveryTimeout = builder.recoveryTimeout;

0 commit comments

Comments
 (0)