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

grpc / grpc-java / #20224

26 Mar 2026 07:06AM UTC coverage: 88.692% (+0.004%) from 88.688%
#20224

push

github

web-flow
Remove extraneous changes beyond avoiding the false positive warnings (#12731)

super.shutdown() is never expected to throw, so some of the changes were
not required.
Also removing the redundant unit test.

Rework of PR #12705.

35491 of 40016 relevant lines covered (88.69%)

0.89 hits per line

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

96.83
/../core/src/main/java/io/grpc/internal/ManagedChannelOrphanWrapper.java
1
/*
2
 * Copyright 2018 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.annotations.VisibleForTesting;
20
import io.grpc.ManagedChannel;
21
import java.lang.ref.Reference;
22
import java.lang.ref.ReferenceQueue;
23
import java.lang.ref.SoftReference;
24
import java.lang.ref.WeakReference;
25
import java.util.concurrent.ConcurrentHashMap;
26
import java.util.concurrent.ConcurrentMap;
27
import java.util.concurrent.atomic.AtomicBoolean;
28
import java.util.logging.Level;
29
import java.util.logging.LogRecord;
30
import java.util.logging.Logger;
31

32
/**
33
 *  Best effort detecting channels that has not been properly cleaned up.
34
 *  Use {@link WeakReference} to avoid keeping the channel alive and retaining too much memory.
35
 *  Check lost references only on new channel creation and log message to indicate
36
 *  the previous channel (id and target) that has not been shutdown. This is done to avoid Object
37
 *  finalizers.
38
 */
39
final class ManagedChannelOrphanWrapper extends ForwardingManagedChannel {
40
  private static final ReferenceQueue<ManagedChannelOrphanWrapper> refqueue =
1✔
41
      new ReferenceQueue<>();
42
  // Retain the References so they don't get GC'd
43
  private static final ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs =
1✔
44
      new ConcurrentHashMap<>();
45
  private static final Logger logger =
1✔
46
      Logger.getLogger(ManagedChannelOrphanWrapper.class.getName());
1✔
47

48
  private final ManagedChannelReference phantom;
49

50
  ManagedChannelOrphanWrapper(ManagedChannel delegate) {
51
    this(delegate, refqueue, refs);
1✔
52
  }
1✔
53

54
  @VisibleForTesting
55
  ManagedChannelOrphanWrapper(
56
      ManagedChannel delegate,
57
      ReferenceQueue<ManagedChannelOrphanWrapper> refqueue,
58
      ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs) {
59
    super(delegate);
1✔
60
    phantom = new ManagedChannelReference(this, delegate, refqueue, refs);
1✔
61
  }
1✔
62

63
  @Override
64
  public ManagedChannel shutdown() {
65
    phantom.clearSafely();
1✔
66
    // This dummy check prevents the JIT from collecting 'this' too early
67
    if (this.getClass() == null) {
1✔
68
      throw new AssertionError();
×
69
    }
70
    return super.shutdown();
1✔
71
  }
72

73
  @Override
74
  public ManagedChannel shutdownNow() {
75
    phantom.clearSafely();
1✔
76
    // This dummy check prevents the JIT from collecting 'this' too early
77
    if (this.getClass() == null) {
1✔
78
      throw new AssertionError();
×
79
    }
80
    return super.shutdownNow();
1✔
81
  }
82

83
  @VisibleForTesting
84
  static final class ManagedChannelReference extends WeakReference<ManagedChannelOrphanWrapper> {
85

86
    private static final String ALLOCATION_SITE_PROPERTY_NAME =
87
        "io.grpc.ManagedChannel.enableAllocationTracking";
88

89
    private static final boolean ENABLE_ALLOCATION_TRACKING =
1✔
90
        Boolean.parseBoolean(System.getProperty(ALLOCATION_SITE_PROPERTY_NAME, "true"));
1✔
91

92
    @SuppressWarnings("StaticAssignmentOfThrowable")
93
    private static final RuntimeException missingCallSite = missingCallSite();
1✔
94

95
    private final ReferenceQueue<ManagedChannelOrphanWrapper> refqueue;
96
    private final ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs;
97

98
    private final String channelStr;
99
    private final Reference<RuntimeException> allocationSite;
100
    private final AtomicBoolean shutdown = new AtomicBoolean();
1✔
101

102
    ManagedChannelReference(
103
        ManagedChannelOrphanWrapper orphanable,
104
        ManagedChannel channel,
105
        ReferenceQueue<ManagedChannelOrphanWrapper> refqueue,
106
        ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs) {
107
      super(orphanable, refqueue);
1✔
108
      allocationSite = new SoftReference<>(
1✔
109
          ENABLE_ALLOCATION_TRACKING
1✔
110
              ? new RuntimeException("ManagedChannel allocation site")
1✔
111
              : missingCallSite);
1✔
112
      this.channelStr = channel.toString();
1✔
113
      this.refqueue = refqueue;
1✔
114
      this.refs = refs;
1✔
115
      this.refs.put(this, this);
1✔
116
      cleanQueue(refqueue);
1✔
117
    }
1✔
118

119
    /**
120
     * This clear() is *not* called automatically by the JVM.  As this is a weak ref, the reference
121
     * will be cleared automatically by the JVM, but will not be removed from {@link #refs}.
122
     * We do it here to avoid this ending up on the reference queue.
123
     */
124
    @Override
125
    public void clear() {
126
      clearInternal();
1✔
127
      // We run this here to periodically clean up the queue if at least some of the channels are
128
      // being shutdown properly.
129
      cleanQueue(refqueue);
1✔
130
    }
1✔
131

132
    /**
133
     * Safe to call concurrently.
134
     */
135
    private void clearSafely() {
136
      if (!shutdown.getAndSet(true)) {
1✔
137
        clear();
1✔
138
      }
139
    }
1✔
140

141
    // avoid reentrancy
142
    private void clearInternal() {
143
      super.clear();
1✔
144
      refs.remove(this);
1✔
145
      allocationSite.clear();
1✔
146
    }
1✔
147

148
    private static RuntimeException missingCallSite() {
149
      RuntimeException e = new RuntimeException(
1✔
150
          "ManagedChannel allocation site not recorded.  Set -D"
151
              + ALLOCATION_SITE_PROPERTY_NAME + "=true to enable it");
152
      e.setStackTrace(new StackTraceElement[0]);
1✔
153
      return e;
1✔
154
    }
155

156
    @VisibleForTesting
157
    static int cleanQueue(ReferenceQueue<ManagedChannelOrphanWrapper> refqueue) {
158
      ManagedChannelReference ref;
159
      int orphanedChannels = 0;
1✔
160
      while ((ref = (ManagedChannelReference) refqueue.poll()) != null) {
1✔
161
        RuntimeException maybeAllocationSite = ref.allocationSite.get();
1✔
162
        boolean wasShutdown = ref.shutdown.get();
1✔
163
        ref.clearInternal(); // technically the reference is gone already.
1✔
164
        if (!wasShutdown) {
1✔
165
          orphanedChannels++;
1✔
166
          Level level = Level.SEVERE;
1✔
167
          if (logger.isLoggable(level)) {
1✔
168
            String fmt =
1✔
169
                "*~*~*~ Previous channel {0} was garbage collected without being shut down! ~*~*~*"
170
                    + System.getProperty("line.separator")
1✔
171
                    + "    Make sure to call shutdown()/shutdownNow()";
172
            LogRecord lr = new LogRecord(level, fmt);
1✔
173
            lr.setLoggerName(logger.getName());
1✔
174
            lr.setParameters(new Object[] {ref.channelStr});
1✔
175
            lr.setThrown(maybeAllocationSite);
1✔
176
            logger.log(lr);
1✔
177
          }
178
        }
179
      }
1✔
180
      return orphanedChannels;
1✔
181
    }
182
  }
183
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc