From 1247f898f070f72ea655a8b517d1dea91a93aa7c Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Wed, 4 Jun 2025 11:54:59 +0530 Subject: [PATCH 1/3] docs: documentation for gracefulShutdownTimeout --- docs/reference/core/zioapp.md | 173 ++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/docs/reference/core/zioapp.md b/docs/reference/core/zioapp.md index 8b0be1cd313f..979dc23f6113 100644 --- a/docs/reference/core/zioapp.md +++ b/docs/reference/core/zioapp.md @@ -109,3 +109,176 @@ object Main extends ZIOApp.Proxy(MyApp1 <> MyApp2) ``` The `<>` operator combines the layers of the two applications and then runs the two applications in parallel. + +## Graceful Shutdown Timeout + +The `gracefulShutdownTimeout` feature in ZIO provides a configurable mechanism to control how long your application waits for resource cleanup when receiving termination signals like **SIGINT** (triggered by **Ctrl+C**). This timeout ensures your application can properly release resources while preventing indefinite hangs during shutdown. By default, it's configured to `Duration.Infinity`, allowing unlimited time for cleanup operations unless explicitly overridden. + +### When to Use Graceful Shutdown Timeout + +You should consider setting a timeout when: + +1. Your application manages resources that need cleanup (database connections, file handles, etc.) +2. You want to ensure your application doesn't hang indefinitely during shutdown +3. You need to balance between allowing cleanup to complete and ensuring timely shutdown +4. You're running in environments where resource cleanup is critical (e.g., cloud platforms) + +### Best Practices + +1. **Choose Appropriate Timeout Duration**: + - Consider the typical cleanup time of your resources + - Add buffer time for unexpected delays + - Account for network latency if cleanup involves remote resources + - Consider platform-specific shutdown behavior + +2. **Handle Timeout Gracefully**: + - Log warnings when timeout occurs + - Implement fallback cleanup mechanisms + - Consider using `Scope` for resource management + - Use `ZIO.acquireReleaseWith` for proper resource handling + +3. **Monitor and Adjust**: + - Log cleanup durations + - Adjust timeout based on observed behavior + - Consider different timeouts for different environments + +### Examples + +#### Example 1: Database Connection Cleanup + +```scala mdoc:compile-only +import zio._ +import zio.Console + +object DatabaseApp extends ZIOAppDefault { + // Wait at most 30 seconds for all finalizers to complete on SIGINT + override def gracefulShutdownTimeout: Duration = Duration.fromSeconds(30) + + val run: ZIO[ZIOAppArgs with Scope, Any, Any] = + // Using acquireReleaseWith to ensure proper resource cleanup + ZIO.acquireReleaseWith( + // Acquire: Establish database connection + acquire = ZIO.logInfo("Connecting to database...") *> + ZIO.sleep(1.second) *> // Simulating connection time + ZIO.succeed("DBConnection") + )( + // Release: Finalizer that runs during shutdown + release = conn => + ZIO.logInfo(s"Closing database connection (5s) ...") *> + ZIO.sleep(5.seconds) *> // Simulating cleanup time + ZIO.logInfo(s"Database connection $conn closed successfully") + ) { conn => + // Use: Main application logic + ZIO.logInfo(s"Running with database connection $conn, press Ctrl+C to interrupt") *> + ZIO.never + } +} +``` + +In this example, we simulate a database application that: +1. Takes 1 second to establish a connection +2. Has a finalizer that takes 5 seconds to properly close the connection +3. Has a 30-second timeout for cleanup +4. Logs each step of the process + +The cleanup will complete successfully within the timeout, ensuring proper database connection closure. + +#### Example 2: File System Operations with Timeout Breach + +```scala mdoc:compile-only +import zio._ +import zio.Console + +object FileSystemApp extends ZIOAppDefault { + // Wait at most 5 seconds for all finalizers to complete on SIGINT + override def gracefulShutdownTimeout: Duration = Duration.fromSeconds(5) + + val run: ZIO[ZIOAppArgs with Scope, Any, Any] = + // Using acquireReleaseWith to ensure proper resource cleanup + ZIO.acquireReleaseWith( + // Acquire: Open file + acquire = ZIO.logInfo("Opening large file...") *> + ZIO.sleep(1.second) *> // Simulating file open + ZIO.succeed("LargeFile") + )( + // Release: Finalizer that runs during shutdown + // This finalizer will be interrupted after 5 seconds + release = file => + ZIO.logInfo(s"Flushing and closing file (10s) ...") *> + ZIO.sleep(10.seconds) *> // Simulating file flush and close + ZIO.logInfo(s"File $file closed successfully") + ) { file => + // Use: Main application logic + ZIO.logInfo(s"Processing file $file, press Ctrl+C to interrupt") *> + ZIO.never + } +} +``` + +This example demonstrates a file system application that: +1. Opens a large file +2. Has a finalizer that needs 10 seconds to properly flush and close the file +3. Has only a 5-second timeout for cleanup +4. Will be forcefully terminated if cleanup takes too long + +When you press Ctrl+C, you'll see: +```bash +**** WARNING **** +Timed out waiting for ZIO application to shut down after 5 seconds. You can adjust your application's shutdown timeout by overriding the `shutdownTimeout` method +``` + +The finalizer will be interrupted after 5 seconds, potentially leaving the file in an inconsistent state. + +#### Example 3: Multiple Resource Cleanup + +```scala mdoc:compile-only +import zio._ +import zio.Console + +object MultiResourceApp extends ZIOAppDefault { + override def gracefulShutdownTimeout: Duration = Duration.fromSeconds(15) + + val run: ZIO[ZIOAppArgs with Scope, Any, Any] = { + val dbResource = ZIO.acquireReleaseWith( + acquire = ZIO.logInfo("Connecting to database...") *> + ZIO.sleep(1.second) *> + ZIO.succeed("DBConnection") + )( + release = conn => + ZIO.logInfo(s"Closing database connection (3s) ...") *> + ZIO.sleep(3.seconds) *> + ZIO.logInfo(s"Database connection $conn closed") + ) + + val fileResource = ZIO.acquireReleaseWith( + acquire = ZIO.logInfo("Opening file...") *> + ZIO.sleep(1.second) *> + ZIO.succeed("File") + )( + release = file => + ZIO.logInfo(s"Closing file (5s) ...") *> + ZIO.sleep(5.seconds) *> + ZIO.logInfo(s"File $file closed") + ) + + for { + db <- dbResource + file <- fileResource + _ <- ZIO.logInfo(s"Running with resources: $db, $file, press Ctrl+C to interrupt") *> + ZIO.never + } yield () + } +} +``` + +This example shows how to handle multiple resources that need cleanup: +1. Database connection (3s cleanup) +2. File handle (5s cleanup) +3. Total cleanup time: 8s +4. Timeout: 15s (sufficient for both resources) + +### Platform-Specific Behavior + +:::note +The graceful shutdown timeout behavior only applies on **JVM** and **Scala Native** platforms. Other platforms like **Scala.js** do not invoke the shutdown hook on external signals. +::: From 0ee0991a56bb326f67d549fefc49e41abec2da74 Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Wed, 4 Jun 2025 12:10:07 +0530 Subject: [PATCH 2/3] docs: label --- docs/reference/core/zioapp.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/reference/core/zioapp.md b/docs/reference/core/zioapp.md index 979dc23f6113..aa86c8fb5c65 100644 --- a/docs/reference/core/zioapp.md +++ b/docs/reference/core/zioapp.md @@ -236,9 +236,11 @@ import zio._ import zio.Console object MultiResourceApp extends ZIOAppDefault { + // Wait at most 15 seconds for all finalizers to complete on SIGINT override def gracefulShutdownTimeout: Duration = Duration.fromSeconds(15) val run: ZIO[ZIOAppArgs with Scope, Any, Any] = { + // Database resource val dbResource = ZIO.acquireReleaseWith( acquire = ZIO.logInfo("Connecting to database...") *> ZIO.sleep(1.second) *> @@ -247,35 +249,38 @@ object MultiResourceApp extends ZIOAppDefault { release = conn => ZIO.logInfo(s"Closing database connection (3s) ...") *> ZIO.sleep(3.seconds) *> - ZIO.logInfo(s"Database connection $conn closed") + ZIO.logInfo(s"Database connection $conn closed successfully") ) + // File resource val fileResource = ZIO.acquireReleaseWith( acquire = ZIO.logInfo("Opening file...") *> ZIO.sleep(1.second) *> - ZIO.succeed("File") + ZIO.succeed("FileHandle") )( release = file => ZIO.logInfo(s"Closing file (5s) ...") *> ZIO.sleep(5.seconds) *> - ZIO.logInfo(s"File $file closed") + ZIO.logInfo(s"File $file closed successfully") ) + // Combine resources using for comprehension for { db <- dbResource file <- fileResource - _ <- ZIO.logInfo(s"Running with resources: $db, $file, press Ctrl+C to interrupt") *> - ZIO.never + _ <- ZIO.logInfo(s"Running with database $db and file $file, press Ctrl+C to interrupt") *> + ZIO.never } yield () } } ``` -This example shows how to handle multiple resources that need cleanup: -1. Database connection (3s cleanup) -2. File handle (5s cleanup) -3. Total cleanup time: 8s -4. Timeout: 15s (sufficient for both resources) +This example demonstrates: +1. Managing multiple resources (database and file) +2. Each resource has its own cleanup time (3s and 5s) +3. Total cleanup time is 8 seconds (well within the 15s timeout) +4. Proper resource acquisition and release using `ZIO.acquireReleaseWith` +5. Clean shutdown with all resources properly released ### Platform-Specific Behavior From 6cb6bcb5b8c1620b9adbc82d657fcc1849bfd5a4 Mon Sep 17 00:00:00 2001 From: CodeMaverick2 Date: Wed, 4 Jun 2025 14:26:11 +0530 Subject: [PATCH 3/3] error with flatmap --- docs/reference/core/zioapp.md | 47 ++++++++++++++--------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/docs/reference/core/zioapp.md b/docs/reference/core/zioapp.md index aa86c8fb5c65..3096eb54cae2 100644 --- a/docs/reference/core/zioapp.md +++ b/docs/reference/core/zioapp.md @@ -239,35 +239,26 @@ object MultiResourceApp extends ZIOAppDefault { // Wait at most 15 seconds for all finalizers to complete on SIGINT override def gracefulShutdownTimeout: Duration = Duration.fromSeconds(15) - val run: ZIO[ZIOAppArgs with Scope, Any, Any] = { - // Database resource - val dbResource = ZIO.acquireReleaseWith( - acquire = ZIO.logInfo("Connecting to database...") *> - ZIO.sleep(1.second) *> - ZIO.succeed("DBConnection") - )( - release = conn => - ZIO.logInfo(s"Closing database connection (3s) ...") *> - ZIO.sleep(3.seconds) *> - ZIO.logInfo(s"Database connection $conn closed successfully") - ) - - // File resource - val fileResource = ZIO.acquireReleaseWith( - acquire = ZIO.logInfo("Opening file...") *> + val run: ZIO[ZIOAppArgs with Scope, Any, Any] = ZIO.scoped { + for { + db <- ZIO.acquireRelease( + ZIO.logInfo("Connecting to database...") *> + ZIO.sleep(1.second) *> + ZIO.succeed("DBConnection") + )(conn => + ZIO.logInfo(s"Closing database connection (3s) ...") *> + ZIO.sleep(3.seconds) *> + ZIO.logInfo(s"Database connection $conn closed successfully") + ) + file <- ZIO.acquireRelease( + ZIO.logInfo("Opening file...") *> ZIO.sleep(1.second) *> ZIO.succeed("FileHandle") - )( - release = file => - ZIO.logInfo(s"Closing file (5s) ...") *> - ZIO.sleep(5.seconds) *> - ZIO.logInfo(s"File $file closed successfully") - ) - - // Combine resources using for comprehension - for { - db <- dbResource - file <- fileResource + )(file => + ZIO.logInfo(s"Closing file (5s) ...") *> + ZIO.sleep(5.seconds) *> + ZIO.logInfo(s"File $file closed successfully") + ) _ <- ZIO.logInfo(s"Running with database $db and file $file, press Ctrl+C to interrupt") *> ZIO.never } yield () @@ -279,7 +270,7 @@ This example demonstrates: 1. Managing multiple resources (database and file) 2. Each resource has its own cleanup time (3s and 5s) 3. Total cleanup time is 8 seconds (well within the 15s timeout) -4. Proper resource acquisition and release using `ZIO.acquireReleaseWith` +4. Proper resource acquisition and release using `ZIO.acquireRelease` 5. Clean shutdown with all resources properly released ### Platform-Specific Behavior