No such courtesy exists. The RX design guideines in section 4.3 suggest you can:
Assume resources are cleaned up after an OnError or OnCompleted message.
And in section 4.4 say you can:
Assume a best effort to stop all outstanding work on Unsubscribe
These guidelines ("courtesies") talk about an operator releasing it's own resources plus those of any it has acquired as soon as possible.
In your code, you aren't testing for either of these scenarios. The purpose of the Unsubscribe
property on an ITestableObservable
is to report when a subscription taken out by an observer was explicitly disposed, not when internal cleanup happened - but you are not storing this handle to be able to dispose it:
xs.Subscribe(ob); /* return of handle ignored here */
So you are trying to assert that you disposed the subscription you threw away, not that the observable you subscribed to cleaned up any subscription and resources it may have taken out.
If you want to see the effect of the timely resource clean up of 4.3/4.4, write an extension method like this:
public static IObservable<T> SpyResourceCleanUp<T>(
this IObservable<T> source, IScheduler scheduler)
{
return Observable.Create<T>(obs =>
{
var subscription = source.Subscribe(obs);
return new CompositeDisposable(
subscription,
Disposable.Create(() => Console.WriteLine(
"Clean up performed at " + scheduler.Now.Ticks)));
});
}
And replace your line:
xs.Subscribe(ob);
with
xs.SpyResourceCleanUp(ts).Subscribe(ob);
(Editing in some of the comments)
On your test I see immediate resource clean-up, as I would expect. And with this change your test will now pass because SpyResourceCleanUp
is unsubscribing from it's parent (xs) as soon as it OnCompletes() itself in adherence to 4.3 of the guidelines.
What might not be obvious here is that Observable.Create
handles calling the Dispose()
method of the returned IDisposable
as soon as either the subscription is disposed or OnComplete()
or OnError()
has been called on the observer. This is how Create
helps you implement section 4.3, and why the test passes with the altered code.
Under the covers, subscriptions to the AnonymousObservable<T> : ObservableBase<T>
returned by Create
are wrapped by an AutoDetachObserver
as you can see here.
i.e. The Disposable
you return from Observable.Create
isn't the one the caller gets - they get a wrapped version that will call your Dispose() either on stream termination or cancellation.