monix.reactive.internal.operators.UpstreamTimeoutObservable.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of monix-reactive_sjs1_2.13 Show documentation
Show all versions of monix-reactive_sjs1_2.13 Show documentation
Sub-module of Monix, exposing the Observable pattern for modeling of reactive streams. See: https://monix.io
The newest version!
/*
* Copyright (c) 2014-2021 by The Monix Project Developers.
* See the project homepage at: https://monix.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package monix.reactive.internal.operators
import java.util.concurrent.TimeUnit
import monix.execution.Ack.{Continue, Stop}
import monix.execution.cancelables.{CompositeCancelable, MultiAssignCancelable, SingleAssignCancelable}
import monix.execution.exceptions.UpstreamTimeoutException
import monix.execution.{Ack, Cancelable, Scheduler}
import monix.reactive.Observable
import monix.reactive.observers.Subscriber
import scala.concurrent.Future
import scala.concurrent.duration.{FiniteDuration, MILLISECONDS}
private[reactive] final class UpstreamTimeoutObservable[+A](source: Observable[A], timeout: FiniteDuration)
extends Observable[A] {
def unsafeSubscribeFn(downstream: Subscriber[A]): Cancelable = {
val timeoutCheck = MultiAssignCancelable()
val mainTask = SingleAssignCancelable()
val composite = CompositeCancelable(mainTask, timeoutCheck)
mainTask := source.unsafeSubscribeFn(new Subscriber[A] with Runnable { self =>
implicit val scheduler: Scheduler = downstream.scheduler
private[this] val timeoutMillis = timeout.toMillis
// MUST BE synchronized by `self`
private[this] var isProcessingOnNext = false
// MUST BE synchronized by `self`
private[this] var isDone = false
// MUST BE synchronized by `self`
private[this] var lastEmittedMillis: Long =
scheduler.clockMonotonic(MILLISECONDS)
locally {
timeoutCheck := scheduler.scheduleOnce(timeout.length, timeout.unit, self)
}
def run(): Unit = self.synchronized {
if (!isDone) {
val rightNow = scheduler.clockMonotonic(MILLISECONDS)
val sinceLastOnNextInMillis =
if (isProcessingOnNext) 0L else rightNow - lastEmittedMillis
if (sinceLastOnNextInMillis >= timeoutMillis) {
// Oops, timeout happened, triggering error.
triggerTimeout()
} else {
val remainingTimeMillis = timeoutMillis - sinceLastOnNextInMillis
// No need for synchronization or ordering on this assignment, since
// there is a clear happens-before relationship between invocations
timeoutCheck := scheduler.scheduleOnce(remainingTimeMillis, TimeUnit.MILLISECONDS, self)
()
}
}
}
def onNext(elem: A): Future[Ack] = {
def unfreeze(): Ack = {
lastEmittedMillis = scheduler.clockMonotonic(MILLISECONDS)
isProcessingOnNext = false
Continue
}
// Unfortunately we can't get away from this synchronization
// as the scheduler is contending on `isDone` and if
// we don't synchronize, then we can break the contract by sending
// an `onNext` concurrently with an `onError` :-(
self.synchronized {
if (isDone) Stop
else {
isProcessingOnNext = true
// Shenanigans for avoiding an unnecessary synchronize
downstream.onNext(elem) match {
case Continue => unfreeze()
case Stop =>
timeoutCheck.cancel()
Stop
case async =>
async.flatMap {
case Continue => self.synchronized(unfreeze())
case Stop =>
timeoutCheck.cancel()
Stop
}
}
}
}
}
def triggerTimeout(): Unit = self.synchronized {
if (!isDone) {
isDone = true
val ex = UpstreamTimeoutException(timeout)
try downstream.onError(ex)
finally mainTask.cancel()
}
}
def onError(ex: Throwable): Unit = self.synchronized {
if (!isDone) {
isDone = true
try downstream.onError(ex)
finally timeoutCheck.cancel()
}
}
def onComplete(): Unit = self.synchronized {
if (!isDone) {
isDone = true
try downstream.onComplete()
finally timeoutCheck.cancel()
}
}
})
composite
}
}