Skip to content

Commit baf816f

Browse files
authored
fixes stack overflow possibility with merge operators (#2616)
* fixes stackoverflows in MergeLimitedSink fix subscribing immediately in merge operators can produce values immediately which can re-enter and cause stack overflows * fix possible fail to dispose and re-lock * fixes stackoverflows in MergeLimitedSink fix subscribing immediately in merge operators can produce values immediately which can re-enter and cause stack overflows * fix possible fail to dispose and re-lock
1 parent 68647db commit baf816f

File tree

1 file changed

+25
-12
lines changed

1 file changed

+25
-12
lines changed

RxSwift/Observables/Merge.swift

+25-12
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,7 @@ private final class MergeLimitedSinkIter<SourceElement, SourceSequence: Observab
169169
self.parent.dispose()
170170
case .completed:
171171
self.parent.group.remove(for: self.disposeKey)
172-
if let next = self.parent.queue.dequeue() {
173-
self.parent.subscribe(next, group: self.parent.group)
174-
}
175-
else {
176-
self.parent.activeCount -= 1
177-
178-
if self.parent.stopped && self.parent.activeCount == 0 {
179-
self.parent.forwardOn(.completed)
180-
self.parent.dispose()
181-
}
182-
}
172+
self.parent.dequeueNextAndSubscribe()
183173
}
184174
}
185175
}
@@ -236,7 +226,8 @@ private class MergeLimitedSink<SourceElement, SourceSequence: ObservableConverti
236226
return self.group
237227
}
238228

239-
func subscribe(_ innerSource: SourceSequence, group: CompositeDisposable) {
229+
@discardableResult
230+
func subscribe(_ innerSource: SourceSequence, group: CompositeDisposable) -> Disposable {
240231
let subscription = SingleAssignmentDisposable()
241232

242233
let key = group.insert(subscription)
@@ -247,6 +238,28 @@ private class MergeLimitedSink<SourceElement, SourceSequence: ObservableConverti
247238
let disposable = innerSource.asObservable().subscribe(observer)
248239
subscription.setDisposable(disposable)
249240
}
241+
return subscription
242+
}
243+
244+
func dequeueNextAndSubscribe() {
245+
if let next = queue.dequeue() {
246+
// subscribing immediately can produce values immediately which can re-enter and cause stack overflows
247+
let disposable = CurrentThreadScheduler.instance.schedule(()) { _ in
248+
// lock again
249+
self.lock.performLocked {
250+
self.subscribe(next, group: self.group)
251+
}
252+
}
253+
_ = group.insert(disposable)
254+
}
255+
else {
256+
activeCount -= 1
257+
258+
if stopped && activeCount == 0 {
259+
forwardOn(.completed)
260+
dispose()
261+
}
262+
}
250263
}
251264

252265
func performMap(_ element: SourceElement) throws -> SourceSequence {

0 commit comments

Comments
 (0)