From f550ce61fb0443a0f7971ec9954b9289a4b30e4e Mon Sep 17 00:00:00 2001 From: ragnar Date: Tue, 6 Feb 2024 22:20:37 +0100 Subject: [PATCH] =?UTF-8?q?sometimes=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../replication/papoctokens/Tokens.scala | 25 +++++---- .../scala/replication/JsoniterCodecs.scala | 2 +- .../main/scala/kofre/datatypes/Epoch.scala | 56 +++++++++++++++++++ .../main/scala/kofre/datatypes/Epoche.scala | 56 ------------------- .../datatypes/contextual/ReplicatedList.scala | 8 +-- 5 files changed, 74 insertions(+), 73 deletions(-) create mode 100644 Modules/RDTs/src/main/scala/kofre/datatypes/Epoch.scala delete mode 100644 Modules/RDTs/src/main/scala/kofre/datatypes/Epoche.scala diff --git a/Modules/Example Replication/jvm/src/main/scala/replication/papoctokens/Tokens.scala b/Modules/Example Replication/jvm/src/main/scala/replication/papoctokens/Tokens.scala index a8dede234..7b61ef733 100644 --- a/Modules/Example Replication/jvm/src/main/scala/replication/papoctokens/Tokens.scala +++ b/Modules/Example Replication/jvm/src/main/scala/replication/papoctokens/Tokens.scala @@ -1,7 +1,7 @@ package replication.papoctokens import kofre.base.{Bottom, Lattice, Orderings, Uid} -import kofre.datatypes.Epoche +import kofre.datatypes.Epoch import kofre.datatypes.contextual.ReplicatedSet import kofre.datatypes.contextual.ReplicatedSet.syntax import kofre.datatypes.experiments.RaftState @@ -12,7 +12,7 @@ import kofre.time.Dots import scala.util.Random -case class Ownership(epoche: Long, owner: Uid) +case class Ownership(epoch: Long, owner: Uid) object Ownership { given Lattice[Ownership] = Lattice.fromOrdering(Orderings.lexicographic) @@ -24,26 +24,27 @@ object Ownership { case class Token(os: Ownership, wants: ReplicatedSet[Uid]) { + def isOwner(using ReplicaId): Boolean = replicaId == os.owner + def request(using ReplicaId, Dots): Dotted[Token] = wants.addElem(replicaId).map(w => Token(Ownership.unchanged, w)) def release(using ReplicaId, Dots): Dotted[Token] = wants.removeElem(replicaId).map(w => Token(Ownership.unchanged, w)) - def grant(using ReplicaId): Token = + def upkeep(using ReplicaId): Token = if !isOwner then Token.unchanged else selectFrom(wants) match case None => Token.unchanged case Some(nextOwner) => - Token(Ownership(os.epoche + 1, nextOwner), ReplicatedSet.empty) + Token(Ownership(os.epoch + 1, nextOwner), ReplicatedSet.empty) def selectFrom(wants: ReplicatedSet[Uid])(using ReplicaId) = // We find the “largest” ID that wants the token. // This is incredibly “unfair” but does prevent deadlocks in case someone needs multiple tokens. wants.elements.maxOption.filter(id => id != replicaId) - def isOwner(using ReplicaId): Boolean = replicaId == os.owner } object Token { @@ -57,18 +58,18 @@ case class ExampleTokens( ) case class Vote(owner: Uid, voter: Uid) -case class Voting(rounds: Epoche[ReplicatedSet[Vote]]) { - - def request(using ReplicaId, Dots): Dotted[Voting] = - if !rounds.value.isEmpty then Voting.unchanged - else voteFor(replicaId) +case class Voting(rounds: Epoch[ReplicatedSet[Vote]]) { def isOwner(using ReplicaId): Boolean = val (id, count) = leadingCount id == replicaId && count >= Voting.threshold + def request(using ReplicaId, Dots): Dotted[Voting] = + if !rounds.value.isEmpty then Voting.unchanged + else voteFor(replicaId) + def release(using ReplicaId): Voting = - Voting(Epoche(rounds.counter + 1, ReplicatedSet.empty)) + Voting(Epoch(rounds.counter + 1, ReplicatedSet.empty)) def upkeep(using ReplicaId, Dots): Dotted[Voting] = val (id, count) = leadingCount @@ -93,7 +94,7 @@ case class Voting(rounds: Epoche[ReplicatedSet[Vote]]) { object Voting { - val unchanged: Dotted[Voting] = Dotted(Voting(Epoche.empty)) + val unchanged: Dotted[Voting] = Dotted(Voting(Epoch.empty)) given Lattice[Voting] = Lattice.derived diff --git a/Modules/Example Replication/shared/src/main/scala/replication/JsoniterCodecs.scala b/Modules/Example Replication/shared/src/main/scala/replication/JsoniterCodecs.scala index 8900fedef..3a8c6847d 100644 --- a/Modules/Example Replication/shared/src/main/scala/replication/JsoniterCodecs.scala +++ b/Modules/Example Replication/shared/src/main/scala/replication/JsoniterCodecs.scala @@ -6,7 +6,7 @@ import kofre.base.Uid import kofre.datatypes.alternatives.ResettableCounter import kofre.datatypes.contextual.{ReplicatedSet, EnableWinsFlag, MultiVersionRegister, ObserveRemoveMap, ReplicatedList} import kofre.datatypes.{ - Epoche, GrowOnlyCounter, GrowOnlyList, GrowOnlyMap, GrowOnlySet, LastWriterWins, PosNegCounter, TwoPhaseSet + Epoch, GrowOnlyCounter, GrowOnlyList, GrowOnlyMap, GrowOnlySet, LastWriterWins, PosNegCounter, TwoPhaseSet } import kofre.dotted.Dotted import kofre.datatypes.experiments.AuctionInterface.AuctionData diff --git a/Modules/RDTs/src/main/scala/kofre/datatypes/Epoch.scala b/Modules/RDTs/src/main/scala/kofre/datatypes/Epoch.scala new file mode 100644 index 000000000..6348b5c97 --- /dev/null +++ b/Modules/RDTs/src/main/scala/kofre/datatypes/Epoch.scala @@ -0,0 +1,56 @@ +package kofre.datatypes + +import kofre.base.{Bottom, Lattice} +import kofre.dotted.HasDots +import kofre.syntax.{OpsSyntaxHelper, PermMutate, PermQuery} +import kofre.time.{Dots, Time} + +case class Epoch[E](counter: Time, value: E) + +object Epoch { + + def empty[E: Bottom]: Epoch[E] = Epoch(0, Bottom[E].empty) + + given bottom[E: Bottom]: Bottom[Epoch[E]] with + override def empty: Epoch[E] = Epoch.empty + + given hasDots[E: HasDots: Bottom]: HasDots[Epoch[E]] = new { + extension (dotted: Epoch[E]) + def dots: Dots = dotted.value.dots + def removeDots(dots: Dots): Option[Epoch[E]] = dotted.value.removeDots(dots).map(nv => dotted.copy(value = nv)) + } + + extension [C, E](container: C) + def epoche: syntax[C, E] = syntax(container) + + implicit class syntax[C, E](container: C) + extends OpsSyntaxHelper[C, Epoch[E]](container) { + def read(using IsQuery): E = current.value + + def write(using IsMutator)(value: E): C = current.copy(value = value).mutator + def epocheWrite(using IsMutator)(value: E): C = Epoch(current.counter + 1, value).mutator + + def map(using IsMutator)(f: E => E): C = write(f(current.value)) + } + + given latticeInstance[E: Lattice]: Lattice[Epoch[E]] = new Lattice[Epoch[E]] { + + override def lteq(left: Epoch[E], right: Epoch[E]): Boolean = (left, right) match { + case (Epoch(cLeft, vLeft), Epoch(cRight, vRight)) => + cLeft < cRight || (cLeft == cRight && Lattice[E].lteq(vLeft, vRight)) + } + + /** Decomposes a lattice state into ic unique irredundant join decomposition of join-irreducible states */ + override def decompose(state: Epoch[E]): Iterable[Epoch[E]] = + val Epoch(c, v) = state + Lattice[E].decompose(v).map(Epoch(c, _)) + + /** By assumption: associative, commutative, idempotent. */ + override def merge(left: Epoch[E], right: Epoch[E]): Epoch[E] = (left, right) match { + case (Epoch(cLeft, vLeft), Epoch(cRight, vRight)) => + if (cLeft > cRight) left + else if (cRight > cLeft) right + else Epoch(cLeft, Lattice[E].merge(vLeft, vRight)) + } + } +} diff --git a/Modules/RDTs/src/main/scala/kofre/datatypes/Epoche.scala b/Modules/RDTs/src/main/scala/kofre/datatypes/Epoche.scala deleted file mode 100644 index 41579c1fc..000000000 --- a/Modules/RDTs/src/main/scala/kofre/datatypes/Epoche.scala +++ /dev/null @@ -1,56 +0,0 @@ -package kofre.datatypes - -import kofre.base.{Bottom, Lattice} -import kofre.dotted.HasDots -import kofre.syntax.{OpsSyntaxHelper, PermMutate, PermQuery} -import kofre.time.{Dots, Time} - -case class Epoche[E](counter: Time, value: E) - -object Epoche { - - def empty[E: Bottom]: Epoche[E] = Epoche(0, Bottom[E].empty) - - given bottom[E: Bottom]: Bottom[Epoche[E]] with - override def empty: Epoche[E] = Epoche.empty - - given hasDots[E: HasDots: Bottom]: HasDots[Epoche[E]] = new { - extension (dotted: Epoche[E]) - def dots: Dots = dotted.value.dots - def removeDots(dots: Dots): Option[Epoche[E]] = dotted.value.removeDots(dots).map(nv => dotted.copy(value = nv)) - } - - extension [C, E](container: C) - def epoche: syntax[C, E] = syntax(container) - - implicit class syntax[C, E](container: C) - extends OpsSyntaxHelper[C, Epoche[E]](container) { - def read(using IsQuery): E = current.value - - def write(using IsMutator)(value: E): C = current.copy(value = value).mutator - def epocheWrite(using IsMutator)(value: E): C = Epoche(current.counter + 1, value).mutator - - def map(using IsMutator)(f: E => E): C = write(f(current.value)) - } - - given latticeInstance[E: Lattice]: Lattice[Epoche[E]] = new Lattice[Epoche[E]] { - - override def lteq(left: Epoche[E], right: Epoche[E]): Boolean = (left, right) match { - case (Epoche(cLeft, vLeft), Epoche(cRight, vRight)) => - cLeft < cRight || (cLeft == cRight && Lattice[E].lteq(vLeft, vRight)) - } - - /** Decomposes a lattice state into ic unique irredundant join decomposition of join-irreducible states */ - override def decompose(state: Epoche[E]): Iterable[Epoche[E]] = - val Epoche(c, v) = state - Lattice[E].decompose(v).map(Epoche(c, _)) - - /** By assumption: associative, commutative, idempotent. */ - override def merge(left: Epoche[E], right: Epoche[E]): Epoche[E] = (left, right) match { - case (Epoche(cLeft, vLeft), Epoche(cRight, vRight)) => - if (cLeft > cRight) left - else if (cRight > cLeft) right - else Epoche(cLeft, Lattice[E].merge(vLeft, vRight)) - } - } -} diff --git a/Modules/RDTs/src/main/scala/kofre/datatypes/contextual/ReplicatedList.scala b/Modules/RDTs/src/main/scala/kofre/datatypes/contextual/ReplicatedList.scala index 4e1f75925..0c65ccb80 100644 --- a/Modules/RDTs/src/main/scala/kofre/datatypes/contextual/ReplicatedList.scala +++ b/Modules/RDTs/src/main/scala/kofre/datatypes/contextual/ReplicatedList.scala @@ -1,7 +1,7 @@ package kofre.datatypes.contextual import kofre.base.{Bottom, Lattice} -import kofre.datatypes.{Epoche, GrowOnlyList, LastWriterWins} +import kofre.datatypes.{Epoch, GrowOnlyList, LastWriterWins} import kofre.dotted.{Dotted, HasDots} import kofre.syntax.{OpsSyntaxHelper, PermQuery, ReplicaId} import kofre.time.{Dot, Dots} @@ -27,10 +27,10 @@ import HasDots.mapInstance * for collaborative applications", see [[https://www.sciencedirect.com/science/article/pii/S0743731510002716?casa_token=lQaLin7aEvcAAAAA:Esc3h3WvkFHUcvhalTPPvV5HbJge91D4-2jyKiSlz8GBDjx31l4xvfH8DIstmQ973PVi46ckXHg here]] * However, since then the implementation was changed significantly, thus it may be a different or even a novel strategy by now. */ -case class ReplicatedList[E](order: Epoche[GrowOnlyList[Dot]], meta: Map[Dot, LastWriterWins[E]]) +case class ReplicatedList[E](order: Epoch[GrowOnlyList[Dot]], meta: Map[Dot, LastWriterWins[E]]) object ReplicatedList { - def empty[E]: ReplicatedList[E] = ReplicatedList(Epoche.empty, Map.empty) + def empty[E]: ReplicatedList[E] = ReplicatedList(Epoch.empty, Map.empty) given lattice[E]: Lattice[ReplicatedList[E]] = Lattice.derived given hasDots[E]: HasDots[ReplicatedList[E]] with { @@ -49,7 +49,7 @@ object ReplicatedList { private class DeltaStateFactory[E] { def make( - epoche: Epoche[GrowOnlyList[Dot]] = empty._1, + epoche: Epoch[GrowOnlyList[Dot]] = empty._1, df: Map[Dot, LastWriterWins[E]] = Map.empty, cc: Dots = Dots.empty ): Dotted[ReplicatedList[E]] = Dotted(ReplicatedList(epoche, df), cc)