diff --git a/.scala-steward.conf b/.scala-steward.conf index 8090c3edc083..6984ef84fd9d 100644 --- a/.scala-steward.conf +++ b/.scala-steward.conf @@ -1,3 +1,7 @@ updates.ignore = [ { groupId = "org.scala-native", artifactId="sbt-scala-native", version = "0.5.7" } ] + +updates.pin = [ + { groupId = "com.github.sbt", artifactId="sbt-ci-release", version = "1.9.3" } +] diff --git a/build.sbt b/build.sbt index f04890ac92fa..abe212714d2b 100644 --- a/build.sbt +++ b/build.sbt @@ -538,7 +538,7 @@ lazy val commonJunitTestSettings = Seq( ), libraryDependencies ++= Seq( "junit" % "junit" % "4.13.2" % Test, - "org.scala-lang.modules" %% "scala-xml" % "2.3.0" % Test, + "org.scala-lang.modules" %% "scala-xml" % "2.4.0" % Test, "org.apache.maven" % "maven-embedder" % "3.9.9" % Test, "org.apache.maven" % "maven-compat" % "3.9.9" % Test, "com.google.inject" % "guice" % "6.0.0" % Test, @@ -659,7 +659,7 @@ lazy val benchmarks = project.module "org.scalacheck" %% "scalacheck" % ScalaCheckVersion, "qa.hedgehog" %% "hedgehog-core" % "0.12.0", "com.github.japgolly.nyaya" %% "nyaya-gen" % nyanaVersion, - "org.springframework" % "spring-core" % "6.2.6" + "org.springframework" % "spring-core" % "6.2.7" ) }, excludeDependencies ++= { diff --git a/core-tests/jvm-native/src/test/scala/zio/internal/OneShotSpec.scala b/core-tests/jvm-native/src/test/scala/zio/internal/OneShotSpec.scala index 1b8a91be9c81..b064e6fb4953 100644 --- a/core-tests/jvm-native/src/test/scala/zio/internal/OneShotSpec.scala +++ b/core-tests/jvm-native/src/test/scala/zio/internal/OneShotSpec.scala @@ -34,7 +34,7 @@ object OneShotSpec extends ZIOBaseSpec { test("get must fail if no value is set") { val oneShot = OneShot.make[Object] - assert(oneShot.get(10000L))(throwsA[Error]) + assert(oneShot.get(10L))(throwsA[OneShot.TimeoutException]) }, test("cannot set value twice") { val oneShot = OneShot.make[Int] diff --git a/core-tests/shared/src/test/scala/zio/CauseSpec.scala b/core-tests/shared/src/test/scala/zio/CauseSpec.scala index 04919cfb0346..0d0b99e0a79b 100644 --- a/core-tests/shared/src/test/scala/zio/CauseSpec.scala +++ b/core-tests/shared/src/test/scala/zio/CauseSpec.scala @@ -1,10 +1,12 @@ package zio -import zio.Cause.{Both, Then, empty} +import zio.Cause.{Both, Die, Empty, Fail, Interrupt, Stackless, Then, empty} import zio.test.Assertion._ import zio.test.TestAspect.samples import zio.test._ +import scala.annotation.tailrec + object CauseSpec extends ZIOBaseSpec { def spec = suite("CauseSpec")( @@ -169,6 +171,77 @@ object CauseSpec extends ZIOBaseSpec { assert(stripped)(isNone) } ), + suite("toString")( + test("not fail with StackOverflowError") { + @tailrec + def genCause(current: Cause[String], depth: Int): Cause[String] = + if (depth <= 0) { + current + } else { + genCause(Both(current, Cause.fail(s"Error$depth")), depth - 1) + } + + val cause = genCause(Cause.fail("Error"), 20000) + + assert(cause.toString)(anything) + }, + test("return properly structured string for nested cause") { + val fiberId = FiberId(123, 456, Trace.empty) + val stackTrace = StackTrace(fiberId, Chunk.empty) + + val cause = Both( + Both( + Both( + Stackless(Empty, true), + Die(new Exception("Ex1"), stackTrace) + ), + Empty + ), + Both( + Fail(new Exception("Ex2"), stackTrace), + Then( + Empty, + Interrupt(fiberId, stackTrace) + ) + ) + ) + + val expected = + """Both(Both(Both(Stackless(Empty,true),Die(java.lang.Exception: Ex1,Stack trace for thread "zio-fiber-123": + |)),Empty),Both(Fail(java.lang.Exception: Ex2,Stack trace for thread "zio-fiber-123": + |),Then(Empty,Interrupt(Runtime(123,456000,),Stack trace for thread "zio-fiber-123": + |))))""".stripMargin + + assert(cause.toString)(equalTo(expected)) + }, + test("return properly structured string for simple causes") { + val fiberId = FiberId(123, 456, Trace.empty) + val stackTrace = StackTrace(fiberId, Chunk.empty) + + val simpleCauseExpectation = Seq( + (Empty, "Empty"), + ( + Die(new Exception("Ex1"), stackTrace), + """Die(java.lang.Exception: Ex1,Stack trace for thread "zio-fiber-123": + |)""".stripMargin + ), + ( + Fail(new Exception("Ex2"), stackTrace), + """Fail(java.lang.Exception: Ex2,Stack trace for thread "zio-fiber-123": + |)""".stripMargin + ), + ( + Interrupt(fiberId, stackTrace), + """Interrupt(Runtime(123,456000,),Stack trace for thread "zio-fiber-123": + |)""".stripMargin + ) + ) + + simpleCauseExpectation.foldLeft(assertTrue(true)) { case (assertion, (cause, expectation)) => + assertion && assert(cause.toString)(equalTo(expectation)) + } + } + ), suite("filter")( test("fail.filter(false)") { val f1 = Cause.fail(()) @@ -264,12 +337,10 @@ object CauseSpec extends ZIOBaseSpec { val bldr = Seq.newBuilder[(Seq[String], Boolean)] val c = c123.filter { c => val res = !c.isInstanceOf[Cause.Both[?]] - println(s"applying filter on: ${c.failures} -> $res") bldr += (c.failures -> res) res } - println(s"\nfiltered cause: $c") zio.test.assert(c)(Assertion.equalTo(c1)) && zio.test.assert(bldr.result()) { equalTo { diff --git a/core-tests/shared/src/test/scala/zio/ConfigProviderSpec.scala b/core-tests/shared/src/test/scala/zio/ConfigProviderSpec.scala index d2939196231c..f203a19cef3c 100644 --- a/core-tests/shared/src/test/scala/zio/ConfigProviderSpec.scala +++ b/core-tests/shared/src/test/scala/zio/ConfigProviderSpec.scala @@ -969,7 +969,6 @@ object ConfigProviderSpec extends ZIOBaseSpec { for { result <- configProvider.load(config) - _ = println(result) } yield assertTrue(result == List(Nil, List(1), List(1, 2))) } } diff --git a/core-tests/shared/src/test/scala/zio/LoggingSpec.scala b/core-tests/shared/src/test/scala/zio/LoggingSpec.scala index d4dd1ac2e956..4d156c809acf 100644 --- a/core-tests/shared/src/test/scala/zio/LoggingSpec.scala +++ b/core-tests/shared/src/test/scala/zio/LoggingSpec.scala @@ -49,7 +49,7 @@ object LoggingSpec extends ZIOBaseSpec { for { _ <- ZIO.logSpan("test span")(ZIO.log("It's alive!")) output <- ZTestLogger.logOutput - _ <- ZIO.debug(output(0).call(ZLogger.default)) + _ = output(0).call(ZLogger.default) } yield assertTrue(true) }, test("an empty log size is, at least, 142 characters") { @@ -79,5 +79,5 @@ object LoggingSpec extends ZIOBaseSpec { assertTrue(output(0).context.get(ref).contains(value)) ) } - ) + ) @@ TestAspect.silentLogging } diff --git a/core-tests/shared/src/test/scala/zio/TagCorrectnessSpec.scala b/core-tests/shared/src/test/scala/zio/TagCorrectnessSpec.scala index 6d7d71acf12a..510a5c9f9483 100644 --- a/core-tests/shared/src/test/scala/zio/TagCorrectnessSpec.scala +++ b/core-tests/shared/src/test/scala/zio/TagCorrectnessSpec.scala @@ -151,13 +151,11 @@ object HigherKindedTagCorrectness extends ZIOBaseSpec { (for { _ <- Cache.put[Option, Int, String](1, "one") value <- Cache.get[Option, Int, String](1) - _ <- ZIO.debug(value) a = Tag[Cache[Option, Int, String]] b = Tag[Cache[({ type Out[In] = Option[In] })#Out, Int, String]] c = Tag[Cache[({ type Bar = Double; type Out[In] = Option[Bar] })#Out, Int, String]] d = Tag[Cache[({ type Out[In] = Option[Double] })#Out, Int, String]] e = Tag[Cache[({ type Id[A] = A; type Out[In] = Option[Id[In]] })#Out, Int, String]] - _ <- ZIO.debug(s"WHAT" + b) } yield assertTrue( a.tag <:< b.tag, b.tag <:< a.tag, diff --git a/core-tests/shared/src/test/scala/zio/ZKeyedPoolSpec.scala b/core-tests/shared/src/test/scala/zio/ZKeyedPoolSpec.scala index ddfd157bc2c1..77f10ca64c32 100644 --- a/core-tests/shared/src/test/scala/zio/ZKeyedPoolSpec.scala +++ b/core-tests/shared/src/test/scala/zio/ZKeyedPoolSpec.scala @@ -4,6 +4,8 @@ import zio.test._ import zio.test.TestAspect._ object ZKeyedPoolSpec extends ZIOBaseSpec { + final private val nPar = 100 + override def spec: Spec[TestEnvironment with Scope, Any] = suite("ZKeyedPoolSpec")( test("acquire release many successfully while other key is blocked") { @@ -12,13 +14,13 @@ object ZKeyedPoolSpec extends ZIOBaseSpec { _ <- pool.get("key1").repeatN(3).unit fiber <- ZIO - .foreachParDiscard(1 to 400) { _ => + .foreachParDiscard(1 to nPar) { _ => ZIO.scoped { pool.get("key2") *> Clock.sleep(10.millis) } } .fork - _ <- TestClock.adjust((10 * 400).millis) + _ <- TestClock.adjust((10 * nPar).millis) _ <- fiber.join } yield assertCompletes }, @@ -28,7 +30,7 @@ object ZKeyedPoolSpec extends ZIOBaseSpec { pool <- ZKeyedPool.make((key: String) => counter.modify(n => (s"$key-$n", n + 1)), size = 4) fiber <- ZIO - .foreachParDiscard(1 to 400) { _ => + .foreachParDiscard(1 to nPar) { _ => ZIO.scoped { pool.get("key1").flatMap { value => pool.invalidate(value).whenZIO(Random.nextBoolean) *> @@ -37,7 +39,7 @@ object ZKeyedPoolSpec extends ZIOBaseSpec { } } .fork - _ <- TestClock.adjust((15 * 400).millis) + _ <- TestClock.adjust((15 * nPar).millis) _ <- fiber.join } yield assertCompletes }, diff --git a/core-tests/shared/src/test/scala/zio/internal/IsFatalSpec.scala b/core-tests/shared/src/test/scala/zio/internal/IsFatalSpec.scala new file mode 100644 index 000000000000..f65ef9c3a7cb --- /dev/null +++ b/core-tests/shared/src/test/scala/zio/internal/IsFatalSpec.scala @@ -0,0 +1,47 @@ +package zio.internal + +import zio.test.Assertion._ +import zio.test._ +import zio.ZIOBaseSpec + +object IsFatalSpec extends ZIOBaseSpec { + def spec = + suite("IsFatal")( + suite("empty")( + test("apply(VirtualMachineError)") { + val isFatal = IsFatal.empty + assert(isFatal(new OutOfMemoryError()))(isTrue) && + assert(isFatal(new StackOverflowError()))(isTrue) + }, + test("apply(Non-VirtualMachineError)") { + val isFatal = IsFatal.empty + assert(isFatal(new RuntimeException()))(isFalse) && + assert(isFatal(new IllegalArgumentException()))(isFalse) + }, + test("|") { + val tag = IsFatal(classOf[RuntimeException]) + assert(IsFatal.empty | tag)(equalTo(tag)) && + assert(tag | IsFatal.empty)(equalTo(tag)) + } + ), + suite("Tag")( + test("matches assignable types") { + val isFatal = IsFatal(classOf[RuntimeException]) + assert(isFatal(new RuntimeException()))(isTrue) && + assert(isFatal(new IllegalArgumentException()))(isTrue) && + assert(isFatal(new Exception()))(isFalse) + } + ), + suite("Both")( + test("matches any of the two") { + val first = IsFatal(classOf[RuntimeException]) + val second = IsFatal(classOf[Exception]) + val combined = first | second + assert(combined(new RuntimeException()))(isTrue) && + assert(combined(new IllegalArgumentException()))(isTrue) && + assert(combined(new Exception()))(isTrue) && + assert(combined(new Throwable()))(isFalse) + } + ) + ) +} diff --git a/core/jvm-native/src/main/scala/zio/ZIOAppPlatformSpecific.scala b/core/jvm-native/src/main/scala/zio/ZIOAppPlatformSpecific.scala index 484178de776b..3849296b5a9e 100644 --- a/core/jvm-native/src/main/scala/zio/ZIOAppPlatformSpecific.scala +++ b/core/jvm-native/src/main/scala/zio/ZIOAppPlatformSpecific.scala @@ -10,7 +10,7 @@ private[zio] trait ZIOAppPlatformSpecific { self: ZIOApp => */ final def main(args0: Array[String]): Unit = { implicit val trace: Trace = Trace.empty - implicit val unsafe: Unsafe = Unsafe.unsafe + implicit val unsafe: Unsafe = Unsafe val newLayer = ZLayer.succeed(ZIOAppArgs(Chunk.fromIterable(args0))) >>> @@ -23,42 +23,62 @@ private[zio] trait ZIOAppPlatformSpecific { self: ZIOApp => result <- runtime.run(ZIO.scoped[Environment with ZIOAppArgs](run)).tapErrorCause(ZIO.logErrorCause(_)) } yield result).provideLayer(newLayer.tapErrorCause(ZIO.logErrorCause(_))) - runtime.unsafe.run { - ZIO.uninterruptible { - for { - fiberId <- ZIO.fiberId - fiber <- workflow.interruptible.exitWith { exit0 => - val exitCode = if (exit0.isSuccess) ExitCode.success else ExitCode.failure - interruptRootFibers(fiberId).as(exitCode) - }.fork - _ <- - ZIO.succeed(Platform.addShutdownHook { () => - if (shuttingDown.compareAndSet(false, true)) { + val shutdownLatch = internal.OneShot.make[Unit] - if (FiberRuntime.catastrophicFailure.get) { - println( - "**** WARNING ****\n" + - "Catastrophic error encountered. " + - "Application not safely interrupted. " + - "Resources may be leaked. " + - "Check the logs for more details and consider overriding `Runtime.reportFatal` to capture context." - ) - } else { - // NOTE: try-catch likely not needed, - // but guarding against cases where the submission of the task fails spuriously - try { - fiber.tellInterrupt(Cause.interrupt(fiberId)) - } catch { - case _: Throwable => - } - } + def shutdownHook(fiberId: FiberId, fiber: Fiber.Runtime[Nothing, ExitCode]): Unit = + Platform.addShutdownHook { () => + if (shuttingDown.compareAndSet(false, true)) { + if (FiberRuntime.catastrophicFailure.get) { + println( + "**** WARNING ****\n" + + "Catastrophic error encountered. " + + "Application not safely interrupted. " + + "Resources may be leaked. " + + "Check the logs for more details and consider overriding `Runtime.reportFatal` to capture context." + ) + } else { + try { + fiber.tellInterrupt(Cause.interrupt(fiberId)) + gracefulShutdownTimeout match { + case Duration.Infinity => shutdownLatch.get() + case d if d <= Duration.Zero => () + case d => shutdownLatch.get(d.toMillis) } - }) - result <- fiber.join - _ <- exit(result) - } yield () + } catch { + case _: OneShot.TimeoutException => + println( + "**** WARNING ****\n" + + s"Timed out waiting for ZIO application to shut down after ${gracefulShutdownTimeout.render}. " + + "You can adjust your application's shutdown timeout by overriding the `shutdownTimeout` method" + ) + case _: Throwable => + } + } + } } - }.getOrThrowFiberFailure() + + val exit0 = + runtime.unsafe.run { + ZIO.uninterruptible { + for { + fiberId <- ZIO.fiberId + fiber <- workflow.interruptible.exitWith { exit0 => + val exitCode = if (exit0.isSuccess) ExitCode.success else ExitCode.failure + interruptRootFibers(fiberId).as(exitCode) + }.fork + result <- { + shutdownHook(fiberId, fiber) + fiber.join + } + } yield result + } + } + + shutdownLatch.set(()) + exit0 match { + case Exit.Success(code) => exitUnsafe(code) + case f => exitUnsafe(ExitCode.failure) + } } private def interruptRootFibers(mainFiberId: FiberId)(implicit trace: Trace): UIO[Unit] = diff --git a/core/jvm-native/src/main/scala/zio/internal/OneShot.scala b/core/jvm-native/src/main/scala/zio/internal/OneShot.scala index 1e7f9ab9f105..0f2f8462c527 100644 --- a/core/jvm-native/src/main/scala/zio/internal/OneShot.scala +++ b/core/jvm-native/src/main/scala/zio/internal/OneShot.scala @@ -72,7 +72,7 @@ private[zio] final class OneShot[A] private () extends ReentrantLock(false) { this.unlock() } - if (value eq null) throw new Error("Timed out waiting for variable to be set") + if (value eq null) throw new OneShot.TimeoutException value } @@ -106,4 +106,6 @@ private[zio] object OneShot { * Makes a new (unset) variable. */ def make[A]: OneShot[A] = new OneShot() + + final class TimeoutException extends Error("Timed out waiting for variable to be set") } diff --git a/core/jvm-native/src/main/scala/zio/internal/ZScheduler.scala b/core/jvm-native/src/main/scala/zio/internal/ZScheduler.scala index 725ec50e73aa..7d937faf9d16 100644 --- a/core/jvm-native/src/main/scala/zio/internal/ZScheduler.scala +++ b/core/jvm-native/src/main/scala/zio/internal/ZScheduler.scala @@ -29,7 +29,7 @@ import scala.collection.mutable * applications. Inspired by "Making the Tokio Scheduler 10X Faster" by Carl * Lerche. [[https://tokio.rs/blog/2019-10-scheduler]] */ -private final class ZScheduler(autoBlocking: Boolean) extends Executor { +private final class ZScheduler(autoBlocking: Boolean) extends Executor { parent => import Trace.{empty => emptyTrace} import ZScheduler.{poolSize, workerOrNull} @@ -160,18 +160,29 @@ private final class ZScheduler(autoBlocking: Boolean) extends Executor { if (isBlocking(worker, runnable)) { submitBlocking(runnable) } else { - var notify = false + var notify = true if ((worker eq null) || worker.blocking) { globalQueue.offer(runnable) - notify = true - } else if ((worker.nextRunnable eq null) && worker.localQueue.isEmpty()) { - worker.nextRunnable = runnable - } else if (worker.localQueue.offer(runnable)) { - notify = true - } else { + } + // Attempt resumption in the current Thread + else if ((worker.nextRunnable eq null) && worker.localQueue.isEmpty()) { + // NOTE: Ideally, we want to do a full work-steal here, but that's too expensive on each yield so we only check the global queue + val fromGlobal = globalQueue.poll() + // Happy path, global queue is empty, so we can proceed to run the current runnable + if (fromGlobal eq null) { + worker.nextRunnable = runnable + notify = false + } else { + // Less common path, global queue is not empty, so we have to prioritize the runnable from it + worker.nextRunnable = fromGlobal + worker.localQueue.offer(runnable) + } + } + // We have to yield, add the runnable to the local / global queue so that it can be scheduled accordingly + else if (!worker.localQueue.offer(runnable)) { handleFullWorkerQueue(worker, runnable) - notify = true } + if (notify) { val currentState = state.get maybeUnparkWorker(currentState) @@ -266,14 +277,23 @@ private final class ZScheduler(autoBlocking: Boolean) extends Executor { private[this] def makeWorker(): ZScheduler.Worker = new ZScheduler.Worker { self => - override val submittedLocations = makeLocations() + override val submittedLocations: ZScheduler.Locations = makeLocations() + + final override def run(): Unit = { + // Store parent mutable object references in stack memory to avoid fetching it from the heap every time + val globalQueue = parent.globalQueue + val workers = parent.workers + val state = parent.state + val cache = parent.cache + val idle = parent.idle + val poolSize = ZScheduler.poolSize - override def run(): Unit = { var currentBlocking = false var currentOpCount = 0L val random = ThreadLocalRandom.current var runnable = null.asInstanceOf[Runnable] var searching = false + while (!isInterrupted) { currentBlocking = blocking val currentNextRunnable = nextRunnable @@ -321,7 +341,7 @@ private final class ZScheduler(autoBlocking: Boolean) extends Executor { currentBlocking = blocking if (currentBlocking) { val runnables = localQueue.pollUpTo(256) - if (runnables.nonEmpty) { + if (!runnables.isEmpty) { globalQueue.offerAll(runnables, random) } } @@ -388,7 +408,7 @@ private final class ZScheduler(autoBlocking: Boolean) extends Executor { // NOTE: Synchronized block in case the supervisor attempts to mark the worker as blocking at the same time // as an external call - def markAsBlocking(): Unit = synchronized { + final def markAsBlocking(): Unit = synchronized { if (blocking) () else { blocking = true diff --git a/core/shared/src/main/scala/zio/Cause.scala b/core/shared/src/main/scala/zio/Cause.scala index ee64370179e2..a6c55fbed157 100644 --- a/core/shared/src/main/scala/zio/Cause.scala +++ b/core/shared/src/main/scala/zio/Cause.scala @@ -21,6 +21,7 @@ import zio.stacktracer.TracingImplicits.disableAutoTrace import java.io.PrintWriter import scala.annotation.tailrec +import scala.collection.mutable import scala.runtime.AbstractFunction2 sealed abstract class Cause[+E] extends Product with Serializable { self => @@ -926,6 +927,60 @@ object Cause extends Serializable { (causeOption, stackless) => causeOption.map(Stackless(_, stackless)) ) + /** + * A Cause that contains one or more sub-causes + */ + private[Cause] sealed trait CompositeCause[+E] { self: Cause[E] => + + /** + * Stack-safe toString for Cause + */ + final private def causeToString: String = { + // result modifier function (Function0[Unit]) or cause (Cause[E]) to visit + val visitStack = new mutable.Stack[Any]() + // calculated string results + val results = new mutable.Stack[String]() + + def twoArgStr(name: String) = { + val right = results.pop() + val left = results.pop() + results.push(s"$name($left,$right)") + } + + @tailrec + def visitRecursive(): String = { + def visitCause(current: Cause[E]) = + current match { + case Both(left, right) => + visitStack.push(() => twoArgStr("Both"), right, left) + case Then(left, right) => + visitStack.push(() => twoArgStr("Then"), right, left) + case Stackless(cause, stackless) => + visitStack.push(() => results.push(s"Stackless(${results.pop()},$stackless)"), cause) + case nonCompositeCause => + results.push(nonCompositeCause.toString) + } + + if (visitStack.isEmpty) { + results.pop() + } else { + visitStack.pop() match { + case fn: Function0[Unit] => + fn() + case cause: Cause[E] => + visitCause(cause) + } + visitRecursive() + } + } + + visitStack.push(self) + visitRecursive() + } + + final override def toString = causeToString + } + case object Empty extends Cause[Nothing] { self => override def map[E1](f: Nothing => E1): Cause[E1] = self override protected def mapAll( @@ -1004,11 +1059,11 @@ object Cause extends Serializable { } } - final case class Stackless[+E](cause: Cause[E], stackless: Boolean) extends Cause[E] + final case class Stackless[+E](cause: Cause[E], stackless: Boolean) extends Cause[E] with CompositeCause[E] - final case class Then[+E](left: Cause[E], right: Cause[E]) extends Cause[E] + final case class Then[+E](left: Cause[E], right: Cause[E]) extends Cause[E] with CompositeCause[E] - final case class Both[+E](left: Cause[E], right: Cause[E]) extends Cause[E] + final case class Both[+E](left: Cause[E], right: Cause[E]) extends Cause[E] with CompositeCause[E] private def equals(left: Cause[Any], right: Cause[Any]): Boolean = { diff --git a/core/shared/src/main/scala/zio/Runtime.scala b/core/shared/src/main/scala/zio/Runtime.scala index fbf7d0fdfdd3..b3a4ed937840 100644 --- a/core/shared/src/main/scala/zio/Runtime.scala +++ b/core/shared/src/main/scala/zio/Runtime.scala @@ -271,6 +271,7 @@ object Runtime extends RuntimePlatformSpecific { def enableRuntimeMetrics(implicit trace: Trace): ZLayer[Any, Nothing, Unit] = enableFlags(RuntimeFlag.RuntimeMetrics) + @deprecated("Unused + unimplemented: using this flag will have no effect", "2.1.19") def enableWorkStealing(implicit trace: Trace): ZLayer[Any, Nothing, Unit] = enableFlags(RuntimeFlag.WorkStealing) diff --git a/core/shared/src/main/scala/zio/RuntimeFlag.scala b/core/shared/src/main/scala/zio/RuntimeFlag.scala index 0115b6dd4044..f1375c05cb6d 100644 --- a/core/shared/src/main/scala/zio/RuntimeFlag.scala +++ b/core/shared/src/main/scala/zio/RuntimeFlag.scala @@ -39,15 +39,17 @@ object RuntimeFlag { FiberRoots, WindDown, CooperativeYielding, - WorkStealing, EagerShiftBack ) /** * The interruption flag determines whether or not the ZIO runtime system will * interrupt a fiber. + * + * '''Note''': Modification of this flag is not recommended. It may cause + * unexpected behavior. */ - case object Interruption extends RuntimeFlag { + private[zio] case object Interruption extends RuntimeFlag { final val index = 0 final val mask = 1 << index final val notMask = ~mask @@ -118,8 +120,11 @@ object RuntimeFlag { * effects in wind-down mode. In wind-down mode, even if interruption is * enabled and a fiber has been interrupted, the fiber will continue its * execution uninterrupted. + * + * '''Note''': Modification of this flag is not recommended. It may cause + * unexpected behavior. */ - case object WindDown extends RuntimeFlag { + private[zio] case object WindDown extends RuntimeFlag { final val index = 6 final val mask = 1 << index final val notMask = ~mask @@ -142,7 +147,11 @@ object RuntimeFlag { /** * The work stealing flag determines whether threads running fibers about to * asynchronously suspend will first attempt to steal work before suspending. + * + * '''Note''': this flag is not implemented. [[zio.internal.ZScheduler]] will + * always steal work. */ + @deprecated("Unused + unimplemented: using this flag will have no effect", "2.1.19") case object WorkStealing extends RuntimeFlag { final val index = 8 final val mask = 1 << index diff --git a/core/shared/src/main/scala/zio/RuntimeFlags.scala b/core/shared/src/main/scala/zio/RuntimeFlags.scala index 763d3a878026..c6a4b8479b0a 100644 --- a/core/shared/src/main/scala/zio/RuntimeFlags.scala +++ b/core/shared/src/main/scala/zio/RuntimeFlags.scala @@ -100,6 +100,7 @@ object RuntimeFlags { def windDown(flags: RuntimeFlags): Boolean = isEnabled(flags, RuntimeFlag.WindDown.mask) + @deprecated("Unused + unimplemented: using this flag will have no effect", "2.1.19") def workStealing(flags: RuntimeFlags): Boolean = isEnabled(flags, RuntimeFlag.WorkStealing.mask) diff --git a/core/shared/src/main/scala/zio/ZIOApp.scala b/core/shared/src/main/scala/zio/ZIOApp.scala index ee403221bc23..a72d9dd0b4c9 100644 --- a/core/shared/src/main/scala/zio/ZIOApp.scala +++ b/core/shared/src/main/scala/zio/ZIOApp.scala @@ -48,6 +48,14 @@ trait ZIOApp extends ZIOAppPlatformSpecific with ZIOAppVersionSpecific { */ def run: ZIO[Environment with ZIOAppArgs with Scope, Any, Any] + /** + * The time that the application will wait for finalizers to run before + * exiting. + * + * '''NOTE''': This is currently used only for JVM & ScalaNative applications + */ + def gracefulShutdownTimeout: Duration = Duration.Infinity + /** * Composes this [[ZIOApp]] with another [[ZIOApp]], to yield an application * that executes the logic of both applications. @@ -71,12 +79,13 @@ trait ZIOApp extends ZIOAppPlatformSpecific with ZIOAppVersionSpecific { * A helper function to exit the application with the specified exit code. */ final def exit(code: ExitCode)(implicit trace: Trace): UIO[Unit] = - ZIO.succeed { - if (shuttingDown.compareAndSet(false, true)) { - try Platform.exit(code.code)(Unsafe.unsafe) - catch { - case _: SecurityException => - } + ZIO.succeed(exitUnsafe(code)(Unsafe)) + + protected[zio] def exitUnsafe(code: ExitCode)(implicit unsafe: Unsafe): Unit = + if (shuttingDown.compareAndSet(false, true)) { + try Platform.exit(code.code) + catch { + case _: SecurityException => } } diff --git a/core/shared/src/main/scala/zio/internal/IsFatal.scala b/core/shared/src/main/scala/zio/internal/IsFatal.scala index 9771467daaa2..587fb100e519 100644 --- a/core/shared/src/main/scala/zio/internal/IsFatal.scala +++ b/core/shared/src/main/scala/zio/internal/IsFatal.scala @@ -20,20 +20,21 @@ sealed trait IsFatal extends (Throwable => Boolean) { self => import IsFatal._ def apply(t: Throwable): Boolean = - if (t.isInstanceOf[StackOverflowError]) true + if (t.isInstanceOf[VirtualMachineError]) true else self match { + case _: Empty.type => false case Both(left, right) => left(t) || right(t) - case Empty => false case Single(tag) => tag.isAssignableFrom(t.getClass) } def |(that: IsFatal): IsFatal = - (self, that) match { - case (self, Empty) => self - case (Empty, that) => that - case (self, that) => Both(self, that) - } + if (self eq Empty) that + else + that match { + case _: Empty.type => self + case _ => Both(self, that) + } } object IsFatal { diff --git a/docs/ecosystem/index.md b/docs/ecosystem/index.md deleted file mode 100644 index 6e32800a0a0e..000000000000 --- a/docs/ecosystem/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: index -title: "ZIO Ecosystem" ---- - -We have two categories of libraries: official and community. -[Official libraries](officials/index.md) are maintained by the ZIO team under the [ZIO Organization](https://github.com/zio). -[Community libraries](community/index.md) are maintained by the ZIO community members. - -## ZIO Compatible Libraries - -To learn about ZIO compatible libraries, see [this section](compatible.md). - -## ZIO Tools - -To learn about tools that are useful for ZIO development, see [this section](tools.md). - -## Project Templates - -In this section, we introduce a list of [project templates](templates.md) that can be used to quickly start a new project. diff --git a/docs/ecosystem/index.mdx b/docs/ecosystem/index.mdx new file mode 100644 index 000000000000..4849f4feaa7f --- /dev/null +++ b/docs/ecosystem/index.mdx @@ -0,0 +1,18 @@ +--- +id: index +title: "ZIO Ecosystem" +--- +import Ecosystem from '@site/src/components/sections/Ecosystem'; + +ZIO has a rich ecosystem of libraries and tools that enhance its capabilities and provide additional functionality. This ecosystem includes libraries for various purposes, such as web development, data processing, testing, and more. + +But it doesn't end there! If you need a comprehensive list of libraries and tools, you can find them in one of the following sections: + +- [Official libraries](officials/index.md), maintained by the ZIO team under the [ZIO Organization](https://github.com/zio). +- [Community libraries](community/index.md), maintained by members of the ZIO community. + +Below are some of the highlights from the official libraries: + + + + diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 3843d0182f21..785b5b3cd9d6 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,7 +6,7 @@ object Dependencies { val JunitPlatformEngineVersion = "1.12.2" val IzumiReflectVersion = "3.0.1" val MagnoliaScala2Version = "1.1.10" - val MagnoliaScala3Version = "1.3.16" + val MagnoliaScala3Version = "1.3.18" val RefinedVersion = "0.11.3" val ScalaCheckVersion = "1.18.1" val ScalaJavaTimeVersion = "2.6.0" @@ -24,7 +24,7 @@ object Dependencies { val ShardcakeVersion = "2.4.2" val ZioMetricsConnectorsVersion = "2.3.1" - val ZioHttpVersion = "3.2.0" + val ZioHttpVersion = "3.3.2" val IzumiVersion = "1.2.15" val ZioConfigVersion = "4.0.2" val ZioFtpVersion = "0.4.3" diff --git a/project/build.properties b/project/build.properties index cc68b53f1a30..6520f6981d5a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.11 +sbt.version=1.11.0 diff --git a/streams-tests/shared/src/test/scala/zio/stream/ZStreamSpec.scala b/streams-tests/shared/src/test/scala/zio/stream/ZStreamSpec.scala index cd05965a42d5..3a774e7cb248 100644 --- a/streams-tests/shared/src/test/scala/zio/stream/ZStreamSpec.scala +++ b/streams-tests/shared/src/test/scala/zio/stream/ZStreamSpec.scala @@ -1466,11 +1466,9 @@ object ZStreamSpec extends ZIOBaseSpec { _ <- ZStream.acquireReleaseWith(push("open1"))(_ => push("close1")) _ <- ZStream .fromChunks(Chunk(()), Chunk(())) - .tap(_ => ZIO.debug("use2") *> push("use2")) + .tap(_ => push("use2")) .ensuring(push("close2")) - _ <- ZStream.acquireReleaseWith(ZIO.debug("open3") *> push("open3"))(_ => - ZIO.debug("close3") *> push("close3") - ) + _ <- ZStream.acquireReleaseWith(push("open3"))(_ => push("close3")) _ <- ZStream .fromChunks(Chunk(()), Chunk(())) .tap(_ => push("use4")) @@ -1507,10 +1505,8 @@ object ZStreamSpec extends ZIOBaseSpec { stream = for { _ <- ZStream .fromChunks(Chunk(1), Chunk(2)) - .tap(n => ZIO.debug(s">>> use2 $n") *> push("use2")) - _ <- ZStream.acquireReleaseWith(ZIO.debug("open3") *> push("open3"))(_ => - ZIO.debug("close3") *> push("close3") - ) + .tap(_ => push("use2")) + _ <- ZStream.acquireReleaseWith(push("open3"))(_ => push("close3")) } yield () _ <- stream.runDrain result <- effects.get @@ -4636,7 +4632,7 @@ object ZStreamSpec extends ZIOBaseSpec { val pipeline = ZPipeline.fromFunction[Scope, Throwable, Byte, Unit] { s => ZStream.fromZIO { for { - is <- s.toInputStream.debug("toInputStream") + is <- s.toInputStream _ <- ZIO.attemptBlocking(is.read()) } yield () } diff --git a/test-sbt/jvm/src/test/scala/zio/test/sbt/ZTestEventSpec.scala b/test-sbt/jvm/src/test/scala/zio/test/sbt/ZTestEventSpec.scala index ed255776ae5e..fd7d252b22d8 100644 --- a/test-sbt/jvm/src/test/scala/zio/test/sbt/ZTestEventSpec.scala +++ b/test-sbt/jvm/src/test/scala/zio/test/sbt/ZTestEventSpec.scala @@ -75,28 +75,24 @@ object ZTestEventSpec extends ZIOSpecDefault { // Required because // - `Selector` equality isn't working // - Ansi colors make comparisons horrible to work with - def assertEqualEvents(result: Event, expected: Event): TestResult = { - println(stripAnsi(result.throwable())) - println("\n==================\n") - println(stripAnsi(result.throwable())) + def assertEqualEvents(result: Event, expected: Event): TestResult = assertTrue( result.fullyQualifiedName() == expected.fullyQualifiedName() ) && - assertTrue( - result.selector().toString == expected.selector().toString - ) && - assertTrue( - result.status() == expected.status() - ) && - assertTrue( - stripAnsi(result.throwable()) - == stripAnsi(expected.throwable()) - ) && - assertTrue( - result.duration() == expected.duration() - ) && - assertCompletes - } + assertTrue( + result.selector().toString == expected.selector().toString + ) && + assertTrue( + result.status() == expected.status() + ) && + assertTrue( + stripAnsi(result.throwable()) + == stripAnsi(expected.throwable()) + ) && + assertTrue( + result.duration() == expected.duration() + ) && + assertCompletes private def stripAnsi(input: Any) = input.toString diff --git a/test-tests/jvm-native/src/test/scala/zio/test/environment/TestClockSpecJVM.scala b/test-tests/jvm-native/src/test/scala/zio/test/environment/TestClockSpecJVM.scala index df86fe01bf0c..1a457489fccf 100644 --- a/test-tests/jvm-native/src/test/scala/zio/test/environment/TestClockSpecJVM.scala +++ b/test-tests/jvm-native/src/test/scala/zio/test/environment/TestClockSpecJVM.scala @@ -123,7 +123,6 @@ object TestClockSpecJVM extends ZIOBaseSpec { _ <- ZIO.succeed(future.cancel(false)) _ <- TestClock.adjust(11.seconds) values <- ref.get - _ <- ZIO.logInfo(s"Values after interruption: $values") } yield assert(values.reverse)(equalTo(List(5L))) } ), @@ -132,5 +131,5 @@ object TestClockSpecJVM extends ZIOBaseSpec { (TestClock.adjust(1.second) &> TestClock.adjust(1.second)).as(assertCompletes) } @@ timeout(10.seconds) ) - ) @@ nonFlaky(20) + ) @@ nonFlaky(10) } diff --git a/test-tests/shared/src/test/scala/zio/test/TestOutputSpec.scala b/test-tests/shared/src/test/scala/zio/test/TestOutputSpec.scala index f01e7c8a43f6..48f9f08f9aff 100644 --- a/test-tests/shared/src/test/scala/zio/test/TestOutputSpec.scala +++ b/test-tests/shared/src/test/scala/zio/test/TestOutputSpec.scala @@ -75,7 +75,7 @@ object TestOutputSpec extends ZIOBaseSpec { for { _ <- ZIO.foreach(events)(event => TestOutput.print(event)) outputEvents <- ZIO.serviceWithZIO[ExecutionEventHolder](_.getEvents) - _ <- ZIO.service[ExecutionEventPrinter].debug("Printer in test") + _ <- ZIO.service[ExecutionEventPrinter] } yield assertTrue( outputEvents == List( diff --git a/test-tests/shared/src/test/scala/zio/test/TestProvideSpec.scala b/test-tests/shared/src/test/scala/zio/test/TestProvideSpec.scala index 28d04367b6f6..60061fecced6 100644 --- a/test-tests/shared/src/test/scala/zio/test/TestProvideSpec.scala +++ b/test-tests/shared/src/test/scala/zio/test/TestProvideSpec.scala @@ -163,7 +163,7 @@ object TestProvideSpec extends ZIOBaseSpec { val intService: ULayer[IntService] = ZLayer(Ref.make(0).map(IntService(_))) val stringService: ULayer[StringService] = - ZLayer(Ref.make("Hello").map(StringService(_)).debug("MAKING")) + ZLayer(Ref.make("Hello").map(StringService(_))) def customTest(int: Int) = test(s"test $int") { @@ -201,7 +201,7 @@ object TestProvideSpec extends ZIOBaseSpec { } yield IntService(ref)) val stringService: ULayer[StringService] = - ZLayer(Ref.make("Hello").map(StringService(_)).debug("MAKING")) + ZLayer(Ref.make("Hello").map(StringService(_))) def customTest(int: Int) = test(s"test $int") { diff --git a/website/package.json b/website/package.json index 456f3c92eaab..48a006af2999 100644 --- a/website/package.json +++ b/website/package.json @@ -22,18 +22,18 @@ "@docusaurus/theme-mermaid": "^3.5.2", "@docusaurus/theme-search-algolia": "^3.5.2", "@mdx-js/react": "^3.0.1", - "@tailwindcss/postcss": "4.1.7", + "@tailwindcss/postcss": "4.1.8", "@zio.dev/izumi-reflect": "2024.11.11-da5b828d4d6e", "@zio.dev/zio-amqp": "1.0.0-alpha.3", "@zio.dev/zio-aws": "2025.2.9-b3c92258585e", "@zio.dev/zio-bson": "1.0.8", "@zio.dev/zio-cache": "0.2.4", - "@zio.dev/zio-cli": "0.7.0", + "@zio.dev/zio-cli": "0.7.2", "@zio.dev/zio-config": "4.0.4", "@zio.dev/zio-direct": "1.0.0-RC7", "@zio.dev/zio-dynamodb": "1.0.0-RC19", "@zio.dev/zio-ftp": "0.4.3", - "@zio.dev/zio-http": "3.2.0", + "@zio.dev/zio-http": "3.3.2", "@zio.dev/zio-json": "0.7.43", "@zio.dev/zio-kafka": "2.12.0", "@zio.dev/zio-lambda": "1.0.5", @@ -50,7 +50,7 @@ "@zio.dev/zio-rocksdb": "0.4.2", "@zio.dev/zio-s3": "2022.11.21-367e7009c0f5", "@zio.dev/zio-sbt": "0.4.0-alpha.31", - "@zio.dev/zio-schema": "1.7.0", + "@zio.dev/zio-schema": "1.7.2", "@zio.dev/zio-sqs": "2022.11.23-5a814304824c", "@zio.dev/zio-streams-compress": "1.1.0", "@zio.dev/zio-telemetry": "3.1.4", @@ -60,7 +60,7 @@ "clsx": "2.1.1", "highlight.js": "11.11.1", "node-fetch": "^3.3.2", - "postcss": "8.5.3", + "postcss": "8.5.4", "prism-react-renderer": "1.3.5", "prism-themes": "1.9.0", "prismjs": "^1.29.0", @@ -69,7 +69,7 @@ "react-icons": "5.5.0", "react-markdown": "10.1.0", "remark-kroki-plugin": "0.1.1", - "tailwindcss": "4.1.7", + "tailwindcss": "4.1.8", "tslib": "^2.4.0" }, "resolutions": { @@ -89,7 +89,7 @@ }, "devDependencies": { "@tsconfig/docusaurus": "2.0.3", - "@types/react": "19.1.4", + "@types/react": "19.1.6", "@types/react-helmet": "6.1.11", "@types/react-router-dom": "5.3.3", "prettier": "3.5.3", diff --git a/website/src/components/sections/Ecosystem/data.js b/website/src/components/sections/Ecosystem/data.js new file mode 100644 index 000000000000..c82f68ea9ae8 --- /dev/null +++ b/website/src/components/sections/Ecosystem/data.js @@ -0,0 +1,91 @@ +export const ecosystemProjects = [ + { + name: 'ZIO HTTP', + description: 'Type-safe, purely functional HTTP library for building high-performance web applications and APIs', + features: [ + 'High-performance server based on Netty', + 'Type-safe and type-driven endpoints', + 'Support for both imperative and declarative endpoints', + 'Designed for cloud-native environments', + 'Support for both server and client applications', + 'WebSocket support for real-time applications', + 'Middleware system for cross-cutting concerns', + 'Integration with ZIO Schema for automatic codecs', + 'Built-in support for streaming responses', + ], + link: 'https://ziohttp.com', + icon: '🌐' + }, + { + name: 'ZIO Streams', + description: 'Powerful, composable, and type-safe streaming library for working with large or infinite data', + features: [ + 'Process infinite streams with finite memory resource', + 'Automatic backpressure handling', + 'Rich set of stream combinators', + 'Non-blocking and asynchronous processing', + ], + link: '/reference/stream', + icon: 'πŸ”—' + }, + { + name: 'ZIO Test', + description: 'Feature-rich testing framework with powerful assertions and property-based testing', + features: [ + 'Property-based testing out of the box', + 'Deterministic testing of concurrent code', + 'Test aspects for reusable configurations', + 'Integration with JUnit and other frameworks' + ], + link: '/reference/test', + icon: 'βœ…' + }, + { + name: 'ZIO STM', + description: 'Software Transactional Memory for safe, composable concurrent programming', + features: [ + 'Atomic, isolated transactions', + 'Composable concurrent operations', + 'No deadlocks or race conditions', + 'Automatic retry of interrupted transactions' + ], + link: '/reference/stm', + icon: 'πŸ”’' + }, + { + name: 'ZIO Schema', + description: 'Declarative schema definitions for data structures with automatic derivation', + features: [ + 'Reification of data structures', + 'Manual and automatic schema derivation', + 'Built-in codecs for JSON, Protobuf, Avro, and Thrift', + 'Schema transformations and migrations', + ], + link: '/reference/schema', + icon: '🧬' + }, + { + name: 'ZIO Config', + description: 'Type-safe, composable configuration management with automatic documentation', + features: [ + 'Type-safe configuration descriptions', + 'Multiple source support (files, env vars)', + 'Automatic documentation generation', + 'Validation with detailed error reporting' + ], + link: '/ecosystem/officials/zio-config', + icon: 'βš™οΈ' + }, + { + name: 'ZIO Logging', + description: 'High-performance, structured logging with contextual information', + features: [ + 'Structured logging for ZIO applications', + 'Multiple backend support (SLF4J, JPL, Console)', + 'Context-aware logging with MDC support', + 'Log correlation across async boundaries' + ], + link: '/ecosystem/officials/zio-logging', + icon: 'πŸ“' + } +]; diff --git a/website/src/components/sections/Ecosystem/index.jsx b/website/src/components/sections/Ecosystem/index.jsx new file mode 100644 index 000000000000..20c9f5fe399d --- /dev/null +++ b/website/src/components/sections/Ecosystem/index.jsx @@ -0,0 +1,77 @@ +import React from 'react'; +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import styles from './styles.module.css'; +import SectionWrapper from '@site/src/components/ui/SectionWrapper'; + +import { ecosystemProjects } from './data'; + +export default function Ecosystem({ title, subtitle, children}) { + // Separate ZIO HTTP from other projects (featured project) + const featuredProject = ecosystemProjects.find(p => p.name === 'ZIO HTTP'); + const otherProjects = ecosystemProjects.filter(p => p.name !== 'ZIO HTTP'); + + return ( + +
+ {/* Featured project in its own row */} + {featuredProject && ( +
+
+
+
+
+ {featuredProject.icon} +
+

{featuredProject.name}

+
+

{featuredProject.description}

+
    + {featuredProject.features.map((feature, fidx) => ( +
  • {feature}
  • + ))} +
+
+ + Learn More + +
+
+
+
+ )} + + {/* Other projects in a grid */} +
+ {otherProjects.map((project, idx) => ( +
+
+
+
+ {project.icon} +
+

{project.name}

+
+

{project.description}

+
    + {project.features.map((feature, fidx) => ( +
  • {feature}
  • + ))} +
+
+ + Learn More + +
+
+
+ ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/website/src/components/sections/Ecosystem/styles.module.css b/website/src/components/sections/Ecosystem/styles.module.css new file mode 100644 index 000000000000..d2625ebf4b2c --- /dev/null +++ b/website/src/components/sections/Ecosystem/styles.module.css @@ -0,0 +1,184 @@ +.ecosystem { + display: flex; + align-items: center; + width: 100%; + background-color: var(--ifm-color-emphasis-100); +} + +.wideContainer { + margin: 0 auto; + padding: 0 var(--ifm-spacing-horizontal); + width: 100%; + max-width: 1400px; +} + +.ecosystemHeader { + margin-bottom: 2rem; +} + +.ecosystemHeader h2 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 1rem; +} + +.ecosystemCards { + display: flex; + flex-wrap: wrap; + margin-bottom: 2rem; +} + +.ecosystemCardCol { + margin-bottom: 2rem; +} + + +.ecosystemCard { + height: 100%; + padding: 2rem; + border-radius: 8px; + background-color: var(--ifm-card-background-color); + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + display: flex; + flex-direction: column; + border: 1px solid var(--ifm-color-emphasis-200); +} + +.ecosystemCard:hover { + transform: translateY(-5px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15); + border-color: var(--ifm-color-primary-lightest); +} + +.ecosystemCardHeader { + display: flex; + align-items: center; + margin-bottom: 1rem; +} + +.ecosystemCardIcon { + font-size: 2rem; + margin-right: 1rem; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + flex-shrink: 0; +} + +.ecosystemCardHeader h3 { + margin: 0; + font-size: 1.5rem; + color: var(--ifm-color-primary); +} + +.mainProjectCol .ecosystemCardHeader h3 { + font-size: 1.8rem; +} + +.ecosystemCardDescription { + margin-bottom: 1.5rem; + color: var(--ifm-color-emphasis-700); + line-height: 1.6; + flex-grow: 0; +} + +.mainProjectCol .ecosystemCardDescription { + font-size: 1.1rem; +} + +.ecosystemCardFeatures { + margin: 0 0 1.5rem 0; + padding-left: 1.5rem; + flex-grow: 1; + list-style-type: disc; +} + +.ecosystemCardFeatures li { + margin-bottom: 0.5rem; + color: var(--ifm-color-emphasis-800); + line-height: 1.5; +} + +/* Two-column layout for featured project features */ +.mainProjectCol .ecosystemCardFeatures { + column-count: 2; + column-gap: 2rem; +} + +.ecosystemCardFooter { + margin-top: auto; + text-align: right; +} + +.ecosystemCardButton { + font-weight: 500; + transition: all 0.2s ease; +} + +.ecosystemCardButton:hover { + background-color: var(--ifm-color-primary); + color: white; + border-color: var(--ifm-color-primary); +} + +/* Dark mode adjustments */ +[data-theme='dark'] .ecosystem { + background-color: var(--ifm-background-surface-color); +} + +[data-theme='dark'] .ecosystemCard { + background-color: var(--ifm-background-color); + border-color: var(--ifm-color-emphasis-300); +} + +[data-theme='dark'] .ecosystemCard:hover { + border-color: var(--ifm-color-primary); +} + +[data-theme='dark'] .ecosystemCardDescription, +[data-theme='dark'] .ecosystemSubtitle { + color: var(--ifm-color-emphasis-600); +} + +[data-theme='dark'] .ecosystemCardFeatures li { + color: var(--ifm-color-emphasis-500); +} + +/* Responsive styles */ +@media (max-width: 996px) { + .ecosystemCardCol { + flex: 0 0 50%; + max-width: 50%; + } + + .mainProjectCol .ecosystemCardFeatures { + column-count: 1; + } +} + +@media (max-width: 768px) { + .ecosystemCardCol, + .mainProjectCol { + flex: 0 0 100%; + max-width: 100%; + } + + .ecosystem { + padding: 3rem 0; + } + + .ecosystemCard { + padding: 1.5rem; + } + + .ecosystemCardHeader h3 { + font-size: 1.3rem; + } + + .mainProjectCol .ecosystemCardHeader h3 { + font-size: 1.5rem; + } +} \ No newline at end of file diff --git a/website/src/components/sections/Hero/index.jsx b/website/src/components/sections/Hero/index.jsx index 846654185715..83af01b108fd 100644 --- a/website/src/components/sections/Hero/index.jsx +++ b/website/src/components/sections/Hero/index.jsx @@ -10,7 +10,7 @@ export default function Hero() { const { siteConfig = {} } = context; return ( -
+
+
+
+
+
+

Learn ZIO with Zionomicon

+

+ The comprehensive guide to building scalable applications with ZIO +

+

+ Zionomicon stands as the comprehensive guide to mastering ZIOβ€”the game-changing + library that's revolutionizing how developers build robust Scala applications. + It takes you from the fundamentals to advanced topics, teaching you how to build + concurrent, resilient, and testable applications. +

+

+ In Zionomicon, you'll master: +

+
    +
  • Modeling complex business logic using ZIO's effect system
  • +
  • Error handling and resource management with ZIO
  • +
  • Concurrent and asynchronous programming patterns
  • +
  • Building predictable and testable applications
  • +
  • Structured dependency injection using ZIO's layer system
  • +
  • And much more to explore!
  • +
+
+ + Get the Book for Free + +
+
+
+
+
+ + Zionomicon Book Cover + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/website/src/components/sections/Zionomicon/styles.module.css b/website/src/components/sections/Zionomicon/styles.module.css new file mode 100644 index 000000000000..5d2aef569fc8 --- /dev/null +++ b/website/src/components/sections/Zionomicon/styles.module.css @@ -0,0 +1,78 @@ +.zionomicon { + display: flex; + align-items: center; + padding: 4rem 0; + width: 100%; + background-color: var(--ifm-color-emphasis-0); +} + +.wideContainer { + margin: 0 auto; + padding: 0 var(--ifm-spacing-horizontal); + width: 100%; + max-width: 1400px; +} + +.ziconContent { + padding: 2rem; +} + +.ziconSubtitle { + font-size: 1.2rem; + color: var(--ifm-color-primary); + margin-bottom: 1.5rem; +} + +.ziconContent ul { + margin-bottom: 2rem; +} + +.ziconContent li { + margin-bottom: 0.5rem; +} + +.buttonContainer { + margin-top: 2rem; + text-align: center; +} + +.ziconImageContainer { + display: flex; + justify-content: center; + align-items: center; + padding: 2rem; +} + +.ziconImage { + max-width: 100%; + height: auto; + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); + border-radius: 8px; + transition: transform 0.3s ease; + cursor: pointer; +} + +.ziconImage:hover { + transform: scale(1.05); +} + +/* Responsive styles */ +@media (max-width: 996px) { + .zionomicon .row { + flex-direction: column-reverse; + } + + .ziconContent { + padding: 2rem 1rem; + text-align: center; + } + + .ziconContent ul { + display: inline-block; + text-align: left; + } + + .ziconImageContainer { + padding: 2rem 1rem 0.5rem 1rem; + } +} \ No newline at end of file diff --git a/website/src/components/ui/SectionWrapper/index.jsx b/website/src/components/ui/SectionWrapper/index.jsx index 20cc78eb67d6..6299e28e7440 100644 --- a/website/src/components/ui/SectionWrapper/index.jsx +++ b/website/src/components/ui/SectionWrapper/index.jsx @@ -1,14 +1,19 @@ import React from 'react'; +import styles from './styles.module.css'; -export default function SectionWrapper({ title, children }) { +export default function SectionWrapper({ title, subtitle, children }) { return ( -
+
{title ? (

{title}

) : null} - + {subtitle ? ( +
+

{subtitle}

+
+ ): null} {children}
); diff --git a/website/src/components/ui/SectionWrapper/styles.module.css b/website/src/components/ui/SectionWrapper/styles.module.css new file mode 100644 index 000000000000..1c8e4b2de108 --- /dev/null +++ b/website/src/components/ui/SectionWrapper/styles.module.css @@ -0,0 +1,6 @@ +.subtitle { + font-size: 1.2rem; + color: var(--ifm-color-emphasis-700); + max-width: 800px; + margin: 0 auto; +} diff --git a/website/src/pages/index.jsx b/website/src/pages/index.jsx index d5b00a22e387..90e7c1775c8c 100644 --- a/website/src/pages/index.jsx +++ b/website/src/pages/index.jsx @@ -4,7 +4,9 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Hero from '@site/src/components/sections/Hero'; import Features from '@site/src/components/sections/Features'; +import Ecosystem from '@site/src/components/sections/Ecosystem'; import Sponsors from '@site/src/components/sections/Sponsors'; +import Zionomicon from '@site/src/components/sections/Zionomicon'; // Construct the home page from all components export default function WelcomePage() { @@ -20,6 +22,8 @@ export default function WelcomePage() {
+ +
diff --git a/website/static/img/zionomicon.png b/website/static/img/zionomicon.png new file mode 100644 index 000000000000..d0b57729938c Binary files /dev/null and b/website/static/img/zionomicon.png differ