Skip to content

Commit 93358ac

Browse files
guiyongGuiyong Yang (from Dev Box)kirankumarkolli
authored
NonBlockingAsyncCache: Fixes lambda func capturing the outer context (memory optimization) (#4470)
# Pull Request Template ## Description Set createValueFunc to null once the Func delegate is not needed. Without breaking the reference, the lambda function will keep capturing of any outer variables, in #4345, DocumentServiceRequest was captured, which prevent GC from recycling the request object, cause memory leak. Below code, the request object will be captured by singleValueInitFunc delegate. ``` addresses = await this.serverPartitionAddressCache.GetAsync( key: partitionKeyRangeIdentity, singleValueInitFunc: (_) => this.GetAddressesForRangeIdAsync( request, cachedAddresses: null, partitionKeyRangeIdentity.CollectionRid, partitionKeyRangeIdentity.PartitionKeyRangeId, forceRefresh: false), forceRefresh: (_) => false); ``` ## Type of change Please delete options that are not relevant. - [] Bug fix (non-breaking change which fixes an issue) ## Closing issues To automatically close an issue: closes #4345 --------- Co-authored-by: Guiyong Yang (from Dev Box) <[email protected]> Co-authored-by: Kiran Kumar Kolli <[email protected]>
1 parent 90da35f commit 93358ac

File tree

1 file changed

+7
-10
lines changed

1 file changed

+7
-10
lines changed

Microsoft.Azure.Cosmos/src/Routing/AsyncCacheNonBlocking.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public async Task<TValue> GetAsync(
9595
{
9696
try
9797
{
98-
TValue cachedResult = await initialLazyValue.GetValueAsync();
98+
TValue cachedResult = await initialLazyValue.GetValueAsync(singleValueInitFunc);
9999
if (forceRefresh == null || !forceRefresh(cachedResult))
100100
{
101101
return cachedResult;
@@ -129,23 +129,23 @@ public async Task<TValue> GetAsync(
129129
// The AsyncLazyWithRefreshTask is lazy and won't create the task until GetValue is called.
130130
// It's possible multiple threads will call the GetOrAdd for the same key. The current asyncLazy may
131131
// not be used if another thread adds it first.
132-
AsyncLazyWithRefreshTask<TValue> asyncLazy = new AsyncLazyWithRefreshTask<TValue>(singleValueInitFunc, this.cancellationTokenSource.Token);
132+
AsyncLazyWithRefreshTask<TValue> asyncLazy = new AsyncLazyWithRefreshTask<TValue>(this.cancellationTokenSource.Token);
133133
AsyncLazyWithRefreshTask<TValue> result = this.values.GetOrAdd(
134134
key,
135135
asyncLazy);
136136

137137
// Another thread async lazy was inserted. Just await on the inserted lazy object.
138138
if (!object.ReferenceEquals(asyncLazy, result))
139139
{
140-
return await result.GetValueAsync();
140+
return await result.GetValueAsync(singleValueInitFunc);
141141
}
142142

143143
// This means the current caller async lazy was inserted into the concurrent dictionary.
144144
// The task is now awaited on so if an exception occurs it can be removed from
145145
// the concurrent dictionary.
146146
try
147147
{
148-
return await result.GetValueAsync();
148+
return await result.GetValueAsync(singleValueInitFunc);
149149
}
150150
catch (Exception e)
151151
{
@@ -253,7 +253,6 @@ private async Task<TValue> UpdateCacheAndGetValueFromBackgroundTaskAsync(
253253
private sealed class AsyncLazyWithRefreshTask<T>
254254
{
255255
private readonly CancellationToken cancellationToken;
256-
private readonly Func<T, Task<T>> createValueFunc;
257256
private readonly object valueLock = new ();
258257
private readonly object removedFromCacheLock = new ();
259258

@@ -266,24 +265,22 @@ public AsyncLazyWithRefreshTask(
266265
CancellationToken cancellationToken)
267266
{
268267
this.cancellationToken = cancellationToken;
269-
this.createValueFunc = null;
270268
this.value = Task.FromResult(value);
271269
this.refreshInProgress = null;
272270
}
273271

274272
public AsyncLazyWithRefreshTask(
275-
Func<T, Task<T>> taskFactory,
276273
CancellationToken cancellationToken)
277274
{
278275
this.cancellationToken = cancellationToken;
279-
this.createValueFunc = taskFactory;
280276
this.value = null;
281277
this.refreshInProgress = null;
282278
}
283279

284280
public bool IsValueCreated => this.value != null;
285281

286-
public Task<T> GetValueAsync()
282+
public Task<T> GetValueAsync(
283+
Func<T, Task<T>> createValueFunc)
287284
{
288285
// The task was already created so just return it.
289286
Task<T> valueSnapshot = this.value;
@@ -303,7 +300,7 @@ public Task<T> GetValueAsync()
303300
}
304301

305302
this.cancellationToken.ThrowIfCancellationRequested();
306-
this.value = this.createValueFunc(default);
303+
this.value = createValueFunc(default);
307304
return this.value;
308305
}
309306
}

0 commit comments

Comments
 (0)