generated from amazon-archives/__template_Apache-2.0
-
Notifications
You must be signed in to change notification settings - Fork 34
Open
Labels
area/loggingCore logging utilityCore logging utilitybugUnexpected, reproducible and unintended software behaviourUnexpected, reproducible and unintended software behaviour
Description
Expected Behaviour
The Logger should be thread-safe when multiple concurrent tasks perform logging operations and context key manipulations (AppendKey, RemoveKey, GetAllKeys) simultaneously.
Current Behaviour
The static Scope dictionary in Logger.Scope.cs (line 15) is not thread-safe, causing exceptions when accessed concurrently:
- InvalidOperationException: "Collection was modified; enumeration operation may not execute" - occurs during
foreachenumeration ofGetAllKeys() - ArgumentException: "Destination array is not long enough" - occurs when calling
GetAllKeys().ToArray()
These exceptions occur in production Lambda environments when concurrent tasks use the logger (e.g., Task.WhenAll scenarios).
Real-World Stack Trace
AggregateException: An error occurred while writing to logger(s). (Collection was modified; enumeration operation may not execute.)
at Microsoft.Extensions.Logging.Logger.ThrowLoggingError(List`1 exceptions)
at AWS.Lambda.Powertools.Logging.Internal.PowertoolsLogger.GetLogEntry(...)
→ Line 229: foreach (var (key, value) in this.GetAllKeys())
Root Cause
Logger.Scope.cs line 15:
private static IDictionary<string, object> Scope { get; } = new Dictionary<string, object>(StringComparer.Ordinal);Race Condition:
- Thread A: Enumerates
ScopeviaGetAllKeys()(called during logging atPowertoolsLogger.GetLogEntry()line 229) - Thread B: Modifies
ScopeviaAppendKey(),RemoveKey(), orRemoveKeys() - Result:
Dictionary<TKey, TValue>is not thread-safe for concurrent read/write operations
Impact
- Production Lambda failures when concurrent operations use the logger
- Intermittent, difficult to debug due to race condition timing
- Affects any Lambda using concurrent tasks (common pattern for staying under API Gateway 29s timeout)
Code snippet
### Minimal Reproduction Test
[TestMethod]
public async Task ConcurrentAccess_ForeachOnGetAllKeys_ThrowsInvalidOperationException()
{
Logger.RemoveKeys(Logger.GetAllKeys()?.Select(x => x.Key).ToArray() ?? []);
var tasks = new List<Task>
{
// Thread 1: Enumerate (mimics GetLogEntry line 229)
Task.Run(() => {
for (int i = 0; i < 100; i++)
foreach (var kvp in Logger.GetAllKeys()) { }
}),
// Thread 2: Log (also enumerates internally)
Task.Run(() => {
for (int i = 0; i < 100; i++)
Logger.LogInformation($"Iteration {i}");
}),
// Thread 3: Modify keys
Task.Run(() => {
for (int i = 0; i < 100; i++) {
Logger.AppendKey($"key_{i % 10}", i);
Logger.RemoveKey($"key_{(i-1) % 10}");
}
})
};
await Task.WhenAll(tasks);
}Possible Solution
- Use
ConcurrentDictionary<string, object>instead ofDictionary<string, object>for the staticScopeproperty - Add locking around all
Scopeaccess (reads and writes) inLogger.Scope.cs - Use
ThreadLocal<Dictionary<string, object>>if context should be thread-specific rather than shared
Steps to Reproduce
- Create a Lambda function or test that uses
Task.WhenAll()to run concurrent operations - Have multiple tasks simultaneously:
- Call logging methods (
LogInformation,LogDebug, etc.) - Call
AppendKey()orRemoveKey()to manipulate context
- Call logging methods (
- Run the test repeatedly (race condition may not trigger every time)
- Observe
InvalidOperationExceptionorArgumentException
Powertools for AWS Lambda (.NET) version
latest (3.0.2)
AWS Lambda function runtime
dotnet8
Debugging logs
hjgraca
Metadata
Metadata
Assignees
Labels
area/loggingCore logging utilityCore logging utilitybugUnexpected, reproducible and unintended software behaviourUnexpected, reproducible and unintended software behaviour
Type
Projects
Status
👀 In review