@@ -2015,6 +2015,224 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F,
2015
2015
): Stream [F2 , O2 ] =
2016
2016
that.mergeHaltL(this )
2017
2017
2018
+ /** Merges two streams with priority given to the first stream.
2019
+ *
2020
+ * Internally, this uses two bounded queues (of one element).
2021
+ * Queue has tryTake() which allows to try a non-blocking read.
2022
+ * This is used to check for elements on the prioritized queue,
2023
+ * before a blocking read through racePair() is tried on both
2024
+ * queues, if no data is available on the prioritized queue.
2025
+ */
2026
+ def mergePreferred [F2 [x] >: F [x], O2 >: O ](
2027
+ that : Stream [F2 , O2 ]
2028
+ )(implicit F : Concurrent [F2 ]): Stream [F2 , O2 ] = {
2029
+ val fstream : F2 [Stream [F2 , O2 ]] =
2030
+ for {
2031
+ interrupt <- F .deferred[Unit ]
2032
+ resultL <- F .deferred[Either [Throwable , Unit ]]
2033
+ resultR <- F .deferred[Either [Throwable , Unit ]]
2034
+ resultQL <- Queue .bounded[F2 , Option [Stream [F2 , O2 ]]](1 )
2035
+ resultQR <- Queue .bounded[F2 , Option [Stream [F2 , O2 ]]](1 )
2036
+ } yield {
2037
+
2038
+ def watchInterrupted (str : Stream [F2 , O2 ]): Stream [F2 , O2 ] =
2039
+ str.interruptWhen(interrupt.get.attempt)
2040
+
2041
+ // action to signal that one stream is finished (by putting a None in it)
2042
+ def doneAndClose (q : Queue [F2 , Option [Stream [F2 , O2 ]]]): F2 [Unit ] = q.offer(None ).void
2043
+
2044
+ // action to interrupt the processing of both streams by completing interrupt
2045
+ val signalInterruption : F2 [Unit ] = interrupt.complete(()).void
2046
+
2047
+ // Read from a stream and (possibly blocking) write to the bounded queue for that stream
2048
+ def go (s : Stream [F2 , O2 ], q : Queue [F2 , Option [Stream [F2 , O2 ]]]): Pull [F2 , Nothing , Unit ] =
2049
+ s.pull.uncons
2050
+ .flatMap {
2051
+ case Some ((hd, tl)) =>
2052
+ val send = q.offer(Some (Stream .chunk(hd)))
2053
+ Pull .eval(send) >> go(tl, q)
2054
+ case None =>
2055
+ Pull .done
2056
+ }
2057
+
2058
+ def runStream (
2059
+ s : Stream [F2 , O2 ],
2060
+ whenDone : Deferred [F2 , Either [Throwable , Unit ]],
2061
+ q : Queue [F2 , Option [Stream [F2 , O2 ]]]
2062
+ ): F2 [Unit ] = {
2063
+ val str = watchInterrupted(go(s, q).stream)
2064
+ str.compile.drain.attempt
2065
+ .flatMap {
2066
+ // signal completion of our side before we will signal interruption,
2067
+ // to make sure our result is always available to others
2068
+ case r @ Left (_) =>
2069
+ whenDone.complete(r) >> signalInterruption
2070
+ case r @ Right (_) =>
2071
+ whenDone.complete(r) >> doneAndClose(q)
2072
+ }
2073
+ }
2074
+
2075
+ // Typedef for the fibres that read from the queues.
2076
+ // That's contained in the Either returned by racePair()
2077
+ type FBR = Fiber [F2 , Throwable , Option [Stream [F2 , O2 ]]]
2078
+
2079
+ // An ADT for tracking state of the two queues.
2080
+ // The types describe the state, starting with BothActive.
2081
+ // Next state is either LeftDone or RightDone.
2082
+ // Final state is BothDone.
2083
+ // The members of those states store the loosing fibre
2084
+ // of a racePair()-call, which will be reused during the
2085
+ // next read.
2086
+ sealed trait QueuesState
2087
+ final case class BothActive (v : Option [Either [FBR , FBR ]]) extends QueuesState
2088
+ final case class LeftDone (rFbr : Option [FBR ]) extends QueuesState
2089
+ final case class RightDone (lFbr : Option [FBR ]) extends QueuesState
2090
+ case object BothDone extends QueuesState
2091
+
2092
+ // Race the given effects, returning the result of the winner
2093
+ // plus the still active fibre of the looser
2094
+ def raceQueues (
2095
+ lq : F2 [Option [Stream [F2 , O2 ]]],
2096
+ rq : F2 [Option [Stream [F2 , O2 ]]]
2097
+ ): F2 [(Option [Stream [F2 , O2 ]], Either [FBR , FBR ])] =
2098
+ F .racePair(lq, rq)
2099
+ .flatMap {
2100
+ case Left ((result, fiber)) =>
2101
+ result.embedError.map(_ -> fiber.asRight[FBR ])
2102
+ case Right ((fiber, result)) =>
2103
+ result.embedError.map(_ -> fiber.asLeft[FBR ])
2104
+ }
2105
+
2106
+ // stream that is generated from pumping out the elements of the queue.
2107
+ val pumpFromQueue : Stream [F2 , O2 ] =
2108
+ Stream
2109
+ .unfoldEval[F2 , QueuesState , Stream [F2 , O2 ]](BothActive (None )) { s =>
2110
+ // Returning None from unfoldEval will stop the stream. If we read a None
2111
+ // from any queue, we cannot return that but must continue reading on the
2112
+ // other queue. Thus, we need a method which can be called recursively to
2113
+ // continue reading in case of None.
2114
+ def readNext (s : QueuesState ): F2 [(Option [Stream [F2 , O2 ]], QueuesState )] =
2115
+ s match {
2116
+ // The initial state, both queues are active and there are no fibres left over
2117
+ case BothActive (None ) =>
2118
+ // check available data on left, which would be prioritized
2119
+ resultQL.tryTake
2120
+ .flatMap {
2121
+ _.fold(
2122
+ // no data available on prioritized queue, race both queues
2123
+ raceQueues(resultQL.take, resultQR.take)
2124
+ .flatMap[(Option [Stream [F2 , O2 ]], QueuesState )] {
2125
+ case (None , Left (fbr)) =>
2126
+ readNext(RightDone (fbr.some))
2127
+ case (None , Right (fbr)) =>
2128
+ readNext(LeftDone (fbr.some))
2129
+ case (Some (s), fbr) =>
2130
+ F .pure(s.some -> BothActive (fbr.some))
2131
+ }
2132
+ )(os =>
2133
+ // we read data from the prioritized queue, however, this sill could be a None,
2134
+ // signalling that queue is done. Handle that:
2135
+ os.fold(readNext(LeftDone (None )))(ls =>
2136
+ F .pure(ls.some -> BothActive (None ))
2137
+ )
2138
+ )
2139
+ }
2140
+
2141
+ // right was looser during the last run
2142
+ case BothActive (Some (Right (fbr))) =>
2143
+ // anyway, check for available data on left first, ignoring the incoming fibre for right
2144
+ resultQL.tryTake
2145
+ .flatMap(
2146
+ _.fold(
2147
+ // use the incoming fibre to read from right queue
2148
+ raceQueues(resultQL.take, fbr.joinWithNever)
2149
+ .flatMap[(Option [Stream [F2 , O2 ]], QueuesState )] {
2150
+ case (None , Left (fbr)) =>
2151
+ readNext(RightDone (fbr.some))
2152
+ case (None , Right (fbr)) =>
2153
+ readNext(LeftDone (fbr.some))
2154
+ case (Some (s), fbr) =>
2155
+ F .pure(s.some -> BothActive (fbr.some))
2156
+ }
2157
+ )(os =>
2158
+ // important to reuse the incoming fibre here!
2159
+ os.fold(readNext(LeftDone (fbr.some)))(ls =>
2160
+ F .pure(ls.some -> BothActive (fbr.asRight[FBR ].some))
2161
+ )
2162
+ )
2163
+ )
2164
+
2165
+ // left was looser during the last run
2166
+ case BothActive (Some (Left (fbr))) =>
2167
+ // Can't check for available data on left this time,
2168
+ // because there's an active fibre reading from the left queue.
2169
+ // Start a race and reuse that fibre for left.
2170
+ raceQueues(fbr.joinWithNever, resultQR.take)
2171
+ .flatMap[(Option [Stream [F2 , O2 ]], QueuesState )] {
2172
+ case (None , Left (fbr)) =>
2173
+ readNext(RightDone (fbr.some))
2174
+ case (None , Right (fbr)) =>
2175
+ readNext(LeftDone (fbr.some))
2176
+ case (Some (s), fbr) =>
2177
+ F .pure(s.some -> BothActive (fbr.some))
2178
+ }
2179
+
2180
+ // Left queue is done, but, it's possible we retrieve an active fibre for right.
2181
+ case LeftDone (fbr) =>
2182
+ fbr
2183
+ .map(_.joinWithNever) // join the incoming fibre if given
2184
+ .getOrElse(resultQR.take) // ordinary take() if no fibre has been given
2185
+ .map {
2186
+ case None =>
2187
+ None -> BothDone
2188
+ case os =>
2189
+ os -> LeftDone (None )
2190
+ }
2191
+
2192
+ // mirror case of above
2193
+ case RightDone (fbr) =>
2194
+ fbr
2195
+ .map(_.joinWithNever)
2196
+ .getOrElse(resultQL.take)
2197
+ .map {
2198
+ case None =>
2199
+ None -> BothDone
2200
+ case os =>
2201
+ os -> RightDone (None )
2202
+ }
2203
+
2204
+ // this should never happen, but we need to make the compiler happy
2205
+ case BothDone =>
2206
+ F .pure(None -> BothDone )
2207
+ }
2208
+
2209
+ // readNext() returns None in _1 if and only if both queues are done
2210
+ readNext(s).map {
2211
+ case (None , _) =>
2212
+ None // finish the stream (unfoldEval)
2213
+ case (Some (s), st) =>
2214
+ (s -> st).some // emit element and new state (unfoldEval)
2215
+ }
2216
+ }
2217
+ .flatten // we have Stream[F2, Stream[F2, O2]] and flatten that to Stream[F2, O2]
2218
+
2219
+ val atRunEnd : F2 [Unit ] =
2220
+ for {
2221
+ _ <- signalInterruption // interrupt so the upstreams have chance to complete
2222
+ left <- resultL.get
2223
+ right <- resultR.get
2224
+ r <- F .fromEither(CompositeFailure .fromResults(left, right))
2225
+ } yield r
2226
+
2227
+ val runStreams =
2228
+ runStream(this , resultL, resultQL).start >> runStream(that, resultR, resultQR).start
2229
+
2230
+ Stream .bracket(runStreams)(_ => atRunEnd) >> watchInterrupted(pumpFromQueue)
2231
+ }
2232
+ Stream .eval(fstream).flatten
2233
+
2234
+ }
2235
+
2018
2236
/** Given two sorted streams emits a single sorted stream, like in merge-sort.
2019
2237
* For entries that are considered equal by the Order, left stream element is emitted first.
2020
2238
* Note: both this and another streams MUST BE ORDERED already
0 commit comments