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

Make Console#readLine cancelable #3465

Merged
merged 5 commits into from
Apr 18, 2023
Merged

Conversation

armanbilge
Copy link
Member

Fixes #3077. This change is source-breaking, because it upgrades the constraint on Console from Sync to Async.

The fix is inspired by typelevel/fs2#3093 (comment). We now run all readLineWithCharset operations on a dedicated daemon thread, that is lazily started.

This stdinReader runs a simple state machine. It waits for a ReadLineRequest, then it reads a line from stdin, then it completes the request callback. If the request was canceled in the meantime, then it saves this line for the next request, which might already be pending.

This implementation never drops a line. If it has a line cached from a previous call that was canceled, the next request will either be completed with said line, or it will be failed with an IllegalStateException if the charset does not match the charset the cached line was read with.

I manually verified that this fixes the example given in #3077, as well as a couple other examples that I played with. I also verified that the cats-effect-stdin thread only appears if you specifically touch readLine.

Comment on lines +33 to +34
@deprecated("Use overload with Async constraint", "3.5.0")
def make[F[_]](F: Sync[F]): Console[F] = F match {
Copy link
Member Author

Choose a reason for hiding this comment

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

I know, I know, v3.5.0 shop is closed. But ... we could fix this beginner stumbling block once and for all 😇

No strong feelings 😁

Copy link
Member Author

@armanbilge armanbilge Feb 28, 2023

Choose a reason for hiding this comment

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

Oh actually, we can fix this source-compatibly and get it into 3.5.x. We can't expose the new instance publicly without a source-breaking change, but we can replace the instance for IO right now, and do the source-breaking change in 3.6. This gets us most of the win.

Comment on lines +90 to +91
} else { // happy days!
callback(line)
Copy link
Member Author

Choose a reason for hiding this comment

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

Something that is nagging at me is that this is a leak here. The callback may have already be canceled, but we are invoking it anyway. There seems no reliable way to know for sure.

I don't see any way to plug this without changing the callback in async { cb => to return a Boolean indicating whether it completed successfully or not. I feel like we've discussed this before.

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.

Life is pain.

/**
* Constructs a `Console` instance for `F` data types that are [[cats.effect.kernel.Async]].
*/
def make[F[_]](implicit F: Async[F]): Console[F] =
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this now the same constraint on both JS and JVM? Do we need the platform split?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, at the moment there's still Native with the Sync constraint. I can revisit that when we get polling system and/or Scala Native multithreading.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

IO.readLine.timeoutTo(...) not cancelling the readLine after timeout.
2 participants