-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1330 from geny200/permit
Add Permit type like Semaphore
- Loading branch information
Showing
12 changed files
with
258 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
modules/core/ce2/src/test/scala/tofu/concurrent/PermitSuite.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package tofu.concurrent | ||
|
||
import cats.data.ReaderT | ||
import cats.effect.concurrent.Deferred | ||
import cats.effect.syntax.concurrent.* | ||
import cats.effect.{Concurrent, ContextShift, IO, Sync} | ||
import cats.syntax.applicativeError.* | ||
import org.scalatest.funsuite.AnyFunSuite | ||
import tofu.compat.unused | ||
import tofu.syntax.monadic.* | ||
|
||
import scala.concurrent.ExecutionContext | ||
|
||
class PermitSuite extends AnyFunSuite { | ||
|
||
private implicit val ioCS: ContextShift[IO] = IO.contextShift(ExecutionContext.global) | ||
|
||
@unused | ||
private def summonInstance[I[_]: Sync, F[_]: Concurrent]: MakePermit[I, F] = | ||
implicitly[MakePermit[I, F]] | ||
|
||
test("check IO has Permit") { | ||
assert( | ||
MakePermit[IO, ReaderT[IO, Unit, _]] | ||
.of(2) | ||
.flatMap(permitProg(_).run(())) | ||
.unsafeRunSync() === Left(()) | ||
) | ||
} | ||
|
||
private def permitProg[F[_]: Concurrent](permit: Permit[F]): F[Either[Unit, Unit]] = | ||
for { | ||
wait1 <- Deferred[F, Unit] | ||
wait2 <- Deferred[F, Unit] | ||
waitEnd <- Deferred[F, Unit] | ||
fiber1 <- permit.withPermit(wait1.complete(()) >> waitEnd.get).start | ||
fiber2 <- permit.withPermit(wait2.complete(()) >> waitEnd.get).start | ||
_ <- wait1.get | ||
_ <- wait2.get | ||
// fiber3 will be blocked, and throw an IllegalStateException when fiber1 or fiber2 will complete | ||
fiber3 <- permit.withPermit(waitEnd.complete(()).attempt).start | ||
resultFiber <- (fiber1.join >> fiber2.join >> fiber3.join).start | ||
_ <- waitEnd.complete(()) | ||
result <- resultFiber.join | ||
} yield result.left.map(_ => ()) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
modules/core/ce3/src/test/scala/tofu/concurrent/PermitSuite.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package tofu.concurrent | ||
|
||
import cats.data.ReaderT | ||
import cats.effect.kernel.Deferred | ||
import cats.effect.syntax.spawn.* | ||
import cats.effect.unsafe.IORuntime | ||
import cats.effect.{Async, Concurrent, IO, Sync} | ||
import org.scalatest.funsuite.AnyFunSuite | ||
import tofu.compat.unused | ||
import tofu.syntax.monadic.* | ||
|
||
class PermitSuite extends AnyFunSuite { | ||
private implicit val iort: IORuntime = IORuntime.global | ||
|
||
@unused | ||
private def summonInstance[I[_]: Sync, F[_]: Async]: MakePermit[I, F] = | ||
implicitly[MakePermit[I, F]] | ||
|
||
test("check Permit") { | ||
assert( | ||
MakePermit[IO, ReaderT[IO, Unit, _]] | ||
.of(2) | ||
.flatMap(permitProg(_).run(())) | ||
.unsafeRunSync() === false | ||
) | ||
} | ||
|
||
private def permitProg[F[_]: Concurrent](permit: Permit[F]): F[Boolean] = | ||
for { | ||
wait1 <- Deferred[F, Unit] | ||
wait2 <- Deferred[F, Unit] | ||
waitEnd <- Deferred[F, Unit] | ||
fiber1 <- permit.withPermit(wait1.complete(()) >> waitEnd.get).start | ||
fiber2 <- permit.withPermit(wait2.complete(()) >> waitEnd.get).start | ||
_ <- wait1.get | ||
_ <- wait2.get | ||
// fiber3 will be blocked, and return false (on complete) when fiber1 or fiber2 will complete | ||
fiber3 <- permit.withPermit(waitEnd.complete(())).start | ||
resultFiber <- (fiber1.join >> fiber2.join >> fiber3.join).flatMap(_.embedNever).start | ||
_ <- waitEnd.complete(()) | ||
result <- resultFiber.join.flatMap(_.embedNever) | ||
} yield result | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
modules/kernel/src/main/scala-2/tofu/internal/instances/MakePermitInstance.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package tofu.internal.instances | ||
|
||
import tofu.concurrent.MakePermit | ||
import tofu.internal.carriers.{MkPermitCE2Carrier, MkPermitCE3Carrier} | ||
|
||
private[tofu] trait MakePermitInstance extends MakePermitInstance0 { | ||
final implicit def interopCE3[I[_], F[_]](implicit carrier: MkPermitCE3Carrier[I, F]): MakePermit[I, F] = | ||
carrier | ||
} | ||
|
||
private[tofu] trait MakePermitInstance0 { | ||
final implicit def interopCE2[I[_], F[_]](implicit carrier: MkPermitCE2Carrier[I, F]): MakePermit[I, F] = | ||
carrier | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
modules/kernel/src/main/scala-3/tofu/internal/instances/MakePermitInstance.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package tofu.internal | ||
package instances | ||
|
||
import scala.compiletime.summonFrom | ||
import tofu.concurrent.MakePermit | ||
import tofu.internal.carriers.{MkPermitCE2Carrier, MkPermitCE3Carrier} | ||
|
||
private[tofu] trait MakePermitInstance: | ||
inline given [I[_], F[_]]: MakePermit[I, F] = summonFrom { | ||
case carrier: MkPermitCE2Carrier[I, F] => carrier | ||
case carrier: MkPermitCE3Carrier[I, F] => carrier | ||
} |
58 changes: 58 additions & 0 deletions
58
modules/kernel/src/main/scala/tofu/concurrent/Permit.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package tofu.concurrent | ||
|
||
import tofu.internal.instances.MakePermitInstance | ||
|
||
/** A purely functional semaphore. A semaphore has a non-negative number of permits available. Acquiring a permit | ||
* decrements the current number of permits and releasing a permit increases the current number of permits. An acquire | ||
* that occurs when there are no permits available results in semantic blocking until a permit becomes available. | ||
* Blocking `withPermit` are cancelable. | ||
*/ | ||
trait Permit[F[_]] { | ||
|
||
/** Returns an effect that acquires a permit, runs the supplied effect, and then releases the permit. The returned | ||
* effect semantically blocks until permit are available. Note that acquires are statisfied in strict FIFO order. | ||
*/ | ||
def withPermit[A](fa: F[A]): F[A] | ||
|
||
} | ||
|
||
object Permit { | ||
type Make[F[_]] = MakePermit[F, F] | ||
|
||
/** A helper for creating instances of [[tofu.concurrent.Permit]] that use the same effect during construction and | ||
* work. If you want to use different effect to construct `Permit` use [[tofu.concurrent.MakePermit]] | ||
*/ | ||
def Make[F[_]](implicit makePermit: Make[F]): MakePermit.PermitApplier[F, F] = | ||
new MakePermit.PermitApplier[F, F](makePermit) | ||
} | ||
|
||
/** A creator of [[tofu.concurrent.Permit]] that supports effectful construction | ||
* @tparam I | ||
* effect for creation of agent | ||
* @tparam F | ||
* effect on which agent will run | ||
*/ | ||
trait MakePermit[I[_], F[_]] { | ||
|
||
/** Creates instance of [[tofu.concurrent.Permit]], initialized with `limit` available permits | ||
* | ||
* @param limit | ||
* maximum concurrent permits | ||
* @return | ||
* `I[ Permit[F] ]` | ||
*/ | ||
def permitOf(limit: Long): I[Permit[F]] | ||
} | ||
|
||
/** A helper for creating instances of [[tofu.concurrent.Permit]] that use different effects during construction and | ||
* work. If you want to use same effect to construct and run `Permit` use [[tofu.concurrent.Permit.Make]] | ||
*/ | ||
object MakePermit extends MakePermitInstance { | ||
|
||
def apply[I[_], F[_]](implicit mkPermit: MakePermit[I, F]): PermitApplier[I, F] = | ||
new PermitApplier[I, F](mkPermit) | ||
|
||
final class PermitApplier[I[_], F[_]](private val mkPermit: MakePermit[I, F]) extends AnyVal { | ||
def of(limit: Long): I[Permit[F]] = mkPermit.permitOf(limit) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters