Skip to main content
Version: 2.x

TSemaphore

TSemaphore is a semaphore with transactional semantics that can be used to control access to a common resource. It holds a certain number of permits, and permits may be acquired or released.

Create a TSemaphore

Creating a TSemaphore with 10 permits:

import zio._
import zio.stm._

val tSemaphoreCreate: STM[Nothing, TSemaphore] = TSemaphore.make(10L)

Acquire a permit

Acquiring a permit reduces the number of remaining permits that the TSemaphore contains. Acquiring a permit is done when a user wants to access a common shared resource:

import zio._
import zio.stm._

val tSemaphoreAcq: STM[Nothing, TSemaphore] = for {
tSem <- TSemaphore.make(2L)
_ <- tSem.acquire
} yield tSem

tSemaphoreAcq.commit

Note that if you try to acquire a permit when there are no more remaining permits in the semaphore then execution will be blocked semantically until a permit is ready to be acquired. Note that semantic blocking does not block threads and the STM transaction will only be retried when a permit is released.

Release a permit

Once you have finished accessing the shared resource, you must release your permit so other parties can access the shared resource:

import zio._
import zio.stm._

val tSemaphoreRelease: STM[Nothing, TSemaphore] = for {
tSem <- TSemaphore.make(1L)
_ <- tSem.acquire
_ <- tSem.release
} yield tSem

tSemaphoreRelease.commit

Retrieve available permits

You can query for the remaining amount of permits in the TSemaphore by using available:

import zio._
import zio.stm._

val tSemaphoreAvailable: STM[Nothing, Long] = for {
tSem <- TSemaphore.make(2L)
_ <- tSem.acquire
cap <- tSem.available
} yield cap

tSemaphoreAvailable.commit

The above code creates a TSemaphore with two permits and acquires one permit without releasing it. Here, available will report that there is a single permit left.

Execute an arbitrary STM action with automatic acquire and release

You can choose to execute any arbitrary STM action that requires acquiring and releasing permit on TSemaphore as part of the same transaction. Rather than doing:

import zio._
import zio.stm._

def yourSTMAction: STM[Nothing, Unit] = STM.unit

val tSemaphoreWithoutPermit: STM[Nothing, Unit] =
for {
sem <- TSemaphore.make(1L)
_ <- sem.acquire
a <- yourSTMAction
_ <- sem.release
} yield a

tSemaphoreWithoutPermit.commit

You can simply use withPermit instead:

import zio._
import zio.stm._

val tSemaphoreWithPermit: IO[Nothing, Unit] =
for {
sem <- TSemaphore.make(1L).commit
a <- sem.withPermit(yourSTMAction.commit)
} yield a

It is considered best practice to use withPermit over using an acquire and a release directly unless dealing with more complicated use cases that involve multiple STM actions where acquire is not at the start and release is not at the end of the STM transaction.

Acquire and release multiple permits

It is possible to acquire and release multiple permits at a time using acquireN and releaseN:

import zio._
import zio.stm._

val tSemaphoreAcquireNReleaseN: STM[Nothing, Boolean] = for {
sem <- TSemaphore.make(3L)
_ <- sem.acquireN(3L)
cap <- sem.available
_ <- sem.releaseN(3L)
} yield cap == 0

tSemaphoreAcquireNReleaseN.commit