4.x: Make TailRecursiveSink lock-free and have less allocations #499
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR changes
TailRecursiveSink
to a lock-free algorithm as well as reduces the allocations in the class by inlining variousIDisposable
fields.The aim of the class is to make sure
OnComplete
orOnError
inConcat
,Catch
and various other operators don't trigger an infinite recursion but stay on the level.This could have been solved with a straightforward trampoline like this
Concat
implementation, however, there is a small complication. The recursion may be not only on the termination side, but on the existence of the innerIObservable
s.For example, to avoid yet another level of recursion due to graphs like
Concat(Concat(a, b), Concat(c, d))
, theExtract
method can get another level ofIEnumerable<IObservable<TSource>>
on the current level, and the concatenation should resume on the innerConcat
.The
Stack
is there to allow returning to an outer enumeration and resume it. I don't know what the length stack was supposed to do; the algorithm doesn't have to know how long eachIEnumerator
is. Was it some kind of optimization for Lists and arrays avoidingMoveNext() == false
and thusIEnumerator.Dispose()
? The former should be as costly as an index comparison and the latter should be no-op anyway.