Easy Racer implementation #89
Replies: 3 comments 7 replies
-
Hi Jack, Cancellable HTTP clientFor your HTTP client, I would suggest looking at gears.async.cancellationScope. The idea is to wrap long-running, but not awaiting code with a guard that can be used to cancel the operation inside. This could resemble this: cancellationScope(() => call.cancel()):
val response = call.execute()
cancellationScope(() => response.close()):
response.body().string() Note though that it should generally be avoided to have such long-running, not awaiting (especially: blocking) code, because it occupies a carrier thread. You should instead try to wrap the async API which does lend more to our idioms: extension (call: Call)
def asyncCall()(using Async): Try[Response] =
Future
.withResolver[Response]: resolver =>
call.enqueue(
new Callback:
def onFailure(th: Throwable): Unit = resolver.reject(th)
def onResponse(response: Response) = resolver.resolve(response)
)
resolver.onCancel(() => call.cancel())
.link()
.awaitResult Cancellable blocking CPU-bound operationsI would suggest to use a separate thread pool for CPU-bound tasks, for similar reasons as outlined above (occupies carrier thread). It's just not something where Futures (or virtual threads) are good at. Again, you can expose the result via a |
Beta Was this translation helpful? Give feedback.
-
I was going through my existing implementation, and found a bug in scenario 4 def scenario4(scenarioUrl: Int => String)(using Async.Spawn): String =
val url = scenarioUrl(4)
def req = Future(scenarioRequest(url).execute().link().body().string())
Seq(withTimeout(1.second)(req), req).awaitFirstWithCancel Tests are passing some of the times, but it turns out that's because the default OkHttp timeout is set pretty low, and depending on which of the requests times out first, we may end up with the correct behavior. If I understand correctly, I believe the issue is that I was able to implement the timeout using awaitFirstWithCancel/orWithCancel: def req = Future:
Try(scenarioRequest(url).execute().link().body().string())
def timeout(duration: FiniteDuration) = Future:
AsyncOperations.sleep(duration)
Failure(TimeoutException())
Seq(
Future:
Seq(req, timeout(1.second)).awaitFirstWithCancel.get,
Future(req.await.get)
).awaitFirstWithCancel But I'd like to know if there is a better way to accomplish this, as what I currently have it very verbose? Alternatively, do you have plans to add something like a |
Beta Was this translation helpful? Give feedback.
-
Hey @m8nmueller, Also, as mentioned above, I notice // Like `gears.async.withTimeout`, but cancels operation upon timeout
def withTimeoutWithCancel[T](timeout: FiniteDuration)(op: Async ?=> T)(using Async): T =
Async.group:
val timeoutFut = Future:
AsyncOperations.sleep(timeout)
Failure(TimeoutException())
Seq(timeoutFut, Future(Try(op))).awaitFirstWithCancel.get But it doesn't seem to be working (the operation isn't being cancelled). |
Beta Was this translation helpful? Give feedback.
-
Hi there,
I have contributed an Easy Racer implementation using Gears.
While working on this, a couple of questions did come up which I'm hoping to be able to get answered here:
Cancellable HTTP client?
Many Easy Racer scenarios have been designed around being able to cancel HTTP requests, and it is unclear to me if Gears currently has a preferred solution to this. There's an example of this in the Supervision with Retries and Timeouts section of the document:
But it is unclear what HTTP client implementation is being used here, or if the request would get cancelled in the event of a timeout.
To get around this, I was able to wrap OkHttp in a Gears Cancellable like so, but I'm curious if there's a preferred/more idiomatic way I should be approaching this.
Cancellable blocking CPU-bound operations?
Easy Racer's Scenario 10 requires us to implement a CPU intensive operation that needs to be cancellable.
I found two ways to approach this:
AtomicBoolean
here). I like this solution in that it fits in the cancellable model, but it does seem to require a fair bit of boilerplate.AsyncOperations.sleep(0)
periodically. This is more succinct, but it may not be the most intuitive what theAsyncOperation.sleep(0)
is meant to do (if this is meant to be the preferred solution, perhaps introducing anAsyncOperation.checkCancellation()
might help make the intent clearer).Again, I'm curious which is the preferred solution, or if I'm missing a completely different solution.
I'm also open to any feedback the team may have on this implementation in general.
Thank you!
Beta Was this translation helpful? Give feedback.
All reactions