Skip to content

Commit 4268b15

Browse files
diegomarquezpmeltsufingcf-owl-bot[bot]
authored
chore: copy functionality from io.grpc.internal.SharedResourceHolder (#1065)
* chore: copy functionality from io.grpc.internal.SharedResourceHolder Included related classes and test file. Tests were adapted to use an easymock mock * fix: remove grpc-core from deps * fix: add used undeclared dependency jsr305 * fix: add copy explanation comments, remove `@ThreadSafe` and related dependency * Update google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/LogExceptionRunnable.java Co-authored-by: Mike Eltsufin <meltsufin@google.com> * Update google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/SharedResourceHolder.java Co-authored-by: Mike Eltsufin <meltsufin@google.com> * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Mike Eltsufin <meltsufin@google.com> Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent dd2c002 commit 4268b15

File tree

5 files changed

+536
-9
lines changed

5 files changed

+536
-9
lines changed

java-core/google-cloud-core-grpc/pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,10 @@
4747
<groupId>io.grpc</groupId>
4848
<artifactId>grpc-api</artifactId>
4949
</dependency>
50-
<dependency>
51-
<groupId>io.grpc</groupId>
52-
<artifactId>grpc-core</artifactId>
53-
</dependency>
5450
<dependency>
5551
<groupId>com.google.http-client</groupId>
5652
<artifactId>google-http-client</artifactId>
5753
</dependency>
58-
5954
<dependency>
6055
<groupId>junit</groupId>
6156
<artifactId>junit</artifactId>

java-core/google-cloud-core-grpc/src/main/java/com/google/cloud/grpc/GrpcTransportOptions.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232
import com.google.cloud.ServiceOptions;
3333
import com.google.cloud.TransportOptions;
3434
import com.google.common.util.concurrent.ThreadFactoryBuilder;
35-
import io.grpc.internal.SharedResourceHolder;
36-
import io.grpc.internal.SharedResourceHolder.Resource;
3735
import java.io.IOException;
3836
import java.io.ObjectInputStream;
3937
import java.util.Objects;
@@ -51,8 +49,8 @@ public class GrpcTransportOptions implements TransportOptions {
5149
private transient ExecutorFactory<ScheduledExecutorService> executorFactory;
5250

5351
/** Shared thread pool executor. */
54-
private static final Resource<ScheduledExecutorService> EXECUTOR =
55-
new Resource<ScheduledExecutorService>() {
52+
private static final SharedResourceHolder.Resource<ScheduledExecutorService> EXECUTOR =
53+
new SharedResourceHolder.Resource<ScheduledExecutorService>() {
5654
@Override
5755
public ScheduledExecutorService create() {
5856
ScheduledThreadPoolExecutor service =
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2023 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+
* 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.google.cloud.grpc;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
21+
import com.google.common.base.Throwables;
22+
import java.util.logging.Level;
23+
import java.util.logging.Logger;
24+
25+
/**
26+
* This class was copied from grpc-core to prevent dependence on an unstable API that may be subject
27+
* to changes
28+
* (https://github.com/grpc/grpc-java/blob/d07ecbe037d2705a1c9f4b6345581f860e505b56/core/src/main/java/io/grpc/internal/LogExceptionRunnable.java)
29+
*
30+
* <p>A simple wrapper for a {@link Runnable} that logs any exception thrown by it, before
31+
* re-throwing it.
32+
*/
33+
final class LogExceptionRunnable implements Runnable {
34+
35+
private static final Logger log = Logger.getLogger(LogExceptionRunnable.class.getName());
36+
37+
private final Runnable task;
38+
39+
public LogExceptionRunnable(Runnable task) {
40+
this.task = checkNotNull(task, "task");
41+
}
42+
43+
@Override
44+
public void run() {
45+
try {
46+
task.run();
47+
} catch (Throwable t) {
48+
log.log(Level.SEVERE, "Exception while executing runnable " + task, t);
49+
Throwables.throwIfUnchecked(t);
50+
throw new AssertionError(t);
51+
}
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return "LogExceptionRunnable(" + task + ")";
57+
}
58+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright 2023 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+
* 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.google.cloud.grpc;
18+
19+
import com.google.common.base.Preconditions;
20+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
21+
import java.util.IdentityHashMap;
22+
import java.util.concurrent.*;
23+
24+
/**
25+
* This class was copied from grpc-core to prevent dependence on an unstable API that may be subject
26+
* to changes
27+
* (https://github.com/grpc/grpc-java/blob/d07ecbe037d2705a1c9f4b6345581f860e505b56/core/src/main/java/io/grpc/internal/SharedResourceHolder.java)
28+
*
29+
* <p>A holder for shared resource singletons.
30+
*
31+
* <p>Components like client channels and servers need certain resources, e.g. a thread pool, to
32+
* run. If the user has not provided such resources, these components will use a default one, which
33+
* is shared as a static resource. This class holds these default resources and manages their
34+
* life-cycles.
35+
*
36+
* <p>A resource is identified by the reference of a {@link Resource} object, which is typically a
37+
* singleton, provided to the get() and release() methods. Each Resource object (not its class) maps
38+
* to an object cached in the holder.
39+
*
40+
* <p>Resources are ref-counted and shut down after a delay when the ref-count reaches zero.
41+
*/
42+
final class SharedResourceHolder {
43+
static final long DESTROY_DELAY_SECONDS = 1;
44+
45+
// The sole holder instance.
46+
private static final SharedResourceHolder holder =
47+
new SharedResourceHolder(
48+
new ScheduledExecutorFactory() {
49+
@Override
50+
public ScheduledExecutorService createScheduledExecutor() {
51+
return Executors.newSingleThreadScheduledExecutor(
52+
getThreadFactory("grpc-shared-destroyer-%d", true));
53+
}
54+
});
55+
56+
private final IdentityHashMap<Resource<?>, Instance> instances = new IdentityHashMap<>();
57+
58+
private final ScheduledExecutorFactory destroyerFactory;
59+
60+
private ScheduledExecutorService destroyer;
61+
62+
// Visible to tests that would need to create instances of the holder.
63+
SharedResourceHolder(ScheduledExecutorFactory destroyerFactory) {
64+
this.destroyerFactory = destroyerFactory;
65+
}
66+
67+
private static ThreadFactory getThreadFactory(String nameFormat, boolean daemon) {
68+
return new ThreadFactoryBuilder().setDaemon(daemon).setNameFormat(nameFormat).build();
69+
}
70+
71+
/**
72+
* Try to get an existing instance of the given resource. If an instance does not exist, create a
73+
* new one with the given factory.
74+
*
75+
* @param resource the singleton object that identifies the requested static resource
76+
*/
77+
public static <T> T get(Resource<T> resource) {
78+
return holder.getInternal(resource);
79+
}
80+
81+
/**
82+
* Releases an instance of the given resource.
83+
*
84+
* <p>The instance must have been obtained from {@link #get(Resource)}. Otherwise will throw
85+
* IllegalArgumentException.
86+
*
87+
* <p>Caller must not release a reference more than once. It's advisory that you clear the
88+
* reference to the instance with the null returned by this method.
89+
*
90+
* @param resource the singleton Resource object that identifies the released static resource
91+
* @param instance the released static resource
92+
* @return a null which the caller can use to clear the reference to that instance.
93+
*/
94+
public static <T> T release(final Resource<T> resource, final T instance) {
95+
return holder.releaseInternal(resource, instance);
96+
}
97+
98+
/**
99+
* Visible to unit tests.
100+
*
101+
* @see #get(Resource)
102+
*/
103+
@SuppressWarnings("unchecked")
104+
synchronized <T> T getInternal(Resource<T> resource) {
105+
Instance instance = instances.get(resource);
106+
if (instance == null) {
107+
instance = new Instance(resource.create());
108+
instances.put(resource, instance);
109+
}
110+
if (instance.destroyTask != null) {
111+
instance.destroyTask.cancel(false);
112+
instance.destroyTask = null;
113+
}
114+
instance.refcount++;
115+
return (T) instance.payload;
116+
}
117+
118+
/** Visible to unit tests. */
119+
synchronized <T> T releaseInternal(final Resource<T> resource, final T instance) {
120+
final Instance cached = instances.get(resource);
121+
if (cached == null) {
122+
throw new IllegalArgumentException("No cached instance found for " + resource);
123+
}
124+
Preconditions.checkArgument(instance == cached.payload, "Releasing the wrong instance");
125+
Preconditions.checkState(cached.refcount > 0, "Refcount has already reached zero");
126+
cached.refcount--;
127+
if (cached.refcount == 0) {
128+
Preconditions.checkState(cached.destroyTask == null, "Destroy task already scheduled");
129+
// Schedule a delayed task to destroy the resource.
130+
if (destroyer == null) {
131+
destroyer = destroyerFactory.createScheduledExecutor();
132+
}
133+
cached.destroyTask =
134+
destroyer.schedule(
135+
new LogExceptionRunnable(
136+
new Runnable() {
137+
@Override
138+
public void run() {
139+
synchronized (SharedResourceHolder.this) {
140+
// Refcount may have gone up since the task was scheduled. Re-check it.
141+
if (cached.refcount == 0) {
142+
try {
143+
resource.close(instance);
144+
} finally {
145+
instances.remove(resource);
146+
if (instances.isEmpty()) {
147+
destroyer.shutdown();
148+
destroyer = null;
149+
}
150+
}
151+
}
152+
}
153+
}
154+
}),
155+
DESTROY_DELAY_SECONDS,
156+
TimeUnit.SECONDS);
157+
}
158+
// Always returning null
159+
return null;
160+
}
161+
162+
/** Defines a resource, and the way to create and destroy instances of it. */
163+
public interface Resource<T> {
164+
/** Create a new instance of the resource. */
165+
T create();
166+
167+
/** Destroy the given instance. */
168+
void close(T instance);
169+
}
170+
171+
interface ScheduledExecutorFactory {
172+
ScheduledExecutorService createScheduledExecutor();
173+
}
174+
175+
private static class Instance {
176+
final Object payload;
177+
int refcount;
178+
ScheduledFuture<?> destroyTask;
179+
180+
Instance(Object payload) {
181+
this.payload = payload;
182+
}
183+
}
184+
}

0 commit comments

Comments
 (0)