Skip to content

Commit e3fbff1

Browse files
committed
Fix ChannelFetcher memory leak
1 parent 0f344f8 commit e3fbff1

File tree

4 files changed

+95
-23
lines changed

4 files changed

+95
-23
lines changed

core/src/main/java/com/microsoft/applicationinsights/TelemetryClient.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,11 @@
4949
* General overview https://docs.microsoft.com/azure/application-insights/app-insights-api-custom-events-metrics
5050
*/
5151
public class TelemetryClient {
52-
private final class TelemetryClientChannelFetcher implements ChannelFetcher {
53-
public TelemetryChannel fetch() {
54-
return getChannel();
55-
}
56-
}
5752

5853
private final TelemetryConfiguration configuration;
5954
private volatile TelemetryContext context;
6055
private TelemetryChannel channel;
6156

62-
private static final Object TELEMETRY_STOP_HOOK_LOCK = new Object();
6357
private static final Object TELEMETRY_CONTEXT_LOCK = new Object();
6458

6559
private static AtomicLong generateCounter = new AtomicLong(0);
@@ -72,10 +66,6 @@ public TelemetryClient(TelemetryConfiguration configuration) {
7266
configuration = TelemetryConfiguration.getActive();
7367
}
7468

75-
synchronized (TELEMETRY_STOP_HOOK_LOCK) {
76-
SDKShutdownActivity.INSTANCE.register(new TelemetryClientChannelFetcher());
77-
}
78-
7969
this.configuration = configuration;
8070
}
8171

@@ -507,6 +497,7 @@ public void flush() {
507497
TelemetryChannel getChannel() {
508498
if (this.channel == null) {
509499
this.channel = configuration.getChannel();
500+
SDKShutdownActivity.INSTANCE.register(this.channel);
510501
}
511502

512503
return this.channel;

core/src/main/java/com/microsoft/applicationinsights/internal/shutdown/SDKShutdownActivity.java

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.io.Closeable;
2525
import java.util.List;
2626
import java.util.ArrayList;
27+
import java.util.Map;
28+
import java.util.WeakHashMap;
2729
import java.util.concurrent.ExecutorService;
2830
import java.util.concurrent.TimeUnit;
2931

@@ -51,10 +53,16 @@ public enum SDKShutdownActivity {
5153
private static class SDKShutdownAction implements Runnable {
5254
private boolean stopped = false;
5355

56+
private final Map<TelemetryChannel, Boolean> channels = new WeakHashMap<>();
57+
@Deprecated
5458
private final List<ChannelFetcher> fetchers = new ArrayList<ChannelFetcher>();
5559
private final List<Stoppable> stoppables = new ArrayList<Stoppable>();
5660
private final List<Closeable> closeables = new ArrayList<Closeable>();
5761

62+
public synchronized void register(TelemetryChannel channel) {
63+
channels.put(channel, true);
64+
}
65+
5866
public synchronized void register(ChannelFetcher fetcher) {
5967
fetchers.add(fetcher);
6068
}
@@ -126,23 +134,29 @@ private void stopInternalLogger() {
126134
* Make sure no exception is thrown!
127135
*/
128136
private void stopChannels() {
137+
for (TelemetryChannel channel : channels.keySet()) {
138+
stopChannel(channel);
139+
}
129140
for (ChannelFetcher fetcher : fetchers) {
141+
stopChannel(fetcher.fetch());
142+
}
143+
}
144+
145+
private void stopChannel(TelemetryChannel channelToStop) {
146+
try {
147+
if (channelToStop != null) {
148+
channelToStop.stop(getPerThreadTimeout(), getPerThreadTimeUnit());
149+
}
150+
} catch (ThreadDeath td) {
151+
throw td;
152+
} catch (Throwable t) {
130153
try {
131-
TelemetryChannel channelToStop = fetcher.fetch();
132-
if (channelToStop != null) {
133-
channelToStop.stop(getPerThreadTimeout(), getPerThreadTimeUnit());
134-
}
154+
InternalLogger.INSTANCE.error("Failed to stop channel: '%s'", t.toString());
155+
InternalLogger.INSTANCE.trace("Stack trace generated is %s", ExceptionUtils.getStackTrace(t));
135156
} catch (ThreadDeath td) {
136157
throw td;
137-
} catch (Throwable t) {
138-
try {
139-
InternalLogger.INSTANCE.error("Failed to stop channel: '%s'", t.toString());
140-
InternalLogger.INSTANCE.trace("Stack trace generated is %s", ExceptionUtils.getStackTrace(t));
141-
} catch (ThreadDeath td) {
142-
throw td;
143-
} catch (Throwable t2) {
144-
// chomp
145-
}
158+
} catch (Throwable t2) {
159+
// chomp
146160
}
147161
}
148162
}
@@ -190,6 +204,10 @@ private void closeClosables() {
190204

191205
private static volatile SDKShutdownAction shutdownAction;
192206

207+
public void register(TelemetryChannel channel) {
208+
getShutdownAction().register(channel);
209+
}
210+
193211
public void register(ChannelFetcher fetcher) {
194212
getShutdownAction().register(fetcher);
195213
}

core/src/main/java/com/microsoft/applicationinsights/internal/util/ChannelFetcher.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
/**
2727
* Created by gupele on 2/2/2015.
2828
*/
29+
@Deprecated
2930
public interface ChannelFetcher {
3031
TelemetryChannel fetch();
3132
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* ApplicationInsights-Java
3+
* Copyright (c) Microsoft Corporation
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
8+
* software and associated documentation files (the ""Software""), to deal in the Software
9+
* without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11+
* persons to whom the Software is furnished to do so, subject to the following conditions:
12+
* The above copyright notice and this permission notice shall be included in all copies or
13+
* substantial portions of the Software.
14+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
* DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package com.microsoft.applicationinsights;
23+
24+
import java.util.LinkedList;
25+
import java.util.List;
26+
27+
import com.microsoft.applicationinsights.channel.TelemetryChannel;
28+
import com.microsoft.applicationinsights.telemetry.Telemetry;
29+
import org.junit.*;
30+
import org.mockito.Matchers;
31+
import org.mockito.Mockito;
32+
import org.mockito.invocation.InvocationOnMock;
33+
import org.mockito.stubbing.Answer;
34+
35+
import static org.mockito.Mockito.mock;
36+
37+
public final class TelemetryClientMemoryTest {
38+
39+
@Test
40+
public void shouldNotRunOOM() {
41+
TelemetryConfiguration configuration = new TelemetryConfiguration();
42+
configuration.setInstrumentationKey("00000000-0000-0000-0000-000000000000");
43+
TelemetryChannel channel = mock(TelemetryChannel.class);
44+
configuration.setChannel(channel);
45+
46+
final List<Telemetry> eventsSent = new LinkedList<Telemetry>();
47+
// Setting the channel to add the sent telemetries to a collection, so they could be verified in tests.
48+
Mockito.doAnswer(new Answer() {
49+
@Override
50+
public Object answer(InvocationOnMock invocation) throws Throwable {
51+
Telemetry telemetry = ((Telemetry) invocation.getArguments()[0]);
52+
eventsSent.add(telemetry);
53+
54+
return null;
55+
}
56+
}).when(channel).send(Matchers.any(Telemetry.class));
57+
58+
for (int i = 0; i < 100000000; i++) {
59+
new TelemetryClient(configuration).getChannel();
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)