Skip to content

Commit 546f3ad

Browse files
authored
Added weak-referencing to operators using background scheduling, to ensure that schedulers do not leak the operator subscriptions. (#1027)
1 parent 7bc76f0 commit 546f3ad

File tree

5 files changed

+49
-19
lines changed

5 files changed

+49
-19
lines changed

src/DynamicData/Cache/Internal/ExpireAfter.ForSource.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,15 @@ private void OnExpirationsChanged()
204204
_nextScheduledManagement = new()
205205
{
206206
Cancellation = _scheduler.Schedule(
207-
state: this,
207+
state: new WeakReference<SubscriptionBase>(this),
208208
dueTime: nextManagementDueTime,
209-
action: static (_, @this) =>
209+
action: static (_, thisReference) =>
210210
{
211-
@this.ManageExpirations();
211+
// Most schedulers won't clear scheduled actions upon cancellation, they'll wait until they were supposed to occur.
212+
// A WeakReference here prevents the whole subscription from memory leaking
213+
// Refer to https://github.com/reactivemarbles/DynamicData/issues/1025
214+
if (thisReference.TryGetTarget(out var @this))
215+
@this.ManageExpirations();
212216

213217
return Disposable.Empty;
214218
}),

src/DynamicData/Cache/Internal/ExpireAfter.ForStream.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,15 @@ private void OnExpirationsChanged()
182182
_nextScheduledManagement = new()
183183
{
184184
Cancellation = _scheduler.Schedule(
185-
state: this,
185+
state: new WeakReference<SubscriptionBase>(this),
186186
dueTime: nextManagementDueTime,
187-
action: (_, @this) =>
187+
action: (_, thisReference) =>
188188
{
189-
@this.ManageExpirations();
189+
// Most schedulers won't clear scheduled actions upon cancellation, they'll wait until they were supposed to occur.
190+
// A WeakReference here prevents the whole subscription from memory leaking
191+
// Refer to https://github.com/reactivemarbles/DynamicData/issues/1025
192+
if (thisReference.TryGetTarget(out var @this))
193+
@this.ManageExpirations();
190194

191195
return Disposable.Empty;
192196
}),

src/DynamicData/Cache/Internal/ToObservableChangeSet.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,7 @@ public void Dispose()
114114
_scheduledExpiration?.Cancellation.Dispose();
115115
}
116116

117-
private IDisposable OnScheduledExpirationInvoked(
118-
IScheduler scheduler,
119-
Expiration intendedExpiration)
117+
private IDisposable OnScheduledExpirationInvoked(Expiration intendedExpiration)
120118
{
121119
try
122120
{
@@ -256,9 +254,20 @@ private void FinishSchedulingExpiration(
256254
ScheduledExpiration unfinishedExpiration,
257255
IScheduler scheduler)
258256
=> unfinishedExpiration.Cancellation.Disposable = scheduler.Schedule(
259-
state: unfinishedExpiration.Expiration,
257+
state: (
258+
thisReference: new WeakReference<Subscription>(this),
259+
expiration: unfinishedExpiration.Expiration),
260260
dueTime: unfinishedExpiration.Expiration.ExpireAt,
261-
action: OnScheduledExpirationInvoked);
261+
action: static (_, state) =>
262+
{
263+
// Most schedulers won't clear scheduled actions upon cancellation, they'll wait until they were supposed to occur.
264+
// A WeakReference here prevents the whole subscription from memory leaking
265+
// Refer to https://github.com/reactivemarbles/DynamicData/issues/1025
266+
if (state.thisReference.TryGetTarget(out var @this))
267+
@this.OnScheduledExpirationInvoked(state.expiration);
268+
269+
return Disposable.Empty;
270+
});
262271

263272
private void TryPublishCompletion()
264273
{

src/DynamicData/List/Internal/ExpireAfter.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,15 @@ private void OnExpirationDueTimesChanged()
197197
_nextScheduledManagement = new()
198198
{
199199
Cancellation = _scheduler.Schedule(
200-
state: this,
200+
state: new WeakReference<SubscriptionBase>(this),
201201
dueTime: nextManagementDueTime,
202-
action: (_, @this) =>
202+
action: (_, thisReference) =>
203203
{
204-
@this.ManageExpirations();
204+
// Most schedulers won't clear scheduled actions upon cancellation, they'll wait until they were supposed to occur.
205+
// A WeakReference here prevents the whole subscription from memory leaking
206+
// Refer to https://github.com/reactivemarbles/DynamicData/issues/1025
207+
if (thisReference.TryGetTarget(out var @this))
208+
@this.ManageExpirations();
205209

206210
return Disposable.Empty;
207211
}),

src/DynamicData/List/Internal/ToObservableChangeSet.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,7 @@ public void Dispose()
110110
_scheduledExpiration?.Cancellation.Dispose();
111111
}
112112

113-
private IDisposable OnScheduledExpirationInvoked(
114-
IScheduler scheduler,
115-
Expiration intendedExpiration)
113+
private IDisposable OnScheduledExpirationInvoked(Expiration intendedExpiration)
116114
{
117115
try
118116
{
@@ -290,9 +288,20 @@ private void FinishSchedulingExpiration(
290288
ScheduledExpiration unfinishedExpiration,
291289
IScheduler scheduler)
292290
=> unfinishedExpiration.Cancellation.Disposable = scheduler.Schedule(
293-
state: unfinishedExpiration.Expiration,
291+
state: (
292+
thisReference: new WeakReference<Subscription>(this),
293+
expiration: unfinishedExpiration.Expiration),
294294
dueTime: unfinishedExpiration.Expiration.ExpireAt,
295-
action: OnScheduledExpirationInvoked);
295+
action: static (_, state) =>
296+
{
297+
// Most schedulers won't clear scheduled actions upon cancellation, they'll wait until they were supposed to occur.
298+
// A WeakReference here prevents the whole subscription from memory leaking
299+
// Refer to https://github.com/reactivemarbles/DynamicData/issues/1025
300+
if (state.thisReference.TryGetTarget(out var @this))
301+
@this.OnScheduledExpirationInvoked(state.expiration);
302+
303+
return Disposable.Empty;
304+
});
296305

297306
private void TryPublishCompletion()
298307
{

0 commit comments

Comments
 (0)