-
-
Notifications
You must be signed in to change notification settings - Fork 566
Make HotSwap safe to concurrent access
#3480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a78ebcd
cbf5ca2
a53da76
620f7dc
1b113f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,11 +64,20 @@ sealed trait Hotswap[F[_], R] { | |
| * is not used thereafter. Failure to do so may result in an error on the _consumer_ side. In | ||
| * any case, no resources will be leaked. | ||
| * | ||
| * For safer access to the current resource see [[get]], which guarantees that it will not be | ||
| * released while it is being used. | ||
| * | ||
| * If [[swap]] is called after the lifetime of the [[Hotswap]] is over, it will raise an | ||
| * error, but will ensure that all resources are finalized before returning. | ||
| */ | ||
| def swap(next: Resource[F, R]): F[R] | ||
|
|
||
| /** | ||
| * Gets the current resource, if it exists. The returned resource is guaranteed to be | ||
| * available for the duration of the returned resource. | ||
| */ | ||
| def get: Resource[F, Option[R]] | ||
|
Comment on lines
+75
to
+79
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a little bit annoying UX that this is an
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bikeshed the name. |
||
|
|
||
| /** | ||
| * Pops and runs the finalizer of the current resource, if it exists. | ||
| * | ||
|
|
@@ -92,45 +101,72 @@ object Hotswap { | |
| * Creates a new [[Hotswap]], which represents a [[cats.effect.kernel.Resource]] that can be | ||
| * swapped during the lifetime of this [[Hotswap]]. | ||
| */ | ||
| def create[F[_], R](implicit F: Concurrent[F]): Resource[F, Hotswap[F, R]] = { | ||
| type State = Option[F[Unit]] | ||
|
|
||
| def initialize: F[Ref[F, State]] = | ||
| F.ref(Some(F.pure(()))) | ||
|
|
||
| def finalize(state: Ref[F, State]): F[Unit] = | ||
| state.getAndSet(None).flatMap { | ||
| case Some(finalizer) => finalizer | ||
| case None => raise("Hotswap already finalized") | ||
| } | ||
|
|
||
| def raise(message: String): F[Unit] = | ||
| F.raiseError[Unit](new RuntimeException(message)) | ||
|
|
||
| Resource.make(initialize)(finalize).map { state => | ||
| new Hotswap[F, R] { | ||
|
|
||
| override def swap(next: Resource[F, R]): F[R] = | ||
| F.uncancelable { poll => | ||
| poll(next.allocated).flatMap { | ||
| case (r, finalizer) => | ||
| swapFinalizer(finalizer).as(r) | ||
| def create[F[_], R](implicit F: Concurrent[F]): Resource[F, Hotswap[F, R]] = | ||
| Resource.eval(Semaphore[F](Long.MaxValue)).flatMap { semaphore => | ||
| sealed abstract class State | ||
| case object Cleared extends State | ||
| case class Acquired(r: R, fin: F[Unit]) extends State | ||
| case object Finalized extends State | ||
|
|
||
| def initialize: F[Ref[F, State]] = | ||
| F.ref(Cleared) | ||
|
|
||
| def finalize(state: Ref[F, State]): F[Unit] = | ||
| state.getAndSet(Finalized).flatMap { | ||
| case Acquired(_, finalizer) => finalizer | ||
| case Cleared => F.unit | ||
| case Finalized => raise("Hotswap already finalized") | ||
| } | ||
|
|
||
| def raise(message: String): F[Unit] = | ||
| F.raiseError[Unit](new RuntimeException(message)) | ||
|
|
||
| def shared: Resource[F, Unit] = semaphore.permit | ||
|
|
||
| def exclusive: Resource[F, Unit] = | ||
| Resource.makeFull[F, Unit](poll => poll(semaphore.acquireN(Long.MaxValue)))(_ => | ||
| semaphore.releaseN(Long.MaxValue)) | ||
|
Comment on lines
+124
to
+128
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stole this from here: It seems like a good addition to trait Lock[F[_]] {
def shared: Resource[F, Unit]
def exclusive: Resource[F, Unit]
}
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| Resource.make(initialize)(finalize).map { state => | ||
| new Hotswap[F, R] { | ||
|
|
||
| override def swap(next: Resource[F, R]): F[R] = | ||
| exclusive.surround { | ||
| F.uncancelable { poll => | ||
| poll(next.allocated).flatMap { | ||
| case (r, fin) => | ||
| swapFinalizer(Acquired(r, fin)).as(r) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| override def clear: F[Unit] = | ||
| swapFinalizer(F.unit).uncancelable | ||
|
|
||
| private def swapFinalizer(next: F[Unit]): F[Unit] = | ||
| state.modify { | ||
| case Some(previous) => | ||
| Some(next) -> previous | ||
| case None => | ||
| None -> (next *> raise("Cannot swap after finalization")) | ||
| }.flatten | ||
| override def get: Resource[F, Option[R]] = | ||
| shared.evalMap { _ => | ||
| state.get.map { | ||
| case Acquired(r, _) => Some(r) | ||
| case _ => None | ||
| } | ||
| } | ||
|
|
||
| override def clear: F[Unit] = | ||
| exclusive.surround(swapFinalizer(Cleared).uncancelable) | ||
|
|
||
| private def swapFinalizer(next: State): F[Unit] = | ||
| state.modify { | ||
| case Acquired(_, fin) => | ||
| next -> fin | ||
| case Cleared => | ||
| next -> F.unit | ||
| case Finalized => | ||
| val fin = next match { | ||
| case Acquired(_, fin) => fin | ||
| case _ => F.unit | ||
| } | ||
| Finalized -> (fin *> raise("Cannot swap after finalization")) | ||
| }.flatten | ||
|
|
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This API is a bit unsafey since it directly returns the
Rbut I think we'll just have to live with that.