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

Potential inconsistency between joinWithNever and docs #4093

Open
wunderk1nd-e opened this issue Jun 26, 2024 · 6 comments · Fixed by #4160
Open

Potential inconsistency between joinWithNever and docs #4093

wunderk1nd-e opened this issue Jun 26, 2024 · 6 comments · Fixed by #4160

Comments

@wunderk1nd-e
Copy link

wunderk1nd-e commented Jun 26, 2024

Hi there,

In the docs for the the Spawn typeclass at the very end it states:

In English, the semantics of this are as follows:

If the child fiber completed successfully, produce its result
If it errored, re-raise the error within the current fiber
If it canceled, attempt to self-cancel, and if the self-cancelation fails, deadlock
Sometimes this is an appropriate semantic, and the cautiously-verbose joinWithNever function implements it for you.

However, the joinWithNever implementation doesn't look like it actually makes any attempts at self cancellation and I think I've confirmed this with a small test app.

object FunTest extends IOApp.Simple {
  override val run: IO[Unit] = for {
    _ <- IO.println("Starting")
    child = IO.println("Starting child...") >>
      IO.sleep(1.second) >>
      IO.canceled >>
      IO.sleep(1.second) >>
      IO.println("Child completed")
    childFiber     <- child.start
    dependentChild <- childFiber.joinWithNever.as("I managed to return something").start
    result <- dependentChild
      .joinWith(IO.pure("I was cancelled"))
      .timeoutTo(3.seconds, "I waited too long :(".pure[IO])
    _ <- IO.println(s"result is $result")
  } yield ()
}

which outputs the following to console

Starting
Starting child...
result is I waited too long :(

Process finished with exit code 0
@durban
Copy link
Contributor

durban commented Jun 28, 2024

Yeah, that's definitely a bug; either in the code or in the docs.

lenguyenthanh added a commit to lenguyenthanh/cats-effect that referenced this issue Sep 8, 2024
Align the implementation with Spawn docs.
Close typelevel#4093.
@armanbilge
Copy link
Member

I'm reminded of a similar confusion with embedNever which we discussed in #3351 (comment).

@lenguyenthanh
Copy link
Member

yeah, tbh I wasn't sure which way I should go fix the docs or fix the code. My intuition is joinWithNever seems safer as the doc says then the current implementation. I'm happy to fix the doc too if We want to keep the current semantic of joinWithNever.

@armanbilge
Copy link
Member

armanbilge commented Sep 9, 2024

@lenguyenthanh your PR passes our test suite so that's a good sign that we are not depending on the bug and hopefully nobody else is either. Also we have precedent for fixing bugs even if they did cause some breakage ... (see the async_ bugfix in 3.5), not saying we want to repeat that necessarily, just saying we did do it 😅

@lenguyenthanh
Copy link
Member

I agree that We need to be careful about the changes. So, the question is what does it take to introduce this change? Or should we be safe and update the docs instead?

@lenguyenthanh
Copy link
Member

As we prefer updating the doc (#4131), how about update the docs like this (just copy/paste from scala doc)?:

-- If it canceled, attempt to self-cancel, and if the self-cancelation fails, **deadlock**
+- If it canceled, the caller is indefinitely suspended without termination (a.k.a **deadlock**)

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