Skip to content

Commit 191585b

Browse files
author
Ilya Surkov
committed
Fix uncancellable reactive-streams StreamSubscriber
1 parent 0377f21 commit 191585b

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

core/shared/src/test/scala/fs2/interop/flow/SubscriberStabilitySpec.scala

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import cats.effect.std.Random
2828

2929
import java.nio.ByteBuffer
3030
import java.util.concurrent.Flow.{Publisher, Subscriber, Subscription}
31-
import scala.concurrent.duration._
31+
import java.util.concurrent.atomic.AtomicBoolean
32+
import scala.concurrent.duration.*
3233

3334
class SubscriberStabilitySpec extends Fs2Suite {
3435
val attempts = 100
@@ -67,4 +68,32 @@ class SubscriberStabilitySpec extends Fs2Suite {
6768
.replicateA_(attempts)
6869
}
6970
}
71+
72+
test("StreamSubscriber cancels subscription on downstream cancellation") {
73+
def makePublisher(
74+
requestCalled: AtomicBoolean,
75+
subscriptionCancelled: AtomicBoolean
76+
): Publisher[ByteBuffer] =
77+
new Publisher[ByteBuffer] {
78+
79+
class SubscriptionImpl extends Subscription {
80+
override def request(n: Long): Unit = requestCalled.set(true)
81+
override def cancel(): Unit = subscriptionCancelled.set(true)
82+
}
83+
84+
override def subscribe(s: Subscriber[? >: ByteBuffer]): Unit =
85+
s.onSubscribe(new SubscriptionImpl)
86+
}
87+
88+
for {
89+
requestCalled <- IO(new AtomicBoolean(false))
90+
subscriptionCancelled <- IO(new AtomicBoolean(false))
91+
publisher = makePublisher(requestCalled, subscriptionCancelled)
92+
_ <- fromPublisher[IO](publisher, chunkSize = 1)
93+
.interruptWhen(Stream.eval(IO(requestCalled.get())).repeat.spaced(10.millis))
94+
.compile
95+
.drain
96+
_ <- IO(subscriptionCancelled.get).assert
97+
} yield ()
98+
}
7099
}

reactive-streams/src/main/scala/fs2/interop/reactivestreams/StreamSubscriber.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,11 @@ object StreamSubscriber {
216216
def onComplete(): Unit = nextState(OnComplete)
217217
def onFinalize: F[Unit] = F.delay(nextState(OnFinalize))
218218
def dequeue1: F[Either[Throwable, Option[Chunk[A]]]] =
219-
F.async_[Either[Throwable, Option[Chunk[A]]]] { cb =>
220-
nextState(OnDequeue(out => cb(Right(out))))
219+
F.async[Either[Throwable, Option[Chunk[A]]]] { cb =>
220+
F.delay {
221+
nextState(OnDequeue(out => cb(Right(out))))
222+
Some(F.unit)
223+
}
221224
}
222225
}
223226
}

reactive-streams/src/test/scala/fs2/interop/reactivestreams/SubscriberStabilitySpec.scala

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ package fs2
2323
package interop
2424
package reactivestreams
2525

26-
import cats.effect._
26+
import cats.effect.*
2727
import cats.effect.std.Random
28-
import org.reactivestreams._
29-
30-
import scala.concurrent.duration._
28+
import org.reactivestreams.*
3129

3230
import java.nio.ByteBuffer
31+
import java.util.concurrent.atomic.AtomicBoolean
32+
import scala.concurrent.duration.*
3333

3434
class SubscriberStabilitySpec extends Fs2Suite {
3535
test("StreamSubscriber has no race condition") {
@@ -87,4 +87,32 @@ class SubscriberStabilitySpec extends Fs2Suite {
8787
if (failed)
8888
fail("Uncaught exception was reported")
8989
}
90+
91+
test("StreamSubscriber cancels subscription on downstream cancellation") {
92+
def makePublisher(
93+
requestCalled: AtomicBoolean,
94+
subscriptionCancelled: AtomicBoolean
95+
): Publisher[ByteBuffer] =
96+
new Publisher[ByteBuffer] {
97+
98+
class SubscriptionImpl extends Subscription {
99+
override def request(n: Long): Unit = requestCalled.set(true)
100+
override def cancel(): Unit = subscriptionCancelled.set(true)
101+
}
102+
103+
override def subscribe(s: Subscriber[? >: ByteBuffer]): Unit =
104+
s.onSubscribe(new SubscriptionImpl)
105+
}
106+
107+
for {
108+
requestCalled <- IO(new AtomicBoolean(false))
109+
subscriptionCancelled <- IO(new AtomicBoolean(false))
110+
publisher = makePublisher(requestCalled, subscriptionCancelled)
111+
_ <- fromPublisher[IO, ByteBuffer](publisher, bufferSize = 1)
112+
.interruptWhen(Stream.eval(IO(requestCalled.get())).repeat.spaced(10.millis))
113+
.compile
114+
.drain
115+
_ <- IO(subscriptionCancelled.get).assert
116+
} yield ()
117+
}
90118
}

0 commit comments

Comments
 (0)