Skip to content

Commit 902b025

Browse files
authored
fix: Refactor StackExchange.Redis v2+ instrumentation to eliminate potential memory leaks.
1 parent 881989c commit 902b025

File tree

22 files changed

+720
-31
lines changed

22 files changed

+720
-31
lines changed

src/Agent/NewRelic/Agent/Core/Agent.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
using NewRelic.Agent.Api;
5+
using NewRelic.Agent.Api.Experimental;
56
using NewRelic.Agent.Configuration;
67
using NewRelic.Agent.Core.AgentHealth;
78
using NewRelic.Agent.Core.Aggregators;
@@ -64,6 +65,7 @@ public class Agent : IAgent // any changes to api, update the interface in exten
6465
private readonly ILogContextDataFilter _logContextDataFilter;
6566
private Extensions.Logging.ILogger _logger;
6667
private volatile IStackExchangeRedisCache _stackExchangeRedisCache;
68+
private readonly ISimpleSchedulingService _simpleSchedulingService;
6769

6870
public Agent(ITransactionService transactionService, ITransactionTransformer transactionTransformer,
6971
IThreadPoolStatic threadPoolStatic, ITransactionMetricNameMaker transactionMetricNameMaker, IPathHashMaker pathHashMaker,
@@ -72,7 +74,7 @@ public Agent(ITransactionService transactionService, ITransactionTransformer tra
7274
IBrowserMonitoringPrereqChecker browserMonitoringPrereqChecker, IBrowserMonitoringScriptMaker browserMonitoringScriptMaker,
7375
IConfigurationService configurationService, IAgentHealthReporter agentHealthReporter, IAgentTimerService agentTimerService,
7476
IMetricNameService metricNameService, Api.ITraceMetadataFactory traceMetadataFactory, ICATSupportabilityMetricCounters catMetricCounters,
75-
ILogEventAggregator logEventAggregator, ILogContextDataFilter logContextDataFilter)
77+
ILogEventAggregator logEventAggregator, ILogContextDataFilter logContextDataFilter, ISimpleSchedulingService simpleSchedulingService)
7678
{
7779
_transactionService = transactionService;
7880
_transactionTransformer = transactionTransformer;
@@ -93,6 +95,7 @@ public Agent(ITransactionService transactionService, ITransactionTransformer tra
9395
_catMetricCounters = catMetricCounters;
9496
_logEventAggregator = logEventAggregator;
9597
_logContextDataFilter = logContextDataFilter;
98+
_simpleSchedulingService = simpleSchedulingService;
9699

97100
Instance = this;
98101
}
@@ -389,6 +392,11 @@ public Dictionary<string, string> GetLinkingMetadata()
389392

390393
#region ExperimentalApi
391394

395+
public ISimpleSchedulingService SimpleSchedulingService
396+
{
397+
get { return _simpleSchedulingService; }
398+
}
399+
392400
public IStackExchangeRedisCache StackExchangeRedisCache
393401
{
394402
get { return _stackExchangeRedisCache; }

src/Agent/NewRelic/Agent/Core/Aggregators/LogEventAggregator.cs

+5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ public override void Collect(LogEventWireModel loggingEventWireModel)
5858

5959
public void CollectWithPriority(IList<LogEventWireModel> logEventWireModels, float priority)
6060
{
61+
if (logEventWireModels == null)
62+
{
63+
return;
64+
}
65+
6166
for (int i = 0; i < logEventWireModels.Count; i++)
6267
{
6368
_agentHealthReporter.ReportLoggingEventCollected();

src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs

+24
Original file line numberDiff line numberDiff line change
@@ -2208,6 +2208,30 @@ public TimeSpan UpdateLoadedModulesCycle
22082208
}
22092209
}
22102210

2211+
private TimeSpan? _stackExchangeRedisCleanupCycleOverride = null;
2212+
public TimeSpan StackExchangeRedisCleanupCycle
2213+
{
2214+
get
2215+
{
2216+
if (_stackExchangeRedisCleanupCycleOverride.HasValue)
2217+
{
2218+
return _stackExchangeRedisCleanupCycleOverride.Value;
2219+
}
2220+
2221+
if (_newRelicAppSettings.TryGetValue("OverrideStackExchangeRedisCleanupCycle", out var harvestCycle))
2222+
{
2223+
if (int.TryParse(harvestCycle, out var parsedHarvestCycle) && parsedHarvestCycle > 0)
2224+
{
2225+
Log.Info("StackExchange.Redis cleanup cycle overridden to " + parsedHarvestCycle + " seconds.");
2226+
_stackExchangeRedisCleanupCycleOverride = TimeSpan.FromSeconds(parsedHarvestCycle);
2227+
return _stackExchangeRedisCleanupCycleOverride.Value;
2228+
}
2229+
}
2230+
2231+
return DefaultHarvestCycle;
2232+
}
2233+
}
2234+
22112235
#endregion
22122236

22132237
#region Helpers

src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs

+3
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,9 @@ public ReportedConfiguration(IConfiguration configuration)
641641
[JsonProperty("update_loaded_modules.cycle")]
642642
public TimeSpan UpdateLoadedModulesCycle => _configuration.UpdateLoadedModulesCycle;
643643

644+
[JsonProperty("stackexchangeredis_cleanup.cycle")]
645+
public TimeSpan StackExchangeRedisCleanupCycle => _configuration.StackExchangeRedisCleanupCycle;
646+
644647
public IReadOnlyDictionary<string, string> GetAppSettings()
645648
{
646649
return _configuration.GetAppSettings();

src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs

+1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ public static void RegisterServices(IContainer container)
203203
container.Register<INativeMethods, LinuxNativeMethods>();
204204
}
205205
container.Register<NewRelicCore.DistributedTracing.ITracePriorityManager, NewRelicCore.DistributedTracing.TracePriorityManager>();
206+
container.Register<NewRelic.Agent.Api.Experimental.ISimpleSchedulingService, SimpleSchedulingService>();
206207

207208
container.Register<UpdatedLoadedModulesService, UpdatedLoadedModulesService>();
208209

src/Agent/NewRelic/Agent/Core/Segments/NoOpSegment.cs

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class NoOpSegment : ISegment, ISegmentExperimental, ISegmentDataState
1818

1919
private readonly IAttributeDefinitions _attribDefs = new AttributeDefinitions(new AttributeFilter(new AttributeFilter.Settings()));
2020

21+
public bool IsDone => true; // the segment is technically done since it is does nothing.
2122
public bool IsValid => false;
2223
public bool DurationShouldBeDeductedFromParent { get; set; } = false;
2324
public bool AlwaysDeductChildDuration { private get; set; } = false;
@@ -64,5 +65,10 @@ public ISpan SetName(string name)
6465
{
6566
return this;
6667
}
68+
69+
public string GetCategory()
70+
{
71+
return string.Empty;
72+
}
6773
}
6874
}

src/Agent/NewRelic/Agent/Core/Segments/Segment.cs

+14-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System.Diagnostics;
1818
using NewRelic.Agent.Core.Configuration;
1919
using NewRelic.Agent.Core.Utils;
20+
using NewRelic.Agent.Extensions.Providers.Wrapper;
2021

2122
namespace NewRelic.Agent.Core.Segments
2223
{
@@ -106,7 +107,13 @@ public Segment(TimeSpan relativeStartTime, TimeSpan? duration, Segment segment,
106107
SpanId = segment.SpanId;
107108
}
108109

110+
public bool IsDone
111+
{
112+
get { return RelativeEndTime.HasValue; }
113+
}
114+
109115
public bool IsValid => true;
116+
110117
public bool DurationShouldBeDeductedFromParent { get; set; } = false;
111118

112119
public bool AlwaysDeductChildDuration { private get; set; } = false;
@@ -138,14 +145,11 @@ public void End()
138145
// this segment may have already been forced to end
139146
if (RelativeEndTime.HasValue == false)
140147
{
148+
// This order is to ensure the segment end time is correct, but also not mark the segment as IsDone so that CleanUp ignores it.
141149
var endTime = _transactionSegmentState.GetRelativeTime();
150+
Agent.Instance.StackExchangeRedisCache?.Harvest(this);
142151
RelativeEndTime = endTime;
143152

144-
if (Agent.Instance.StackExchangeRedisCache != null)
145-
{
146-
Agent.Instance.StackExchangeRedisCache.Harvest(this);
147-
}
148-
149153
Finish();
150154

151155
_transactionSegmentState.CallStackPop(this, true);
@@ -432,5 +436,10 @@ public ISpan SetName(string name)
432436
SegmentNameOverride = name;
433437
return this;
434438
}
439+
440+
public string GetCategory()
441+
{
442+
return EnumNameCache<SpanCategory>.GetName(Data.SpanCategory);
443+
}
435444
}
436445
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using NewRelic.Agent.Api.Experimental;
7+
using NewRelic.Agent.Core.Utilities;
8+
9+
namespace NewRelic.Agent.Core.Time
10+
{
11+
public class SimpleSchedulingService : DisposableService, ISimpleSchedulingService
12+
{
13+
private readonly IScheduler _scheduler;
14+
private readonly List<Action> _executingActions;
15+
16+
public SimpleSchedulingService(IScheduler scheduler)
17+
{
18+
_scheduler = scheduler;
19+
_executingActions = new List<Action>();
20+
}
21+
22+
public void StartExecuteEvery(Action action, TimeSpan timeBetweenExecutions, TimeSpan? optionalInitialDelay = null)
23+
{
24+
_scheduler.ExecuteEvery(action, timeBetweenExecutions, optionalInitialDelay);
25+
_executingActions.Add(action);
26+
}
27+
28+
public void StopExecuting(Action action)
29+
{
30+
_scheduler.StopExecuting(action);
31+
_executingActions.Remove(action);
32+
}
33+
34+
public override void Dispose()
35+
{
36+
foreach (var executingAction in _executingActions)
37+
{
38+
_scheduler.StopExecuting(executingAction);
39+
}
40+
41+
_executingActions.Clear();
42+
43+
base.Dispose();
44+
}
45+
}
46+
}

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/IAgentExperimental.cs

+2
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,7 @@ public interface IAgentExperimental
3333
void RecordLogMessage(string frameworkName, object logEvent, Func<object,DateTime> getTimestamp, Func<object,object> getLogLevel, Func<object,string> getLogMessage, Func<object, Exception> getLogException, Func<object, Dictionary<string, object>> getContextData, string spanId, string traceId);
3434

3535
Extensions.Helpers.IStackExchangeRedisCache StackExchangeRedisCache { get; set; }
36+
37+
ISimpleSchedulingService SimpleSchedulingService { get; }
3638
}
3739
}

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/Experimental/ISegmentExperimental.cs

+10
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,15 @@ public interface ISegmentExperimental
4444
/// </summary>
4545
string UserCodeNamespace { get; set; }
4646

47+
/// <summary>
48+
/// Returns the category of the segment.
49+
/// </summary>
50+
/// <returns>Category of the segment.</returns>
51+
string GetCategory();
52+
53+
/// <summary>
54+
/// Will be true if a relative end time has been set on the segment. In most situations, this is only set when a segment is ended.
55+
/// </summary>
56+
bool IsDone { get; }
4757
}
4858
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
6+
namespace NewRelic.Agent.Api.Experimental
7+
{
8+
public interface ISimpleSchedulingService
9+
{
10+
void StartExecuteEvery(Action action, TimeSpan timeBetweenExecutions, TimeSpan? optionalInitialDelay = null);
11+
12+
void StopExecuting(Action action);
13+
}
14+
}

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Configuration/IConfiguration.cs

+1
Original file line numberDiff line numberDiff line change
@@ -203,5 +203,6 @@ public interface IConfiguration
203203
TimeSpan DefaultHarvestCycle { get; }
204204
TimeSpan SqlTracesHarvestCycle { get; }
205205
TimeSpan UpdateLoadedModulesCycle { get; }
206+
TimeSpan StackExchangeRedisCleanupCycle { get; }
206207
}
207208
}

0 commit comments

Comments
 (0)