Skip to content
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 Async#asyncCheckAttempt for #3087 #3091

Merged
merged 7 commits into from
Nov 14, 2022

Conversation

seigert
Copy link
Contributor

@seigert seigert commented Jul 11, 2022

Implementation of proposed utility method in Async for double-check registration. Also implemented in IO itself and some tests.

I've chosen asyncPoll as a name as method itself reminds me about Rust's Future poll method with also returns computed result or provides waker to schedule another poll sometime later.

@armanbilge armanbilge linked an issue Jul 11, 2022 that may be closed by this pull request
Comment on lines 1120 to 1135
def asyncPoll[A](
k: (Either[Throwable, A] => Unit) => IO[Either[Option[IO[Unit]], A]]): IO[A] = {
val body = new Cont[IO, A, A] {
def apply[G[_]](implicit G: MonadCancel[G, Throwable]) = { (resume, get, lift) =>
G.uncancelable { poll =>
lift(k(resume)) flatMap {
case Right(a) => G.pure(a)
case Left(Some(fin)) => G.onCancel(poll(get), lift(fin))
case Left(None) => poll(get)
}
}
}
}

IOCont(body, Tracing.calculateTracingEvent(k))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if I missed it—do we get an optimization be re-implementing this for IO? Otherwise probably we can delegate to the Async implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added it because a) only tests for async I've found were in IOSpec.scala and b) IO has async & async_ reimplemented too (and only visible bonus is Tracking + IOCont). If that not a concern, I could delete it.

Maybe some law should be added to AsyncLaws instead? Like (F.asyncPool[A](k => F.pure(Right(a))) <* F.unit) <-> F.pure(a))?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, thanks, I missed the Tracing. Actually my main concern was that if we override the implementation in IO, then the generic implementation in Async is not being tested anywhere, since as you point out the only are were in IOSpec.

Copy link
Contributor Author

@seigert seigert Jul 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it was this way before -- Async.async & Async.async_ a re-implemented in IO almost word by word, so the status quo stays the same. :)

Anyway, I've added three more laws to AsyncLaws about asyncPoll <-> (async, pure) (hope they make some sense) and added some synthetic test for some 'basic' Async variant based off IO which reuses all method implementations in Async itself.

@seigert
Copy link
Contributor Author

seigert commented Jul 11, 2022

Also, should docs for cont be updated about this method? "If you are an implementor, and you have async (or asyncPool), Async.defaultCont provides an implementation of cont in terms of async/asyncPool."

@seigert seigert requested a review from armanbilge July 12, 2022 12:03
@seigert
Copy link
Contributor Author

seigert commented Jul 14, 2022

@armanbilge, do you have any insight why check may randomly flick? Yesterday it was IoAppSpec, not it is WorkerThreadNameSpec -- both should be unaffected by my changes. :(

@armanbilge
Copy link
Member

Darn, I just restarted CI for you. IOAppSpec is a well known flake. WorkerThreadNameSpec was only recently added, but I guess it's a flaky one too :(

Copy link
Member

@djspiewak djspiewak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Thank you for taking this on, and I'm sorry it took me so long to review. Left some comments.

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class AsyncSpec extends BaseSpec with Discipline {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this have to be pulled out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @armanbilge mentioned above, Async version of async_ (and new generic version of async itself) was not tested anywhere, as IOSpec.scala only tests Async[IO] instance that, in turn, uses redefined IO.async & IO.async_.

They are almost literal copies of one another, but still -- as there is two methods in Async that both should provide backward compatible generic implementations via asyncPoll I've decided to pull out test spec for Async itself.

The problem is that Async does not have any instances except for IO, and IO overrides most methods, so spec provides another Async instance that only defines necessary abstract methods.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh gotcha. Nice catch!

* @see
* [[async]] for a simplified variant without an option for immediate result
*/
def asyncPoll[A](
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one problem I see with the name is we already use the word Poll for something quite different! This might be worth bikeshedding a bit to avoid user confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think my English is good enough to provide a meaningful name, but after some thoughts there are my suggestions:

  1. asyncAttempt/tryAsync -- as in 'we'll try to schedule async operation but if results are already available, then so be it'. Both with 'error handling' connotations, sadly.
  2. resolve/resolveEventually -- as in 'computed value will be resolved one way or another, right now or in future';
  3. registerAsyncCallback -- intentionally enterprise-like name to emphasis that this is a low-level method like cont.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give this some thought and report back in a day or two! I actually rather like asyncAttempt, but you're right it does have that negative "error" context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@djspiewak, did you had time to think about a name?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I haven't! Thank you for the reminder. I'll get to this, I apologize.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think asyncCheckAttempt is probably the best I can come up with. I agree with you that we don't necessarily want it to be a really concise name.

Suggested change
def asyncPoll[A](
def asyncCheckAttempt[A](

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -62,22 +62,28 @@ import java.util.concurrent.atomic.AtomicReference
trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F] {

/**
* The asynchronous FFI.
* The asynchronous FFI with option for immediate result.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we unify this with the IO.asyncCheckAttempt scaladoc?

Copy link
Contributor Author

@seigert seigert Nov 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that I understood you correctly, but I've tried to sync IO and Async async* method descriptions with exception of 'real-world examples'.

@seigert seigert changed the title Add Async#asyncPoll for #3087 Add Async#asyncCheckAttempt for #3087 Nov 14, 2022
Copy link
Member

@djspiewak djspiewak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!! This has been a very much appreciated effort.

@djspiewak djspiewak merged commit fa3324a into typelevel:series/3.x Nov 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider an Async utility to encapsulate double-check registration
3 participants