or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

application.mdconcurrency.mdcore-effects.mddependency-injection.mderror-handling.mdindex.mdmetrics.mdresource-management.mdservices.mdstm.mdstreams.mdtesting.md
tile.json

resource-management.mddocs/

Resource Management

ZIO's Scope-based resource lifecycle management provides automatic cleanup and finalizer execution for leak-free applications with guaranteed resource cleanup even in the presence of failures and interruptions.

Capabilities

Scope - Resource Lifecycle Management

The foundational abstraction for managing resource lifecycles with automatic cleanup guarantees.

/**
 * A scope represents a resource lifecycle boundary where resources can be safely allocated
 * and automatically cleaned up when the scope closes
 */
sealed trait Scope {
  /** Add a finalizer that runs when the scope closes */
  def addFinalizer(finalizer: => UIO[Any]): UIO[Unit]
  
  /** Add a finalizer that receives the exit status */
  def addFinalizerExit(finalizer: Exit[Any, Any] => UIO[Any]): UIO[Unit]
  
  /** Create a child scope */
  def fork: UIO[Scope.Closeable]
  
  /** Create a child scope with custom execution strategy */
  def forkWith(executionStrategy: => ExecutionStrategy): UIO[Scope.Closeable]
  
  /** Extend the scope to cover a larger region */
  def extend[R]: Scope.ExtendPartiallyApplied[R]
}

/**
 * A closeable scope that can be explicitly closed
 */
abstract class Scope.Closeable extends Scope {
  /** Close the scope, running all finalizers */
  def close(exit: => Exit[Any, Any]): UIO[Unit]
  
  /** Number of finalizers registered */
  def size: Int
  
  /** Use this scope for an effect */
  def use[R]: Scope.UsePartiallyApplied[R]
}

Scope Factory Methods and Operations

Create and manage scopes for various resource management patterns.

/**
 * Create a new closeable scope
 */
def make: UIO[Scope.Closeable]

/**
 * Create a scope with custom execution strategy
 */
def makeWith(executionStrategy: => ExecutionStrategy): UIO[Scope.Closeable]

/**
 * Create a scope that runs finalizers in parallel
 */
def parallel: UIO[Scope.Closeable]

/**
 * Add a finalizer to the current scope
 */
def addFinalizer(finalizer: => UIO[Any]): ZIO[Scope, Nothing, Unit]

/**
 * Add an exit-aware finalizer to the current scope
 */
def addFinalizerExit(finalizer: Exit[Any, Any] => UIO[Any]): ZIO[Scope, Nothing, Unit]

/**
 * Access to global scope (never closes)
 */
val global: Scope.Closeable

/**
 * Default scope layer for dependency injection
 */
val default: ZLayer[Any, Nothing, Scope]

/**
 * Execute an effect with a fresh scope
 */
def scoped[R, E, A](zio: ZIO[R with Scope, E, A]): ZIO[R, E, A]

/**
 * Execute an effect that requires a scope
 */
def scopedWith[R, E, A](f: Scope => ZIO[R, E, A]): ZIO[R, E, A]

Usage Examples:

import zio._

// Basic resource management with scope
val resourceProgram = ZIO.scoped {
  for {
    _        <- Scope.addFinalizer(Console.printLine("Cleaning up resources"))
    resource <- ZIO.attempt(new FileInputStream("data.txt"))
    _        <- Scope.addFinalizer(ZIO.succeed(resource.close()))
    data     <- ZIO.attempt(resource.read())
  } yield data
}

// Manual scope control
val manualScopeProgram = for {
  scope <- Scope.make
  _     <- scope.addFinalizer(Console.printLine("Manual cleanup"))
  
  result <- scope.use { implicit s =>
    for {
      resource <- acquireResource
      data     <- processResource(resource)
    } yield data
  }
  
  _ <- scope.close(Exit.succeed(()))
} yield result

// Nested scopes with different lifecycles
val nestedScopes = ZIO.scoped {
  for {
    _     <- Scope.addFinalizer(Console.printLine("Outer scope cleanup"))
    outer <- acquireOuterResource
    
    result <- ZIO.scoped {
      for {
        _     <- Scope.addFinalizer(Console.printLine("Inner scope cleanup"))
        inner <- acquireInnerResource
        data  <- processResources(outer, inner)
      } yield data
    }
  } yield result
}

Resource Acquisition Patterns

Common patterns for safe resource acquisition and management.

/**
 * Acquire-release pattern for resource management
 */
def acquireRelease[R, E, A](
  acquire: => ZIO[R, E, A]
)(release: A => URIO[R, Any]): ZIO[R with Scope, E, A]

/**
 * Acquire-release with exit-aware cleanup
 */
def acquireReleaseExit[R, E, A](
  acquire: => ZIO[R, E, A]
)(release: (A, Exit[Any, Any]) => URIO[R, Any]): ZIO[R with Scope, E, A]

/**
 * Acquire multiple resources atomically
 */
def acquireReleaseAttempt[R, E, A](
  acquire: ZIO[R, E, A]
)(release: A => URIO[R, Any]): ZIO[R with Scope, E, A]

/**
 * Auto-closeable resource pattern (for Java interop)
 */
def fromAutoCloseable[R, E, A <: AutoCloseable](
  fa: ZIO[R, E, A]
): ZIO[R with Scope, E, A]

/**
 * Ensuring finalizer runs regardless of how effect completes
 */
def ensuring[R1 <: R](finalizer: => URIO[R1, Any]): ZIO[R1, E, A]

/**
 * Add finalizer that only runs on specific exit cases
 */
def onExit[R1 <: R](cleanup: Exit[E, A] => URIO[R1, Any]): ZIO[R1, E, A]

Usage Examples:

// File processing with automatic cleanup
val fileProcessing = ZIO.scoped {
  for {
    file <- ZIO.acquireRelease(
              ZIO.attempt(new BufferedReader(new FileReader("input.txt")))
            )(reader => ZIO.succeed(reader.close()).ignore)
            
    output <- ZIO.acquireRelease(
                ZIO.attempt(new PrintWriter("output.txt"))
              )(writer => ZIO.succeed(writer.close()).ignore)
              
    _      <- processFile(file, output)
  } yield ()
}

// Database connection management
val databaseWork = ZIO.scoped {
  for {
    connection <- ZIO.acquireRelease(
                    ZIO.attempt(DriverManager.getConnection(jdbcUrl))
                  )(conn => ZIO.succeed(conn.close()).ignore)
                  
    statement  <- ZIO.acquireRelease(
                    ZIO.attempt(connection.prepareStatement(sql))
                  )(stmt => ZIO.succeed(stmt.close()).ignore)
                  
    results    <- ZIO.attempt(statement.executeQuery())
    data       <- processResults(results)
  } yield data
}

// Network resource management
val networkOperation = ZIO.scoped {
  for {
    socket <- ZIO.acquireReleaseExit(
                ZIO.attempt(new Socket("localhost", 8080))
              ) { (socket, exit) =>
                for {
                  _ <- Console.printLine(s"Closing socket. Exit: $exit")
                  _ <- ZIO.succeed(socket.close()).ignore
                } yield ()
              }
              
    stream <- ZIO.fromAutoCloseable(
                ZIO.attempt(socket.getInputStream())
              )
              
    data   <- readFromStream(stream)
  } yield data
}

Advanced Resource Patterns

Complex resource management patterns for sophisticated applications.

// Resource pool pattern
class ResourcePool[R, E, A](
  factory: ZIO[R, E, A],
  cleanup: A => URIO[R, Any],
  maxSize: Int
) {
  private val available = Queue.bounded[A](maxSize)
  private val inUse = Ref.make(Set.empty[A])
  
  def acquire: ZIO[R with Scope, E, A] = ZIO.scoped {
    for {
      resource <- available.take.orElse(factory)
      _        <- inUse.update(_ + resource)
      _        <- Scope.addFinalizer(release(resource))
    } yield resource
  }
  
  def release(resource: A): URIO[R, Unit] = {
    for {
      _     <- inUse.update(_ - resource)
      _     <- available.offer(resource).ignore
    } yield ()
  }
  
  def shutdown: URIO[R, Unit] = {
    for {
      resources <- available.takeAll
      _         <- ZIO.foreachDiscard(resources)(cleanup)
    } yield ()
  }
}

// Cached resource pattern
class CachedResource[R, E, A](
  factory: ZIO[R, E, A],
  cleanup: A => URIO[R, Any],
  ttl: Duration
) {
  private val cache = Ref.make(Option.empty[(A, Long)])
  
  def get: ZIO[R with Scope, E, A] = ZIO.scoped {
    for {
      now    <- Clock.currentTime(TimeUnit.MILLISECONDS)
      cached <- cache.get
      
      resource <- cached match {
        case Some((resource, timestamp)) if (now - timestamp) < ttl.toMillis =>
          ZIO.succeed(resource)
        case _ =>
          for {
            newResource <- factory
            _           <- cache.set(Some((newResource, now)))
            _           <- Scope.addFinalizer(cleanup(newResource))
          } yield newResource
      }
    } yield resource
  }
  
  def invalidate: UIO[Unit] = cache.set(None)
}

// Scoped reference pattern
class ScopedRef[A](initialValue: A) {
  private val ref = Ref.make(initialValue)
  
  def scoped: ZIO[Scope, Nothing, Ref[A]] = {
    for {
      currentValue <- ref.get
      scopedRef    <- Ref.make(currentValue)
      _            <- Scope.addFinalizerExit { exit =>
                        for {
                          finalValue <- scopedRef.get
                          _          <- ref.set(finalValue)
                        } yield ()
                      }
    } yield scopedRef
  }
  
  def get: UIO[A] = ref.get
  def set(a: A): UIO[Unit] = ref.set(a)
}

Usage Examples:

// HTTP client with connection pooling
val httpClientProgram = for {
  pool <- ZIO.succeed(new ResourcePool(
            factory = ZIO.attempt(HttpClientBuilder.create().build()),
            cleanup = client => ZIO.succeed(client.close()).ignore,
            maxSize = 10
          ))
          
  results <- ZIO.foreachPar(urls) { url =>
               ZIO.scoped {
                 for {
                   client   <- pool.acquire
                   request  <- ZIO.succeed(HttpGet(url))  
                   response <- ZIO.attempt(client.execute(request))
                   body     <- ZIO.attempt(EntityUtils.toString(response.getEntity))
                 } yield body
               }
             }
             
  _ <- pool.shutdown
} yield results

// Cached database connection
val cachedDbProgram = for {
  connectionCache <- ZIO.succeed(new CachedResource(
                       factory = ZIO.attempt(DriverManager.getConnection(url)),
                       cleanup = conn => ZIO.succeed(conn.close()).ignore,
                       ttl = 5.minutes
                     ))
                     
  results <- ZIO.foreachPar(queries) { query =>
               ZIO.scoped {
                 for {
                   conn   <- connectionCache.get
                   stmt   <- ZIO.attempt(conn.prepareStatement(query))
                   result <- ZIO.attempt(stmt.executeQuery())
                   data   <- extractData(result)
                 } yield data
               }
             }
} yield results

// Hierarchical resource management
val hierarchicalResources = ZIO.scoped {
  for {
    // Top-level resources
    _        <- Scope.addFinalizer(Console.printLine("Shutting down application"))
    config   <- loadConfiguration
    
    // Mid-level resources  
    database <- ZIO.scoped {
                  for {
                    _  <- Scope.addFinalizer(Console.printLine("Closing database"))
                    db <- initializeDatabase(config.dbConfig)
                  } yield db
                }
                
    // Low-level resources
    result   <- ZIO.scoped {
                  for {
                    _       <- Scope.addFinalizer(Console.printLine("Cleaning up transaction"))
                    session <- database.createSession()
                    _       <- Scope.addFinalizer(ZIO.succeed(session.close()).ignore)
                    data    <- session.query("SELECT * FROM users")
                  } yield data
                }
  } yield result
}