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

Change IO's run-loop to encode error handlers #46

Merged
merged 3 commits into from
May 8, 2017

Conversation

alexandru
Copy link
Member

@alexandru alexandru commented May 6, 2017

I discovered a weird space leak that I talked about in #42 (comment) by debugging the behavior of this law:

  lazy val stackSafetyOnRepeatedAttempts = {
    val result = (0 until 10000).foldLeft(F.delay(())) { (acc, _) =>
      F.attempt(acc).map(_ => ())
    }

    F.runAsync(result)(_ => IO.unit).unsafeRunSync() <-> (())
  }

This PR fixes this space leak by changing the behavior of IO#attempt:

  • removes the AndThen.ShortCircuit logic
  • adds the notion of AndThen.ErrorHandler, which is meant to process errors
  • upon an error being signaled, in the AndThen implementation we discard Single references until we reach an ErrorHandler that can process our error, or until we've got no more AndThen nodes to process, in which case we rethrow the error ... this is basically how Java's call-stack also works

This implementation also opens up the possibility of a more optimized handleErrorWith, since now we don't necessarily need attempt.flatMap to express it.

@alexandru alexandru changed the title Change IO`s run-loop implementation to encode error handlers Change IO's run-loop implementation to encode error handlers May 6, 2017
@alexandru alexandru changed the title Change IO's run-loop implementation to encode error handlers Change IO's run-loop to encode error handlers May 6, 2017
@codecov-io
Copy link

codecov-io commented May 6, 2017

Codecov Report

Merging #46 into master will increase coverage by 0.97%.
The diff coverage is 97.5%.

@@            Coverage Diff             @@
##           master      #46      +/-   ##
==========================================
+ Coverage   86.94%   87.91%   +0.97%     
==========================================
  Files          17       17              
  Lines         268      273       +5     
  Branches       10       20      +10     
==========================================
+ Hits          233      240       +7     
+ Misses         35       33       -2

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 looks really fantastic. Would you mind adding a couple more AndThenTests though? I think the test coverage here is probably alright, but it's through IO. If only for documentation reasons, having some of these edge cases tested directly on AndThen would be nice.

Very clever trick though. Obvious in hindsight.

var continue = true

@inline def processRight(f: (Any) => Any): Unit =
Copy link
Member

Choose a reason for hiding this comment

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

This is probably actually faster if we don't inline in the bytecode, and HotSpot is allowed to do its thing due to the byte size of the body.

@alexandru
Copy link
Member Author

@djspiewak will do, tomorrow :-)

@alexandru
Copy link
Member Author

Changes:

  • have added more tests in AndThenTests, bringing coverage to 100% :-P
    • btw: those tests specified with forAll were not running, for ScalaCheck you have to use check, see change
  • removed those two @inline annotations

@@ -44,14 +44,14 @@ private[effect] sealed abstract class AndThen[-A, +B] extends Product with Seria
var hasSuccessRef = isSuccess
var continue = true

def processRight(f: (Any) => Any): Unit =
def processSuccess(f: (Any) => Any): Unit =
Copy link
Member

Choose a reason for hiding this comment

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

👍👍👍

@djspiewak
Copy link
Member

I'm 👍 on this, but I want to hear from @mpilquist before merging. If he gives it the thumbs up, I'll bring it in. I'm pretty excited about this too, since I suspect it'll make Effect[StateT[F, S, ?]] (given Effect[F] and Monoid[S]) possible, whereas before the laws would blow the heap.

Copy link
Member

@mpilquist mpilquist left a comment

Choose a reason for hiding this comment

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

👍 Looks great!

@@ -135,7 +141,17 @@ sealed abstract class IO[+A] {
*
* @see [[IO.raiseError]]
*/
def attempt: IO[Either[Throwable, A]]
def attempt: IO[Either[Throwable, A]] = {
val fe = AndThen((a: A) => Pure(Right(a)), e => Pure(Left(e)))
Copy link
Member

Choose a reason for hiding this comment

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

Avoid constructing this in Pure and RaiseError cases? E.g., change to def

Copy link
Member

Choose a reason for hiding this comment

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

What do you mean?

Copy link
Member

Choose a reason for hiding this comment

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

Change val fe = ... to def fe = ... to avoid constructing a new AndThen and then not using it in the Pure and RaiseError cases below.

Copy link
Member

Choose a reason for hiding this comment

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

Oh right! Github cut off the diff so I couldn't see. I'll make that change on merge.


class AndThenTests extends FunSuite with Matchers {
class AndThenTests extends FunSuite with Matchers with Checkers {
Copy link
Member

Choose a reason for hiding this comment

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

Minor thing: I prefer GeneratorDrivenPropertyChecks and using matchers in forAll instead of switching to the boolean assertion style. Not suggesting changing this though.

Copy link
Member

Choose a reason for hiding this comment

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

Oh is that how you use matchers in properties? I was wondering. I miss Specs2. :-(

@djspiewak djspiewak merged commit 17dabc4 into typelevel:master May 8, 2017
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.

None yet

4 participants