@@ -60,6 +60,10 @@ abstract class Topic[F[_], A] { self =>
6060 * Note: if `publish1` is called concurrently by multiple producers,
6161 * different subscribers may receive messages from different producers
6262 * in a different order.
63+ *
64+ * Note: if `publish1` returns `Left(Topic.Closed)`, it is possible
65+ * that some subscribers received the event while others did not due
66+ * to concurrent closure.
6367 */
6468 def publish1 (a : A ): F [Either [Topic .Closed , Unit ]]
6569
@@ -152,47 +156,30 @@ object Topic {
152156 (
153157 F .ref(State .initial[F , A ]),
154158 SignallingRef [F , Int ](0 ),
155- F .deferred[Unit ],
156159 F .deferred[Unit ]
157- ).mapN { case (state, subscriberCount, signalClosure, publishersFinished ) =>
160+ ).mapN { case (state, subscriberCount, signalClosure) =>
158161 new Topic [F , A ] {
159162
160163 def foreach [B ](lm : LongMap [B ])(f : B => F [Unit ]) =
161- lm.foldLeft(F .unit) { case (op, (_, b)) => f(b) >> op }
164+ lm.foldLeft(F .unit) { case (op, (_, b)) => op >> f(b) }
162165
163166 def publish1 (a : A ): F [Either [Topic .Closed , Unit ]] =
164- state.flatModify {
165- case s @ State .Active (subs, _, n, false ) =>
166- val inc = n + 1
167- val newState = s.copy(publishing = inc)
168-
169- val sends = subs.foldLeft(F .pure(true )) { case (acc, (_, chan)) =>
170- chan.send(a).map(_.isRight).map2(acc)(_ && _)
171- }
172-
173- val action = sends.flatMap { allSucceeded =>
174- state
175- .flatModify {
176- case s @ State .Active (subs, _, n, closing) =>
177- val dec = n - 1
178- if (dec == 0 && closing) {
179- val closeAction = foreach(subs)(_.close.void)
180- (State .Closed (), closeAction >> publishersFinished.complete(()).void)
181- } else {
182- (s.copy(publishing = dec), F .unit)
167+ state.get.flatMap {
168+ case State .Closed () =>
169+ Topic .closed.pure[F ]
170+ case State .Active (subs, _) =>
171+ subs.toList.foldLeftM(Topic .rightUnit) {
172+ case (Left (Topic .Closed ), _) => Topic .closed.pure[F ]
173+ case (Right (_), (_, chan)) =>
174+ chan.send(a).flatMap {
175+ case Right (_) => Topic .rightUnit.pure[F ]
176+ case Left (_) =>
177+ state.get.map {
178+ case State .Closed () => Topic .closed
179+ case _ => Topic .rightUnit
183180 }
184- case s @ State .Closed () => (s, F .unit)
185- }
186- .map { _ =>
187- if (allSucceeded) Topic .rightUnit else Topic .closed
188181 }
189182 }
190- (newState, action)
191-
192- case s @ State .Active (_, _, _, true ) =>
193- (s, Topic .closed.pure[F ])
194- case s @ State .Closed () =>
195- (s, Topic .closed.pure[F ])
196183 }
197184
198185 def subscribeAwait (maxQueued : Int ): Resource [F , Stream [F , A ]] =
@@ -208,20 +195,18 @@ object Topic {
208195 def subscribeAwaitImpl (chan : Channel [F , A ]): Resource [F , Stream [F , A ]] = {
209196 val subscribe : F [Option [Long ]] =
210197 state.flatModify {
211- case s @ State .Active (subs, nextId, _, false ) =>
212- val newState = s.copy(subscribers = subs.updated(nextId, chan), nextId = nextId + 1 )
198+ case State .Active (subs, nextId) =>
199+ val newState = State . Active ( subs.updated(nextId, chan), nextId + 1 )
213200 val action = subscriberCount.update(_ + 1 )
214201 val result = Some (nextId)
215202 newState -> action.as(result)
216- case s @ State .Active (_, _, _, true ) =>
217- s -> F .pure(None )
218203 case closed @ State .Closed () =>
219204 closed -> F .pure(None )
220205 }
221206
222207 def unsubscribe (id : Long ): F [Unit ] =
223208 state.flatModify {
224- case s @ State .Active (subs, _, _, _ ) =>
209+ case State .Active (subs, nextId ) =>
225210 // _After_ we remove the bounded channel for this
226211 // subscriber, we need to drain it to unblock to
227212 // publish loop which might have already enqueued
@@ -231,7 +216,7 @@ object Topic {
231216 chan.close >> chan.stream.compile.drain
232217 }
233218
234- s.copy(subscribers = subs - id) -> (drainChannel *> subscriberCount.update(_ - 1 ))
219+ State . Active ( subs - id, nextId ) -> (drainChannel *> subscriberCount.update(_ - 1 ))
235220
236221 case closed @ State .Closed () =>
237222 closed -> F .unit
@@ -265,15 +250,9 @@ object Topic {
265250
266251 def close : F [Either [Topic .Closed , Unit ]] =
267252 state.flatModify {
268- case s @ State .Active (subs, _, n, false ) =>
269- if (n == 0 ) {
270- val action = foreach(subs)(_.close.void) *> signalClosure.complete(())
271- (State .Closed (), (action >> publishersFinished.complete(())).as(Topic .rightUnit))
272- } else {
273- (s.copy(closing = true ), publishersFinished.get.as(Topic .rightUnit))
274- }
275- case s @ State .Active (_, _, _, true ) =>
276- (s, publishersFinished.get.as(Topic .rightUnit))
253+ case State .Active (subs, _) =>
254+ val action = foreach(subs)(_.close.void) *> signalClosure.complete(())
255+ (State .Closed (), action.as(Topic .rightUnit))
277256 case closed @ State .Closed () =>
278257 (closed, Topic .closed.pure[F ])
279258 }
@@ -288,15 +267,13 @@ object Topic {
288267 private object State {
289268 case class Active [F [_], A ](
290269 subscribers : LongMap [Channel [F , A ]],
291- nextId : Long ,
292- publishing : Long ,
293- closing : Boolean
270+ nextId : Long
294271 ) extends State [F , A ]
295272
296273 case class Closed [F [_], A ]() extends State [F , A ]
297274
298275 def initial [F [_], A ]: State [F , A ] =
299- Active (LongMap .empty, 1L , 0L , false )
276+ Active (LongMap .empty, 1L )
300277 }
301278
302279 private final val closed : Either [Closed , Unit ] = Left (Closed )
0 commit comments