• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20380

30 Jul 2026 05:47AM UTC coverage: 88.842% (-0.002%) from 88.844%
#20380

push

github

kannanjgithub
Merge branch 'release-v1.82.3' into v1.82.x

36291 of 40849 relevant lines covered (88.84%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.73
/../core/src/main/java/io/grpc/internal/SharedResourceHolder.java
1
/*
2
 * Copyright 2014 The gRPC Authors
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 io.grpc.internal;
18

19
import com.google.common.base.Preconditions;
20
import java.util.IdentityHashMap;
21
import java.util.concurrent.Executors;
22
import java.util.concurrent.ScheduledExecutorService;
23
import java.util.concurrent.ScheduledFuture;
24
import java.util.concurrent.TimeUnit;
25
import javax.annotation.concurrent.ThreadSafe;
26

27
/**
28
 * A holder for shared resource singletons.
29
 *
30
 * <p>Components like client channels and servers need certain resources, e.g. a thread pool, to
31
 * run. If the user has not provided such resources, these components will use a default one, which
32
 * is shared as a static resource. This class holds these default resources and manages their
33
 * life-cycles.
34
 *
35
 * <p>A resource is identified by the reference of a {@link Resource} object, which is typically a
36
 * singleton, provided to the get() and release() methods. Each Resource object (not its class) maps
37
 * to an object cached in the holder.
38
 *
39
 * <p>Resources are ref-counted and shut down after a delay when the ref-count reaches zero.
40
 */
41
@ThreadSafe
42
public final class SharedResourceHolder {
43
  static final long DESTROY_DELAY_SECONDS = 1;
44

45
  // The sole holder instance.
46
  private static final SharedResourceHolder holder = new SharedResourceHolder(
1✔
47
      new ScheduledExecutorFactory() {
1✔
48
        @Override
49
        public ScheduledExecutorService createScheduledExecutor() {
50
          return Executors.newSingleThreadScheduledExecutor(
1✔
51
              GrpcUtil.getThreadFactory("grpc-shared-destroyer-%d", true));
1✔
52
        }
53
      });
54

55
  private final IdentityHashMap<Resource<?>, Instance> instances =
1✔
56
      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) {
1✔
64
    this.destroyerFactory = destroyerFactory;
1✔
65
  }
1✔
66

67
  /**
68
   * Try to get an existing instance of the given resource. If an instance does not exist, create a
69
   * new one with the given factory.
70
   *
71
   * @param resource the singleton object that identifies the requested static resource
72
   */
73
  public static <T> T get(Resource<T> resource) {
74
    return holder.getInternal(resource);
1✔
75
  }
76

77
  /**
78
   * Releases an instance of the given resource.
79
   *
80
   * <p>The instance must have been obtained from {@link #get(Resource)}. Otherwise will throw
81
   * IllegalArgumentException.
82
   *
83
   * <p>Caller must not release a reference more than once. It's advisory that you clear the
84
   * reference to the instance with the null returned by this method.
85
   *
86
   * @param resource the singleton Resource object that identifies the released static resource
87
   * @param instance the released static resource
88
   *
89
   * @return a null which the caller can use to clear the reference to that instance.
90
   */
91
  public static <T> T release(final Resource<T> resource, final T instance) {
92
    return holder.releaseInternal(resource, instance);
1✔
93
  }
94

95
  /**
96
   * Visible to unit tests.
97
   *
98
   * @see #get(Resource)
99
   */
100
  @SuppressWarnings("unchecked")
101
  synchronized <T> T getInternal(Resource<T> resource) {
102
    Instance instance = instances.get(resource);
1✔
103
    if (instance == null) {
1✔
104
      instance = new Instance(resource.create());
1✔
105
      instances.put(resource, instance);
1✔
106
    }
107
    if (instance.destroyTask != null) {
1✔
108
      instance.destroyTask.cancel(false);
1✔
109
      instance.destroyTask = null;
1✔
110
    }
111
    instance.refcount++;
1✔
112
    return (T) instance.payload;
1✔
113
  }
114

115
  /**
116
   * Visible to unit tests.
117
   */
118
  synchronized <T> T releaseInternal(final Resource<T> resource, final T instance) {
119
    final Instance cached = instances.get(resource);
1✔
120
    if (cached == null) {
1✔
121
      throw new IllegalArgumentException("No cached instance found for " + resource);
1✔
122
    }
123
    Preconditions.checkArgument(instance == cached.payload, "Releasing the wrong instance");
1✔
124
    Preconditions.checkState(cached.refcount > 0, "Refcount has already reached zero");
1✔
125
    cached.refcount--;
1✔
126
    if (cached.refcount == 0) {
1✔
127
      Preconditions.checkState(cached.destroyTask == null, "Destroy task already scheduled");
1✔
128
      // Schedule a delayed task to destroy the resource.
129
      if (destroyer == null) {
1✔
130
        destroyer = destroyerFactory.createScheduledExecutor();
1✔
131
      }
132
      cached.destroyTask = destroyer.schedule(new LogExceptionRunnable(new Runnable() {
1✔
133
        @Override
134
        public void run() {
135
          synchronized (SharedResourceHolder.this) {
1✔
136
            // Refcount may have gone up since the task was scheduled. Re-check it.
137
            if (cached.refcount != 0) {
1✔
138
              return;
×
139
            }
140
            instances.remove(resource);
1✔
141
            if (instances.isEmpty()) {
1✔
142
              destroyer.shutdown();
1✔
143
              destroyer = null;
1✔
144
            }
145
          }
1✔
146
          resource.close(instance);
1✔
147
        }
1✔
148
      }), DESTROY_DELAY_SECONDS, TimeUnit.SECONDS);
149
    }
150
    // Always returning null
151
    return null;
1✔
152
  }
153

154
  /**
155
   * Defines a resource, and the way to create and destroy instances of it.
156
   */
157
  public interface Resource<T> {
158
    /**
159
     * Create a new instance of the resource.
160
     */
161
    T create();
162

163
    /**
164
     * Destroy the given instance.
165
     */
166
    void close(T instance);
167
  }
168

169
  interface ScheduledExecutorFactory {
170
    ScheduledExecutorService createScheduledExecutor();
171
  }
172

173
  private static class Instance {
174
    final Object payload;
175
    int refcount;
176
    ScheduledFuture<?> destroyTask;
177

178
    Instance(Object payload) {
1✔
179
      this.payload = payload;
1✔
180
    }
1✔
181
  }
182
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc