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

[ETCM-269] future task #756

Merged
merged 44 commits into from
Nov 4, 2020
Merged

[ETCM-269] future task #756

merged 44 commits into from
Nov 4, 2020

Conversation

lemastero
Copy link
Contributor

@lemastero lemastero commented Oct 22, 2020

Description

Replace Future with Task in JSON RPC.

Proposed Solution

Important Changes Introduced

Testing

@rtkaczyk
Copy link
Contributor

When done, could you please provide a brief argument for the choice of Monix rather than cats-effect or ZIO?

@biandratti biandratti requested review from KonradStaniec and ntallar and removed request for KonradStaniec and ntallar October 23, 2020 21:29
@lemastero lemastero marked this pull request as ready for review October 27, 2020 10:03
@lemastero lemastero changed the title WIP [ETCM-269] future task [ETCM-269] future task Oct 27, 2020
@lemastero lemastero marked this pull request as draft October 27, 2020 12:36
@kapke
Copy link
Contributor

kapke commented Oct 28, 2020

One more thing, that might be useful - in service specs you might use SpecBase traits and for sync, blocking specs write a wrapper like done there: https://github.com/input-output-hk/mantis/pull/735/files#diff-4f204c964a1f172070b2e06bc6b410e888aa541c50fe67c44824d5028d1e82d8R59. So new specs are free to return Task and not bother about actually executing it

@lemastero
Copy link
Contributor Author

lemastero commented Oct 29, 2020

could you please provide a brief argument for the choice of Monix rather than cats-effect or ZIO?

I am strongly convinced ZIO is better.

  • very detailed thread dumps
  • explicit errors (you can use Task[Either[E,A]] but you need nested maps/flatmaps to handle error, ZIO expose directly methods to deal with that, and in sense, you are encouraged to say what error type is)
  • capabilities of FP patterns like monad transformers, Kleisli, Contravariant, Functor, Profunctor, Arrow, Monad, MonadError, Compose are included in ZIO and hence is easier and more performant
  • you can change input/environment and that is not possible neither with Monix/cats-effect nor with Monix BIO
  • ZIO has consistent design, always input, output and error channel not only in ZIO but in other abstractions

monix.Task because:

  • it is already in the project
  • it requires the least changes (when changing Future to Task you need to just change name + take care of differences in laziness and slightly different API) with ZIO there is more changes (you can model better error, because you can model input, functions become val, ...)
  • fewer changes == better review
  • fewer changes == less opportunity to make error

Why monix.Task not cas-effect - I think abstracting over effect type sounds good in theory but:

  • is way more complex in terms of wiring, has a more steep learning curve. I was on project with monix, I was on project with monix + CE and the latter was significantly more complex. I like fancy types in libraries/frameworks - more powerful, more general. I prefer simple code in production = easier to maintain.
  • IMHO using CE to be able to switch IO monad implementation from Task to ZIO sounds good but is not great. ZIO powers come from modeling explicitly errors and input + consistent design + use data types instead of traits (speed). CE has the same design as monix. So the only reasonable IO type implementation for CE is Task.
  • switching from Task to ZIO is simpler (in theory you can import zio.Task instead of monix.eval.Task in the simplest approach) compared to CE to ZIO

I am willing to try to port code to ZIO and see by myself what would be practical benefits for mantis and come back to this discussion. Yet for now, Task seems like a good choice (stepping stone) :)

build.sbt Outdated Show resolved Hide resolved
Copy link
Contributor

@kapke kapke left a comment

Choose a reason for hiding this comment

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

Looks good to me!
While I do think tests could be improved a bit, I think that they can be improved separately from this PR, even if organically when working on RPC

message: Any
)(implicit timeout: Timeout, classTag: ClassTag[A], sender: ActorRef = Actor.noSender): Task[A] =
Task
.fromFuture((to ? message).mapTo[A])
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to use strict evaluation here? Shouldn't be Task.deferFuture?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very good question 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This breaks the tests, I will explore what is needed to fix that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to Task.deferFuture: 7494676

val result: ServiceResponse[ListPeersInfoResponse] =
debugService.listPeersInfo(ListPeersInfoRequest())
val result =
debugService.listPeersInfo(ListPeersInfoRequest()).runToFuture
Copy link
Contributor

Choose a reason for hiding this comment

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

Why runToFuture? To be consistent we should use runSyncUnsafe everywhere (and avoid futureValue)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

runSyncUnsafe was implemented at the beginning. But this break the tests:

[info] DebugServiceSpec:
[info] DebugService
[info] - should return list of peers info *** FAILED *** (8 seconds, 376 milliseconds)
[info]   java.lang.AssertionError: assertion failed: timeout (3 seconds) during expectMsg while waiting for PeerInfoRequest(PeerId(testProbe-4))
[info]   at scala.Predef$.assert(Predef.scala:223)
[info]   at akka.testkit.TestKitBase.expectMsg_internal(TestKit.scala:459)
[info]   at akka.testkit.TestKitBase.expectMsg(TestKit.scala:436)
[info]   at akka.testkit.TestKitBase.expectMsg$(TestKit.scala:436)
[info]   at akka.testkit.TestKit.expectMsg(TestKit.scala:969)
[info]   at io.iohk.ethereum.jsonrpc.DebugServiceSpec$$anon$1.<init>(DebugServiceSpec.scala:35)
[info]   at io.iohk.ethereum.jsonrpc.DebugServiceSpec.$anonfun$new$1(DebugServiceSpec.scala:28)
[info]   at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85)
[info]   at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83)
[info]   at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
[info]   ...

Some tests contain a mix of:

  • expectations on Akka TestProbe
  • sending messages to actors
  • assertions at the end.

In some cases, I moved expectations and assertions at the end. But in some cases, I was not able to do it so I used conversion to Future to get async behavior.

Copy link
Contributor

@mmrozek mmrozek left a comment

Choose a reason for hiding this comment

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

LGTM!

@lemastero lemastero merged commit 53e6dda into develop Nov 4, 2020
@dzajkowski dzajkowski deleted the feature/ETCM_269_future_task branch April 9, 2021 12:04
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.

5 participants