List operators drop OnCompleted and mishandle OnError
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
调研方向
Start by reproducing the ToCollection() completion failure described in the issue, then trace the affected operators and overloads listed under “Never completes” and “Error path” to their implementations. The issue provides a harness method that exercises empty and one-changeset sources with completion and error signals; use it to verify each behavior. Done means the listed overloads forward terminal events correctly and regression tests cover the cases.
由索引模型根据 Issue 内容生成。
描述
While fixing Switch (#1136, #1137) I wanted to know whether that was a one off or the tip of something, so I wrote a harness that drives every operator overload with a source that either completes or errors, and records what actually comes out the other end.
It reached 476 of the 499 extension methods that hang off IObservable. The rest do not return an observable (AsObservableCache, the Bind overloads returning IDisposable, ChangeSetAggregator and so on) so there is nothing to assert about.
Two things are worth saying before the list.
Overloads of the same operator do not behave alike. Sort is the clearest case:
source.Sort(comparer) // completes
source.Sort(Observable.Return(comparer)) // never completes
source.Sort(comparer, resorter) // never completes
Testing one overload and assuming the rest are fine would have missed most of this, so every overload was driven separately.
"Never completes" means with an empty source. An operator that merges a stream per item is entitled to stay open while those streams are open, so I checked completion against a source that never produced an item. Nothing can legitimately block completion in that case. AutoRefresh, WhenAnyPropertyChanged and GroupOnPropertyWithImmutableState all complete on an empty source and are not listed here.
On the list side 65 overloads either never complete or get the error path wrong. Grouped by the implementation they share.
Repro
var source = new SourceList<Person>();
var completed = false;
using var sub = source.Connect()
.ToCollection()
.Subscribe(_ => { }, () => completed = true);
source.Dispose();
// completed is false
Never completes
BatchIf / BufferIf
BufferIf(IObservable<bool>, bool, IScheduler)
BufferIf(IObservable<bool>, bool, TimeSpan?, IScheduler)
BufferIf(IObservable<bool>, IScheduler)
BufferIf(IObservable<bool>, TimeSpan?, IScheduler)
Combiner (Combine)
And(params IObservable<IChangeSet<TObject, TKey>>[])
Except(params IObservable<IChangeSet<TObject, TKey>>[])
Or(params IObservable<IChangeSet<TObject, TKey>>[])
Xor(params IObservable<IChangeSet<TObject, TKey>>[])
DeferUntilLoaded
DeferUntilLoaded()
SkipInitial()
Grouping
GroupOn(Func<IObservable<IChangeSet<TObject,TKey>>,TObject>, IObservable<Unit>)
GroupOn(Func<IObservable<IChangeSet<TObject>>,TObject>, IObservable<Unit>)
GroupOn(Func<Object,TObject>, IObservable<Unit>)
GroupOn(Func<TObject,TObject>, IObservable<Unit>)
GroupOnProperty(Expression<Func<TObject,TObject>>, TimeSpan?, IScheduler)
GroupOnPropertyWithImmutableState(Expression<Func<TObject,TObject>>, TimeSpan?, IScheduler)
GroupWithImmutableState(Func<IObservable<IChangeSet<TObject,TKey>>,TKey>, IObservable<Unit>)
GroupWithImmutableState(Func<IObservable<IChangeSet<TObject>>,TKey>, IObservable<Unit>)
GroupWithImmutableState(Func<Object,TKey>, IObservable<Unit>)
GroupWithImmutableState(Func<TObject,TKey>, IObservable<Unit>)
MergeChangeSets
MergeChangeSets(IEnumerable<IObservable<IChangeSet<IObservable<IChangeSet<TObject,TKey>>>>>, IEqualityComparer<IObservable<IChangeSet<TObject,TKey>>>, IScheduler, bool)
MergeChangeSets(IEnumerable<IObservable<IChangeSet<IObservable<IChangeSet<TObject>>>>>, IEqualityComparer<IObservable<IChangeSet<TObject>>>, IScheduler, bool)
MergeChangeSets(IEnumerable<IObservable<IChangeSet<Object>>>, IEqualityComparer<Object>, IScheduler, bool)
MergeChangeSets(IEnumerable<IObservable<IChangeSet<TObject>>>, IEqualityComparer<TObject>, IScheduler, bool)
MergeChangeSets(IObservable<IChangeSet<IObservable<IChangeSet<TObject,TKey>>>>, IEqualityComparer<IObservable<IChangeSet<TObject,TKey>>>, IScheduler, bool)
MergeChangeSets(IObservable<IChangeSet<IObservable<IChangeSet<TObject>>>>, IEqualityComparer<IObservable<IChangeSet<TObject>>>, IScheduler, bool)
MergeChangeSets(IObservable<IChangeSet<Object>>, IEqualityComparer<Object>, IScheduler, bool)
MergeChangeSets(IObservable<IChangeSet<TObject>>, IEqualityComparer<TObject>, IScheduler, bool)
MonitorStatus
MonitorStatus()
QueryWhenChanged
QueryWhenChanged()
QueryWhenChanged(Func<IReadOnlyCollection<IObservable<IChangeSet<TObject,TKey>>>,TObject>)
QueryWhenChanged(Func<IReadOnlyCollection<IObservable<IChangeSet<TObject>>>,TObject>)
QueryWhenChanged(Func<IReadOnlyCollection<Object>,TObject>)
QueryWhenChanged(Func<IReadOnlyCollection<TObject>,TObject>)
Switch (already covered by #1137 / #1139)
Switch()
ToCollection / ToSortedCollection
ToCollection()
ToSortedCollection(Func<IObservable<IChangeSet<TObject,TKey>>,TKey>, SortDirection)
ToSortedCollection(Func<IObservable<IChangeSet<TObject>>,TKey>, SortDirection)
ToSortedCollection(Func<Object,TKey>, SortDirection)
ToSortedCollection(Func<TObject,TKey>, SortDirection)
ToSortedCollection(IComparer<IObservable<IChangeSet<TObject,TKey>>>)
ToSortedCollection(IComparer<IObservable<IChangeSet<TObject>>>)
ToSortedCollection(IComparer<Object>)
ToSortedCollection(IComparer<TObject>)
Error path
Aggregation Scan
Maximum(Func<IObservable<IChangeSet<TObject,TKey>>,int>, int)
Maximum(Func<IObservable<IChangeSet<TObject>>,int>, int)
Maximum(Func<Object,int>, int)
Maximum(Func<TObject,int>, int)
Minimum(Func<IObservable<IChangeSet<TObject,TKey>>,int>, int)
Minimum(Func<IObservable<IChangeSet<TObject>>,int>, int)
Minimum(Func<Object,int>, int)
Minimum(Func<TObject,int>, int)
MergeChangeSets
MergeChangeSets(IEqualityComparer<TObject>, IComparer<TObject>)
MergeManyChangeSets
MergeManyChangeSets(Func<IObservable<IChangeSet<TObject,TKey>>,IObservable<IChangeSet<TObject,TKey>>>, IComparer<TObject>)
MergeManyChangeSets(Func<IObservable<IChangeSet<TObject,TKey>>,IObservable<IChangeSet<TObject,TKey>>>, IEqualityComparer<TObject>, IComparer<TObject>)
MergeManyChangeSets(Func<IObservable<IChangeSet<TObject,TKey>>,IObservable<IChangeSet<TObject>>>, IEqualityComparer<TObject>)
MergeManyChangeSets(Func<IObservable<IChangeSet<TObject>>,IObservable<IChangeSet<TObject,TKey>>>, IComparer<TObject>)
MergeManyChangeSets(Func<IObservable<IChangeSet<TObject>>,IObservable<IChangeSet<TObject,TKey>>>, IEqualityComparer<TObject>, IComparer<TObject>)
MergeManyChangeSets(Func<IObservable<IChangeSet<TObject>>,IObservable<IChangeSet<TObject>>>, IEqualityComparer<TObject>)
MergeManyChangeSets(Func<Object,IObservable<IChangeSet<TObject,TKey>>>, IComparer<TObject>)
MergeManyChangeSets(Func<Object,IObservable<IChangeSet<TObject,TKey>>>, IEqualityComparer<TObject>, IComparer<TObject>)
MergeManyChangeSets(Func<Object,IObservable<IChangeSet<TObject>>>, IEqualityComparer<TObject>)
MergeManyChangeSets(Func<TObject,IObservable<IChangeSet<TObject,TKey>>>, IComparer<TObject>)
MergeManyChangeSets(Func<TObject,IObservable<IChangeSet<TObject,TKey>>>, IEqualityComparer<TObject>, IComparer<TObject>)
MergeManyChangeSets(Func<TObject,IObservable<IChangeSet<TObject>>>, IEqualityComparer<TObject>)
Why this matters
Anything that waits for a terminal event breaks against these: TakeUntil, Concat, LastAsync, ToTask, Amb. The completion pattern in the repo's own testing guidance (Publish, then LastOrDefaultAsync().ToTask(), then dispose the source) hangs forever rather than failing, so tests written that way around these operators are not asserting what they look like they assert.
The error cases are worse than the completion ones. Where an operator throws out of Subscribe instead of calling OnError, the exception surfaces on whichever thread happened to be writing to the source, which is usually nowhere near the subscription. Where the error is swallowed entirely the subscriber simply never learns the stream is dead.
Method
Each overload was subscribed and then given, in turn: OnCompleted with no items, OnError with no items, one changeset followed by OnCompleted, one changeset followed by OnError, and a dispose followed by more writes. Terminal events were delivered both during subscription and afterwards, since several of these only misbehave when the terminal event arrives synchronously, which is what Switch did.
- 主要语言
- C#
- 星标
- 1.9k
- 派生
- 194
- 平均合并
- 12 分钟
- 30 天内合并 PR
- 1
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
reactivemarbles/DynamicData 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 68/100
reactivemarbles/DynamicData#1229 ·
维护者通常 1 天内回复
-
infrastructure
难度 2/5 1-3 小时 新手友好度 64/100
reactivemarbles/DynamicData#1089 · 1 条评论 ·
维护者通常 1 天内回复
-
[Bug]: ChangeAwareList merges an insert one slot before a pending AddRange, corrupting list order downstream可能已有人在做 @dwcullop 于 9 天前认领。 未关闭bug
难度 3/5 半天 新手友好度 63/100
reactivemarbles/DynamicData#1225 ·
维护者通常 1 天内回复
-
[Feature]: Make Avg, StdDev, Maximum and Minimum refresh-aware, consistent with Sum可能已有人在做 @dwcullop 于 14 天前认领。 未关闭breaking change enhancement
难度 5/5 一周以上 新手友好度 20/100
reactivemarbles/DynamicData#1214 ·
维护者通常 1 天内回复
-
bug
难度 5/5 一周以上 新手友好度 35/100
reactivemarbles/DynamicData#1196 ·
维护者通常 1 天内回复
查看 reactivemarbles/DynamicData 的全部 Issue
相似的 Issue
-
bug
难度 2/5 1-3 小时 新手友好度 68/100
immichFrame/ImmichFrame#735 ·
-
bug
难度 2/5 1-3 小时 新手友好度 78/100
dimitar-radenkov/Pointframe#318 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 68/100
DotHarness/dotcraft#546 ·
维护者通常 1 天内回复
-
a-corriger bug
难度 2/5 1-3 小时 新手友好度 78/100
christian-raj/S-Aloha#303 · 1 条评论 ·
-
Input editor says "Apply Changes" even when no changes have been made.可能已有人在做 @hcooper 今天认领。 未关闭
难度 1/5 1 小时以内 新手友好度 72/100
MobiFlight/MobiFlight-Connector#3526 ·
维护者通常 1 天内回复