Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@adamgfraser
Copy link
Contributor

Resolves #1684.

I set it up so that Meta is excluded for equals and hashCode. Key things to look at would be:

  1. That I got things set up so we can add additional metadata in the future while retaining binary compatibility.
  2. Any other types of metadata we want to add right now?
  3. How we are rendering the Cause with stackless. Right now it is pretty aggressive where we just show the first cause of parallel or sequential errors, don't print the stack trace of a Throwable, and don't print tracing information. So every failure is three of four lines saying a fiber failed, whether or not it was unchecked, a string representation of the error if available, and a line that no ZIO tracing is available.

I also noticed that some of the tests in CauseSpec weren't being run because there were two assertions on separate lines that weren't joined with a &&. 😱 Fortunately there were no failing tests after I fixed it.

Copying @ioleo.

@vasilmkd
Copy link
Contributor

Not to sound negative or anything, it's just that I don't have much experience with keeping binary compatibility and I really admire the effort.

I also know that this has nothing to do with the fact that we are close to ZIO 1.0 but more with the fact that we would like to evolve this further, or in a completely different direction and this would allow for a smoother upgrade path. I also know that I'm not going to be involved with the review of this and maybe you shouldn't listen to my opinion, but I'd feel much more sure in the future "binary sustainability" if we took the time to set up some tooling that make this possible. I also think this would be beneficial to the releases post 1.0.

As I said, I don't have much experience with this, but I'm pretty sure there are other Java and Scala libraries that include these types of checks in the toolchain. I'd also be willing to help, but not before two weeks from now, as I have exams. I'd like to know everyone's thoughts on this. Thanks.

@adamgfraser
Copy link
Contributor Author

@vasilmkd I think those are great points. There is actually an open issue for the hackathon to add MIMA support to check for binary compatibility (#427).


private def e9 = prop { (c: Cause[String]) =>
(Cause.stackless(c) must_== c) &&
(c must_== Cause.stackless(c))
Copy link
Member

Choose a reason for hiding this comment

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

👍

}

// Meta is excluded completely from equals & hashCode
final case class Meta[E](cause: Cause[E], data: Data) extends Cause[E] {
Copy link
Member

Choose a reason for hiding this comment

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

It may be time to make all these case classes / objects private so we have more flexibility to change Cause during the 1.x lifecycle. What do you think?

A basic fold method that hides Meta could be used to allow people to traverse.

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 think that makes sense. So then fold would go to needing to take a parameter for each constructor of Cause, because we couldn't allow people to pattern match against the Cause constructors, right?

}
}

final case class Data private[Cause] (stackless: Boolean)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this is enough for binary backward compatibility (I don't think so, but I am not sure). You might want to make the Data class private too

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 tried to do that initially but ran into issues because Data is exposed when users pattern match against Meta. But maybe to your point above if we disallow that then we can make this completely private. We just need to make sure we give users enough functionality to work with Cause like in the ZStream snippet I updated..

Copy link
Member

Choose a reason for hiding this comment

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

Did anyone say streams? 🤪

I'd be happy to move that snippet into Cause's companion object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@iravid Awesome! 😃

Copy link
Member

Choose a reason for hiding this comment

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

@adamgfraser Good point. I'm not exactly sure what to do here, but I do know if we don't hide enough, we're gonna have real trouble changing functionality in 1.x. We'd need MiMa to test to for sure either way...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why don't I try make all the constructors private and implementing a new version of fold. Then we should be able to make Data private. I can also try to set up MiMa so we can test binary compatibility and then we can just turn it on when we go live with 1.0.

Copy link
Member

Choose a reason for hiding this comment

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

That would be fantastic! Thank you! 🙏

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 tried to add MiMa but it looks like it needs to compare a current version against a released version. However, I was able to make the constructors and Data private so I think we should be in good shape from a binary compatibility perspective.

final case class Data private[Cause] (stackless: Boolean)

object Data {
final def apply(stackless: Boolean): Data =
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 this function is implied by using a case class isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're trying to preserve the ability to augment Data with additional fields while preserving binary compatibility. So the thought from @neko-kai, if I understood it correctly, was that by overriding these methods we could then forward them to a new version and keep the old version private for compatibility. But I admit I am still learning about this so I am more just trying to follow the advice of others with more expertise at this point.

)
}
case Meta(cause, data) =>
if (data.stackless) cause match {
Copy link
Member

Choose a reason for hiding this comment

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

You may want to pass Data in from above, that way you can have a default for all Meta, and each level can override it. For example, you might have stack nested inside stackless .That means you want the stack on the inside part but not on the outside part. This logic requires top-down inheritance of parent settings, with child override.

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 think we could do that by changing the last case to:

case m @ Meta(_, _) => causeToSequential(m)

The cases for all the other constructors that allow nesting already pass down the parent Meta. So then if you had nested Meta cases you would get to the Meta(Meta) branch and you would then trigger the rule that innermost scope takes precedence.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not convinced you can do it without a stack (either real stack or fake stack), as you have "edges" that should inherit parent behavior and "wedges" that have their own behavior, and you have to flip flop between them. However, we can fix this later, I am happy to get this merged in for 1.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we are definitely simplifying a little bit here in that we are throwing away the second branch of any Both or Then cases, which might themselves contain metadata indicating that they should be shown. But as you say we can clean this up later,

@adamgfraser
Copy link
Contributor Author

This is ready for another review.

@adamgfraser adamgfraser mentioned this pull request Sep 19, 2019
29 tasks
`Both.equals` satisfies commutativity $e7
"""
object CauseSpec
extends ZIOBaseSpec(
Copy link
Member

Choose a reason for hiding this comment

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

💪

}

final case class Die(value: Throwable) extends Cause[Nothing] {
object Fail {
Copy link
Member

Choose a reason for hiding this comment

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

I'd make all the companion objects private, too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need these if we want to expose the apply methods on them. Normally I wouldn't do this but there is a good amount of code that uses them, and then is a reserved keyword so we can't use the normal lower case smart constructor for that. I could delete the companion objects and replace with methods with capitalized names or we could go through and change everything, maybe using parallel and sequential for the smart constructors, though that is wordier.

new Both(left, right)
}

private case class Data(stackless: Boolean)
Copy link
Member

Choose a reason for hiding this comment

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

This should be well-hidden from Scalac. 😆

}

final case class Fail[E](value: E) extends Cause[E] {
private case class Fail[E](value: E) extends Cause[E] {
Copy link
Member

Choose a reason for hiding this comment

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

private final?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.

private case class Meta[E](cause: Cause[E], data: Data) extends Cause[E] {
override final def hashCode: Int = cause.hashCode
override final def equals(obj: Any): Boolean = obj match {
case traced: Traced[_] => cause == traced
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
case traced: Traced[_] => cause == traced
case traced: Traced[_] => cause == traced.cause

Copy link
Member

Choose a reason for hiding this comment

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

Traced could probably be replaced by Meta, tbh...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes.

* one exists.
*/
final def dieOption: Option[Throwable] =
fold(_ => None, t => Some(t), None)(_ orElse _, _ orElse _, (z, _) => z)
Copy link
Member

Choose a reason for hiding this comment

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

Might want to always use named parameters with the new fold 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. The ordering of type parameters is definitely arbitrary other than that I tried to curry it so that the Z parameter could be inferred.

@adamgfraser
Copy link
Contributor Author

@jdegoes I updated this to have causeToSequential take Data as a parameter. I think you were right about this being a better way to do it. Now the semantics of stackless are better defined where stackless causes all "leaves" of the Cause tree with Throwable failures to be rendered without stack traces and is overridable by inner scopes. This also makes it orthagonal to untraced where stackless deals exclusively with rendering of Throwable stack traces, untraced deals with removing ZIO traces, and you could combine the two if you want both behaviors.

The one issue that this still leaves open is the constructors. Normally my preference would be to just expose lower case "smart" constructors (e.g. die, fail, etc...). The complication here is that when is a reserved word and usage as an identifier is deprecated. So for now I have exposed companion object with apply methods to allow capitalized constructors (Both, Then). Are you okay with that or do you want to come up with different names? sequential and parallel seems most obvious but they are longer.

@adamgfraser adamgfraser requested a review from jdegoes September 23, 2019 18:34
@adamgfraser
Copy link
Contributor Author

@iravid I added a flatMap method on Cause. We can now say that Cause forms a monad with fail as unit. Let me know if you have any comments.

@adamgfraser adamgfraser dismissed stale reviews from ghostdogpr and neko-kai via 1a76255 October 1, 2019 10:03
@adamgfraser
Copy link
Contributor Author

@ghostdogpr Done. Yes, I think this is good to merge. Though now a couple of the builds on Scala.JS are failing. It looks unrelated, though I haven't seen these errors before:

:: problems summary ::
:::: ERRORS
	Server access Error: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: timestamp check failed url=https://repo.scala-sbt.org/scalasbt/maven-snapshots/org/fusesource/fusesource-pom/1.11/fusesource-pom-1.11.jar

	Server access Error: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: timestamp check failed url=https://repo.scala-sbt.org/scalasbt/maven-snapshots/org/fusesource/jansi/jansi-project/1.12/jansi-project-1.12.jar

And later:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

<--- Last few GCs --->

  122003 ms: Mark-sweep 1358.7 (1435.0) -> 1358.4 (1435.0) MB, 2487.3 / 0 ms [allocation failure] [GC in old space requested].
  124501 ms: Mark-sweep 1358.4 (1435.0) -> 1358.4 (1435.0) MB, 2497.6 / 0 ms [allocation failure] [GC in old space requested].
  127013 ms: Mark-sweep 1358.4 (1435.0) -> 1358.3 (1435.0) MB, 2512.2 / 0 ms [last resort gc].
  129557 ms: Mark-sweep 1358.3 (1435.0) -> 1358.2 (1435.0) MB, 2544.0 / 0 ms [last resort gc].


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0xdd8f6c9fa9 <JS Object>
    3: from__sc_IterableOnce__sci_List [/home/circleci/project/core-tests/js/target/scala-2.13/core-tests-test-fastopt.js:144739] [pc=0x1eae85cf5d63] (this=0x13d65fec9209 <a $c_sci_List$ with map 0xcd93277b211>,coll=0x2d8ac8a87801 <a $c_sjsr_WrappedVarArgs with map 0xcd9327865d9>)
    4: collectAll__sc_Iterable__s_Option [/home/circleci/project/core-tests/js/target/scala-2.13/core-tests-test-fast...

[error] org.scalajs.testcommon.RPCCore$ClosedException: org.scalajs.jsenv.ComJSEnv$ComClosedException: JSCom has been closed
[error] 	at org.scalajs.testcommon.RPCCore.helpClose(RPCCore.scala:224)
[error] 	at org.scalajs.testcommon.RPCCore.call(RPCCore.scala:165)
[error] 	at org.scalajs.testcommon.RunMuxRPC.call(RunMuxRPC.scala:45)
[error] 	at org.scalajs.testadapter.RunnerAdapter.$anonfun$done$1(RunnerAdapter.scala:58)
[error] 	at scala.collection.immutable.List.map(List.scala:290)
[error] 	at org.scalajs.testadapter.RunnerAdapter.done(RunnerAdapter.scala:58)
[error] 	at sbt.Defaults$.$anonfun$allTestGroupsTask$11(Defaults.scala:1222)
[error] 	at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:238)
[error] 	at scala.collection.immutable.Map$Map3.foreach(Map.scala:195)
[error] 	at scala.collection.TraversableLike.map(TraversableLike.scala:238)
[error] 	at scala.collection.TraversableLike.map$(TraversableLike.scala:231)
[error] 	at scala.collection.AbstractTraversable.map(Traversable.scala:108)
[error] 	at sbt.Defaults$.$anonfun$allTestGroupsTask$5(Defaults.scala:1220)
[error] 	at scala.Function1.$anonfun$compose$1(Function1.scala:49)
[error] 	at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
[error] 	at sbt.std.Transform$$anon$4.work(Transform.scala:67)
[error] 	at sbt.Execute.$anonfun$submit$2(Execute.scala:280)
[error] 	at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:19)
[error] 	at sbt.Execute.work(Execute.scala:289)
[error] 	at sbt.Execute.$anonfun$submit$1(Execute.scala:280)
[error] 	at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:178)
[error] 	at sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
[error] 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] 	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[error] 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
[error] 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
[error] 	at java.lang.Thread.run(Thread.java:748)
[error] Caused by: org.scalajs.jsenv.ComJSEnv$ComClosedException: JSCom has been closed
[error] 	at org.scalajs.jsenv.nodejs.AbstractNodeJSEnv$NodeComJSRunner.receive(AbstractNodeJSEnv.scala:313)
[error] 	at org.scalajs.jsenv.nodejs.AbstractNodeJSEnv$NodeComJSRunner.receive$(AbstractNodeJSEnv.scala:293)
[error] 	at org.scalajs.jsenv.nodejs.NodeJSEnv$ComNodeRunner.receive(NodeJSEnv.scala:70)
[error] 	at org.scalajs.jsenv.ComJSRunner.receive(ComJSRunner.scala:27)
[error] 	at org.scalajs.jsenv.ComJSRunner.receive$(ComJSRunner.scala:27)
[error] 	at org.scalajs.jsenv.nodejs.NodeJSEnv$ComNodeRunner.receive(NodeJSEnv.scala:70)
[error] 	at org.scalajs.testadapter.ComJSEnvRPC$$anon$1.run(ComJSEnvRPC.scala:34)
[error] Caused by: java.io.EOFException
[error] 	at java.io.DataInputStream.readInt(DataInputStream.java:392)
[error] 	at org.scalajs.jsenv.nodejs.AbstractNodeJSEnv$NodeComJSRunner.receive(AbstractNodeJSEnv.scala:303)
[error] 	at org.scalajs.jsenv.nodejs.AbstractNodeJSEnv$NodeComJSRunner.receive$(AbstractNodeJSEnv.scala:293)
[error] 	at org.scalajs.jsenv.nodejs.NodeJSEnv$ComNodeRunner.receive(NodeJSEnv.scala:70)
[error] 	at org.scalajs.jsenv.ComJSRunner.receive(ComJSRunner.scala:27)
[error] 	at org.scalajs.jsenv.ComJSRunner.receive$(ComJSRunner.scala:27)
[error] 	at org.scalajs.jsenv.nodejs.NodeJSEnv$ComNodeRunner.receive(NodeJSEnv.scala:70)
[error] 	at org.scalajs.testadapter.ComJSEnvRPC$$anon$1.run(ComJSEnvRPC.scala:34)
[error] (coreTestsJS / Test / executeTests) org.scalajs.testcommon.RPCCore$ClosedException: org.scalajs.jsenv.ComJSEnv$ComClosedException: JSCom has been closed
[error] Total time: 487 s (08:07), completed Oct 1, 2019 10:16:15 AM
Exited with code 1

@ghostdogpr
Copy link
Member

Oh you fixed the problem with master 👍

@adamgfraser
Copy link
Contributor Author

Yes, we had a conflict when I removed the ZIO internal test suite dependency last night and then the test migrations we merged were relying on the implementation of nonFlaky in that instead of the one in TestAspect#nonFlaky.

@adamgfraser
Copy link
Contributor Author

Hmmm, looks like we're getting the same error.

@adamgfraser
Copy link
Contributor Author

I think I see the problem. The previous version was not actually doing 100 runs on Scala.js, only on JVM. So when I switched it over we ended up doing way more runs of those tests on Scala.js, which I guess caused us to run out of heap? Anyway, let me adjust the settings so we get the same behavior as the original and push another change.

@adamgfraser
Copy link
Contributor Author

@ghostdogpr Hmmm. Still not working, and always those same two builds.. I think there are a few things we can pursue:

  1. I can try to do a PR that just fixes the issue on master and does nothing else so we try to isolate the issue.
  2. We can try to look at when those messages started popping up. The first message about the server access error appears even on the JS build that does complete successfully.
  3. We can look into if there is any CI issue that is causing this or CI setting that we could change regarding heap or garbage collection.

@ghostdogpr
Copy link
Member

Maybe try your first suggestion first? There wasn't any issue in #1862 but this one looks systematic so that's really weird.

@adamgfraser
Copy link
Contributor Author

Note that this reverts the ZStream#flatMap implementation. If @iravid is okay with it this is ready to merge.

@iravid
Copy link
Member

iravid commented Oct 1, 2019

Yes, totally fine! Thanks for spotting it @adamgfraser

@ghostdogpr ghostdogpr merged commit e490dfa into zio:master Oct 1, 2019
@ghostdogpr ghostdogpr mentioned this pull request Oct 1, 2019
@adamgfraser adamgfraser deleted the 1684 branch October 2, 2019 00:21
Twizty pushed a commit to Twizty/zio that referenced this pull request Nov 13, 2019
* implement Cause.Meta

* fix access levels

* make case class final

* address review comments

* migrate tests to ZIO Test

* address review comments

* fix type

* fix another typo

* pass `Data` in from above

* implement flatMap

* implement flatten

* implement unsandbox via flatten

* apply nonFlaky only on JVM

* use jvm test aspect more

* try ignoring tests

* start reenabling tests

* remove unused import

* reenable more tests

* Add ZSchedule.elapsed example to docs (zio#1871)

* Add ZSchedule.elapsed example to docs

* ignore flaky supervision test (zio#1874)

* CircleCI improvements (zio#1859)

* CircleCI improvements

* CircleCI improvements, yaml fix

* CircleCI improvements, yaml fix 2

* CircleCI improvements, yaml fix 3

* Implement ZIO#timeoutFork (zio#1856)

* implement timeoutFork

* address review comments
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.

Add Meta term to Cause enumeration

9 participants