Skip to content

Commit 9b66750

Browse files
authored
fix: Improve error handling in AWS account ID parsing logic (#2984)
1 parent 7ee9825 commit 9b66750

File tree

3 files changed

+85
-32
lines changed

3 files changed

+85
-32
lines changed

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Caching/WeakReferenceKey.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ namespace NewRelic.Agent.Extensions.Caching
1111
/// <typeparam name="T"></typeparam>
1212
public class WeakReferenceKey<T> where T : class
1313
{
14+
private readonly int _hashCode;
1415
private WeakReference<T> WeakReference { get; }
1516

1617
public WeakReferenceKey(T cacheKey)
1718
{
1819
WeakReference = new WeakReference<T>(cacheKey);
20+
_hashCode = cacheKey.GetHashCode(); // store the hashcode since we use it in the Equals method and the object could have been GC'd by the time we need to look for it
1921
}
2022

2123
public override bool Equals(object obj)
@@ -27,19 +29,17 @@ public override bool Equals(object obj)
2729
{
2830
return ReferenceEquals(thisTarget, otherTarget);
2931
}
32+
33+
// if one of the targets has been garbage collected, we can still compare the hashcodes
34+
return otherKey.GetHashCode() == _hashCode;
3035
}
3136

3237
return false;
3338
}
3439

3540
public override int GetHashCode()
3641
{
37-
if (WeakReference.TryGetTarget(out var target))
38-
{
39-
return target.GetHashCode();
40-
}
41-
42-
return 0;
42+
return WeakReference.TryGetTarget(out var target) ? target.GetHashCode() : _hashCode;
4343
}
4444

4545
/// <summary>

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AwsSdk/AmazonServiceClientWrapper.cs

+59-8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace NewRelic.Providers.Wrapper.AwsSdk;
1111

1212
public class AmazonServiceClientWrapper : IWrapper
1313
{
14+
private bool _disableServiceClientWrapper;
15+
1416
private const int LRUCapacity = 100;
1517
// cache the account id per instance of AmazonServiceClient.Config
1618
public static LRUCache<WeakReferenceKey<object>, string> AwsAccountIdByClientConfigCache = new(LRUCapacity);
@@ -27,32 +29,81 @@ public CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo)
2729

2830
public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
2931
{
32+
if (_disableServiceClientWrapper) // something bad happened on a previous call, so we're disabling this wrapper
33+
return Delegates.NoOp;
34+
3035
object client = instrumentedMethodCall.MethodCall.InvocationTarget;
3136

3237
var weakReferenceKey = new WeakReferenceKey<object>(client);
33-
if (AmazonServiceClientInstanceCache.Contains(weakReferenceKey)) // don't do anything if we've already seen this client instance
38+
39+
// don't do anything if we've already seen this client instance -- the account ID is cached already
40+
if (AmazonServiceClientInstanceCache.Contains(weakReferenceKey))
3441
return Delegates.NoOp;
3542

43+
// add this client instance to cache so we don't process it again
3644
AmazonServiceClientInstanceCache.Add(weakReferenceKey);
3745

38-
string awsAccountId;
46+
// retrieve and cache the account id
47+
string awsAccountId = null;
3948
try
4049
{
4150
// get the AWSCredentials parameter
42-
dynamic awsCredentials = instrumentedMethodCall.MethodCall.MethodArguments[0];
51+
if (instrumentedMethodCall.MethodCall.MethodArguments.Length > 0)
52+
{
53+
dynamic awsCredentials = instrumentedMethodCall.MethodCall.MethodArguments[0];
54+
if (awsCredentials != null)
55+
{
56+
dynamic immutableCredentials = awsCredentials.GetCredentials();
57+
if (immutableCredentials != null)
58+
{
59+
string accessKey = immutableCredentials.AccessKey;
4360

44-
dynamic immutableCredentials = awsCredentials.GetCredentials();
45-
string accessKey = immutableCredentials.AccessKey;
61+
if (!string.IsNullOrEmpty(accessKey))
62+
{
63+
try
64+
{
65+
// convert the access key to an account id
66+
awsAccountId = AwsAccountIdDecoder.GetAccountId(accessKey);
67+
}
68+
catch (Exception e)
69+
{
70+
agent.Logger.Debug(e, "Unexpected exception parsing AWS Account ID from AccessKey.");
71+
}
72+
}
73+
else
74+
agent.Logger.Debug("Unable to parse AWS Account ID from AWSCredentials because AccessKey was null.");
75+
}
76+
else
77+
agent.Logger.Debug("Unable to parse AWS Account ID from AWSCredentials because GetCredentials() returned null.");
78+
}
79+
else
80+
agent.Logger.Debug("Unable to parse AWS Account ID from AWSCredentials because AWSCredentials was null.");
81+
}
82+
else
83+
agent.Logger.Debug("Unable to parse AWS Account ID from AWSCredentials because there were no arguments in the method call.");
4684

47-
// convert the access key to an account id
48-
awsAccountId = AwsAccountIdDecoder.GetAccountId(accessKey);
85+
// fall back to configuration if we didn't get an account id from the credentials
86+
if (string.IsNullOrEmpty(awsAccountId))
87+
{
88+
agent.Logger.Debug("Using AccountId from configuration.");
89+
awsAccountId = agent.Configuration.AwsAccountId;
90+
}
4991
}
5092
catch (Exception e)
5193
{
52-
agent.Logger.Info($"Unable to parse AWS Account ID from AccessKey. Using AccountId from configuration instead. Exception: {e.Message}");
94+
agent.Logger.Debug(e, "Unexpected exception in AmazonServiceClientWrapper.BeforeWrappedMethod(). Using AccountId from configuration.");
5395
awsAccountId = agent.Configuration.AwsAccountId;
5496
}
5597

98+
// disable the wrapper if we get this far and there's no account id
99+
if (string.IsNullOrEmpty(awsAccountId))
100+
{
101+
agent.Logger.Warn("Unable to parse AWS Account ID from AWSCredentials or configuration. Further AWS Account ID parsing will be disabled.");
102+
_disableServiceClientWrapper = true;
103+
104+
return Delegates.NoOp;
105+
}
106+
56107
return Delegates.GetDelegateFor(onComplete: () =>
57108
{
58109
// get the _config field from the client

tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/Cache/WeakReferenceKeyTests.cs

+20-18
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,6 @@ public void GetHashCode_ShouldReturnDifferentHashCodeForDifferentObjects()
9393
Assert.That(hashCode1, Is.Not.EqualTo(hashCode2));
9494
}
9595

96-
[Test]
97-
public async Task GetHashCode_ShouldReturnZeroIfTargetIsGarbageCollected()
98-
{
99-
// Arrange
100-
var weakRefKey = GetWeakReferenceKey();
101-
102-
// Act
103-
Assert.That(weakRefKey.Value, Is.Not.Null);
104-
// force garbage collection
105-
GC.Collect();
106-
GC.WaitForPendingFinalizers();
107-
await Task.Delay(500);
108-
GC.Collect(); // Force another collection
109-
110-
// Assert
111-
Assert.That(weakRefKey.GetHashCode(), Is.EqualTo(0));
112-
}
113-
11496
[Test]
11597
public async Task Value_ShouldReturnNullIfTargetIsGarbageCollected()
11698
{
@@ -168,6 +150,26 @@ public async Task Equals_ShouldReturnFalseIfTargetIsGarbageCollected()
168150
Assert.That(weakRefKey1.Equals(weakRefKey2), Is.False);
169151
}
170152

153+
[Test]
154+
public async Task GetHashCode_ShouldReturnOriginalHashcodeIfTargetIsGarbageCollected()
155+
{
156+
// Arrange
157+
var weakRefKey = GetWeakReferenceKey();
158+
var originalHashCode = weakRefKey.GetHashCode();
159+
160+
// Act
161+
Assert.That(weakRefKey.Value, Is.Not.Null);
162+
163+
// force garbage collection
164+
GC.Collect();
165+
GC.WaitForPendingFinalizers();
166+
await Task.Delay(500);
167+
GC.Collect(); // Force another collection
168+
169+
// Assert
170+
Assert.That(weakRefKey.Value, Is.Null); // ensure GC really happened
171+
Assert.That(weakRefKey.GetHashCode(), Is.EqualTo(originalHashCode));
172+
}
171173
private class Foo
172174
{
173175
public string Bar { get; set; }

0 commit comments

Comments
 (0)