Skip to content

Commit

Permalink
sometimes …
Browse files Browse the repository at this point in the history
  • Loading branch information
rmgk committed Feb 6, 2024
1 parent 72265f9 commit f550ce6
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions Modules/RDTs/src/main/scala/kofre/datatypes/Epoch.scala
Original file line number Diff line number Diff line change
@@ -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))
}
}
}
56 changes: 0 additions & 56 deletions Modules/RDTs/src/main/scala/kofre/datatypes/Epoche.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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}
Expand All @@ -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 {
Expand All @@ -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)
Expand Down

0 comments on commit f550ce6

Please sign in to comment.