-
Notifications
You must be signed in to change notification settings - Fork 607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add withTimeout combinator #1777
Add withTimeout combinator #1777
Conversation
465ddb3
to
dead200
Compare
It's strange that 2.12 build has failed, b/c the failure is in the suite that was not touched |
Isn't this just |
@kubukoz seems like |
It won't result in any error indeed, but you can write a |
I meant something like this: def timeout[F[_]: Concurrent: Timer, A](duration: FiniteDuration): Pipe[F, A, A] = in =>
Stream.eval(Deferred[F, Either[TimeoutException, Unit]])
.flatMap { deff =>
in.onFinalizeCase {
case ExitCase.Canceled => deff.complete(Left(new TimeoutException(s"Timed out after $duration")))
case _ => deff.complete(Right(()))
}.interruptAfter(duration) ++ Stream.eval_(deff.get.rethrow)
} |
@kubukoz Thanks! Yes, a solution like that came to my mind, but I thought that we wouldn't be able to distinguish between user-requested cancellation and one caused by the timeout. Am I wrong? |
Oh, I suppose that might be true, really good point. How about this... def timeout[F[_]: Concurrent: Timer, A](duration: FiniteDuration): Pipe[F, A, A] =
_.interruptWhen(Stream.sleep[F](duration).as(true)) I don't know why I didn't come up with that sooner. Edit: no, wait, that's too simple. here we go again... |
This will not result in an error, I guess? That will just interrupt the stream. Maybe something like that will do? def timeout[F[_]: Concurrent: Timer, A](duration: FiniteDuration): Pipe[F, A, A] =
_.interruptWhen(Timer[F].sleep(duration).as(Left(new TimeoutException(s"Timed out after $duration")))) |
Yeah, that should do it 😅 |
What's your opinion on what this operator should be - a function on a |
I think it should be a function on Stream. Such is the convention for these, and we don't really have a good place to put pipes that are that generic in. |
Thank you very much for advice! The implementation became much shorter and clearer 😅 |
@@ -2713,6 +2714,17 @@ final class Stream[+F[_], +O] private[fs2] (private val free: FreeC[F, O, Unit]) | |||
)(f: (Stream[F, O], Stream[F2, O2]) => Stream[F2, O3]): Stream[F2, O3] = | |||
f(this, s2) | |||
|
|||
/** Fails this stream with a [[TimeoutException]] if it does not complete within given `timeout`. */ | |||
def timeout[F2[x] >: F[x]]( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use context bounds here
@@ -3786,4 +3787,14 @@ class StreamSpec extends Fs2Spec { | |||
} | |||
} | |||
} | |||
|
|||
"withTimeout" - { | |||
"timeout never-ending stream" in { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind also testing composability of timeouts? i.e. if you do (s1.timeout(d1) ++ s2).timeout(d2)
, what happens depending on the times of s1
, s2
, d1 > d2
(if d2 > d1, s1 might make it in time but s2 might not, so we still want to timeout, etc.)
Just a couple minor things, and I think we're good to go. I'd also like to see what @SystemFw thinks. |
I added context bounds and a couple of tests as you recommended. It seems like other test cases (with different |
Sure, it makes sense. Thanks a lot! |
On a day-to-day job basis we often use such a combinator for
Stream
, allowing us to time out and interrupt streams that do not complete within givenFiniteDuration
. Such a combinator may be useful as a part of theStream
itself