-
Notifications
You must be signed in to change notification settings - Fork 49
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 timeout capability to Future implementation of FetchMonadError #127
Conversation
Removed synchronization on Promise and simplified the PR |
Have added tests for the two cases of a future failing because it timed out and succeeding because it finished before the timeout occurred. |
override def name = "ArticleFuture" | ||
override def fetchOne(id: ArticleId): Query[Option[Article]] = | ||
Query.async((ok, fail) => { | ||
Thread.sleep(delay.toMillis) |
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.
Thread.sleep
doesn't work on ScalaJS, which is the reason that Travis is complaining.
case Async(ac, timeout) => { | ||
|
||
case Sync(e) => | ||
Future.successful({e.value}) |
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.
Evaluating e
could potentially throw an exception and those aren't caught by Future.successful
.
|
||
implicit val dsWillTimeout = ConfigurableTimeoutDatasource(250 milliseconds, 750 milliseconds) | ||
|
||
def article(id: Int): Fetch[Article] = Fetch(ArticleId(id)) |
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.
If you added an implicit DataSource[ArticleId, Article]
you wouldn't need to implement this method in all the tests.
def article(id: Int)(implicit DS: DataSource[ArticeId, Article]): Fetch[Article] =
Fetch(ArticleId(id)) // or Fetch(ArticleId(id))(DS)
import scala.concurrent.duration._ | ||
import org.scalatest._ | ||
import cats.data.NonEmptyList | ||
import scala.language.postfixOps |
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.
You shouldn't need this as we have "-language:postfixOps"
as a scalacOption
.
val expTime = System.nanoTime + ms*1000000 | ||
while(System.nanoTime < expTime) {} | ||
println("sleep finished") | ||
} |
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 am not sure if we can actually test the time out in ScalaJS.
I think that if you block like this, you block everything in JS, so the TimerTask
cannot finish the promise with a TimeoutExecption
(which is happening in the failing test on Travis).
We could revert back to using Thread.sleep
and add this as a JVM only test, by moving it to jvm/src/test/scala
, what do you think?
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.
Yeah I think that makes sense. I've been trying to think of a way to isolate the timeout behaviour in a way that can be demonstrated to work with scala js but haven't come up with a way yet.
I've updated the PR so that the tests that require non-blocking sleep are now in the JVM module. Seems travis build is failing but this is due to a timeout building the docs |
timer.schedule(timerTask, timeout.toMillis) | ||
|
||
// Execute the user's action | ||
ec.execute(() => { |
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.
This conversion (and the one below) to Runnable
only works in 2.12 I think which added support for Java 8 SAMs.
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.
Oops my bad. IntelliJ suggested it and it seemed like a good idea. I'll change it back
I think everything looks good now, just getting a timeout with Travis building the docs |
There definitely is something strange going on when combining Commenting out A minimal tut file which makes tut hang (notice that we aren't even using the time out here, since we are using // ```tut
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext.Implicits.global
import fetch.{FetchMonadError, Query}
import fetch.implicits._
val query: Query[Option[Int]] = Query.sync { Some(1) }
val future: Future[Option[Int]] = FetchMonadError[Future].runQuery(query)
Await.result(future, Duration.Inf)
// ``` If I change the case Sync(e) =>
cats.ApplicativeError[Future, Throwable].catchNonFatalEval(e) It seems like I need to do some more investigating to see what is going on ... |
Ah timer is supposed to run on a daemon thread and if it doesn't then it can stop a program from terminating. I'll check the code and see if I'm doing that correctly |
Codecov Report
@@ Coverage Diff @@
## master #127 +/- ##
==========================================
+ Coverage 78% 78.43% +0.43%
==========================================
Files 14 14
Lines 300 306 +6
Branches 2 2
==========================================
+ Hits 234 240 +6
Misses 66 66
Continue to review full report at Codecov.
|
I've updated so Timer is on a daemon thread and also created lazily so it is not initialized by programs that don't use it |
implicit ec: ExecutionContext | ||
): FetchMonadError[Future] = | ||
// Shared Timer object to schedule timeouts | ||
lazy val timer: Timer = new Timer(true) |
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.
Should we make this private[fetch]
?
|
||
case class ArticleId(id: Int) | ||
case class Article(id: Int, content: String) { | ||
def author: Int = id + 1 |
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 seems this author
method isn't used, so we can drop it, right?
Remove unused author ID from test Rename test variable to be more accurate in the output
case _ => | ||
|
||
// Execute the user's action | ||
ec.execute(new Runnable { |
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.
Nitpicking, we can create this Runnable
once before the timeout match { ... }
.
Even more nitpicking, we can lose the curly braces around ac(p.trySuccess, p.tryFailure)
.
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.
Done, maybe there's a better naming for the runnable than 'runnable', suggestions welcome
implicit ec: ExecutionContext | ||
): FetchMonadError[Future] = | ||
// Shared Timer object to schedule timeouts | ||
private[fetch] lazy val timer: Timer = new Timer(true) |
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.
We should probably name the daimon thread as well, something like "fetch-future-timeout-daemon" ?
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.
yeah good idea, will help avoid confusion when people look at running threads and don't know what it's for
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.
Thanks @justinhj!
The attached PR adds timeout capability to FetchMonadError[Future]
I also modified Query.Sync to return a completed future which I think is the intent rather than run it in a Future
All tests pass but I didn't yet create tests for the timeout, I can do so if the PR looks like something you might want
Only thing I'm not sure about is whether the use of synchronize is needed. I'm not sure but I think Promise may be safe to use from multiple threads because it uses an AtomicReference but didn't find any confirmation of that.