Skip to content

Commit

Permalink
move papocstuff to new package
Browse files Browse the repository at this point in the history
  • Loading branch information
haaase committed Apr 3, 2024
1 parent fcdec5a commit 3665f7c
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package replication.papoctokens
package replication.protocols

import rdts.base.{Bottom, Lattice, Orderings, Uid}
import rdts.datatypes.contextual.ReplicatedSet
Expand Down Expand Up @@ -54,62 +54,6 @@ case class ExampleTokens(
calendarBinteractionA: Token
)

case class Vote(owner: Uid, voter: Uid)
case class Voting(rounds: Epoch[ReplicatedSet[Vote]], numParticipants: LastWriterWins[Int]) {
def threshold: Int = numParticipants.value / 2 + 1

def isOwner(using LocalReplicaId): Boolean =
val (id, count) = leadingCount
id == replicaId && count >= threshold

def request(using LocalReplicaId, Dots): Dotted[Voting] =
if !rounds.value.isEmpty then Voting.unchanged
else voteFor(replicaId)

def release(using LocalReplicaId): Voting =
if !isOwner
then Voting.unchanged.data
else Voting(Epoch(rounds.counter + 1, ReplicatedSet.empty), numParticipants)

def upkeep(using LocalReplicaId, Dots): Dotted[Voting] =
val (id, count) = leadingCount
if checkIfMajorityPossible(count)
then voteFor(id)
else Dotted(forceRelease)

def forceRelease(using LocalReplicaId): Voting =
Voting(Epoch(rounds.counter + 1, ReplicatedSet.empty), numParticipants)

def voteFor(uid: Uid)(using LocalReplicaId, Dots): Dotted[Voting] =
if rounds.value.elements.exists { case Vote(_, voter) => voter == replicaId }
then Voting.unchanged // already voted!
else
val newVote = rounds.value.addElem(Vote(uid, replicaId))
newVote.map(rs => Voting(rounds.write(rs), numParticipants))

def checkIfMajorityPossible(count: Int): Boolean =
val totalVotes = rounds.value.elements.size
val remainingVotes = numParticipants.value - totalVotes
(count + remainingVotes) > threshold

def leadingCount(using id: LocalReplicaId): (Uid, Int) =
val votes: Set[Vote] = rounds.value.elements
val grouped: Map[Uid, Int] = votes.groupBy(_.owner).map((o, elems) => (o, elems.size))
if grouped.isEmpty
then (replicaId, 0)
else grouped.maxBy((o, size) => size)
// .maxBy((o, size) => size)
}

object Voting {
given Bottom[Int] with
def empty = 0
val unchanged: Dotted[Voting] = Dotted(Voting(Epoch.empty, LastWriterWins.empty[Int]))

given Lattice[Voting] = Lattice.derived

}

case class Exclusive[T: Lattice: Bottom](token: Token, value: T) {
def transform(f: T => T)(using LocalReplicaId) =
if token.isOwner then f(value) else Bottom.empty
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package replication.protocols

import rdts.datatypes.contextual.ReplicatedSet
import rdts.datatypes.{Epoch, LastWriterWins}
import rdts.base.{Bottom, Lattice, Orderings, Uid}
import rdts.dotted.Dotted
import rdts.syntax.LocalReplicaId.replicaId
import rdts.time.Dots
import rdts.syntax.LocalReplicaId
import rdts.syntax.LocalReplicaId.replicaId

case class Vote(owner: Uid, voter: Uid)

case class Voting(rounds: Epoch[ReplicatedSet[Vote]], numParticipants: LastWriterWins[Int]) {
def threshold: Int = numParticipants.value / 2 + 1

def isOwner(using LocalReplicaId): Boolean =
val (id, count) = leadingCount
id == replicaId && count >= threshold

def request(using LocalReplicaId, Dots): Dotted[Voting] =
if !rounds.value.isEmpty then Voting.unchanged
else voteFor(replicaId)

def release(using LocalReplicaId): Voting =
if !isOwner
then Voting.unchanged.data
else Voting(Epoch(rounds.counter + 1, ReplicatedSet.empty), numParticipants)

def upkeep(using LocalReplicaId, Dots): Dotted[Voting] =
val (id, count) = leadingCount
if checkIfMajorityPossible(count)
then voteFor(id)
else Dotted(forceRelease)

def forceRelease(using LocalReplicaId): Voting =
Voting(Epoch(rounds.counter + 1, ReplicatedSet.empty), numParticipants)

def voteFor(uid: Uid)(using LocalReplicaId, Dots): Dotted[Voting] =
if rounds.value.elements.exists { case Vote(_, voter) => voter == replicaId }
then Voting.unchanged // already voted!
else
val newVote = rounds.value.addElem(Vote(uid, replicaId))
newVote.map(rs => Voting(rounds.write(rs), numParticipants))

def checkIfMajorityPossible(count: Int): Boolean =
val totalVotes = rounds.value.elements.size
val remainingVotes = numParticipants.value - totalVotes
(count + remainingVotes) > threshold

def leadingCount(using id: LocalReplicaId): (Uid, Int) =
val votes: Set[Vote] = rounds.value.elements
val grouped: Map[Uid, Int] = votes.groupBy(_.owner).map((o, elems) => (o, elems.size))
if grouped.isEmpty
then (replicaId, 0)
else grouped.maxBy((o, size) => size)
// .maxBy((o, size) => size)
}

object Voting {
given Bottom[Int] with
def empty = 0
val unchanged: Dotted[Voting] = Dotted(Voting(Epoch.empty, LastWriterWins.empty[Int]))

given Lattice[Voting] = Lattice.derived

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import rdts.datatypes.contextual.ReplicatedSet
import rdts.datatypes.{Epoch, LastWriterWins}
import rdts.syntax.LocalReplicaId
import rdts.time.Dots
import replication.papoctokens.{Vote, Voting}
import replication.papoctokens.Voting.*

class VotingTests2 extends munit.FunSuite {
class VotingTests2Participants extends munit.FunSuite {
given dots: Dots = Dots.empty
// create replicas with for set of 2 participants
// create replicas for set of 2 participants
val id1: LocalReplicaId = LocalReplicaId.gen()
val id2: LocalReplicaId = LocalReplicaId.gen()
var voting = Voting(
Expand All @@ -19,24 +17,24 @@ class VotingTests2 extends munit.FunSuite {
test("No initial owner") {
assert(!voting.isOwner(using id1))
}
test("Still not owner after one vote"){
test("Still not owner after one vote") {
voting = voting.merge(voting.voteFor(id1.uid)(using id1).data)
assert(!voting.isOwner(using id1))
}
test("Duplicate vote changes nothing"){
test("Duplicate vote changes nothing") {
assertEquals(voting.merge(voting.voteFor(id1.uid)(using id1).data), voting)
}
test("Is owner after two votes"){
test("Is owner after two votes") {
voting = voting.merge(voting.voteFor(id1.uid)(using id2).data)
assert(voting.isOwner(using id1))
assert(!voting.isOwner(using id2))
}
test("Is not owner for 4 participants"){
test("Is not owner for 4 participants") {
voting = voting.merge(Voting(voting.rounds, LastWriterWins.now(4)))
assert(!voting.isOwner(using id1))
assert(!voting.isOwner(using id2))
}
test("Is owner for 3 participants"){
test("Is owner for 3 participants") {
voting = voting.merge(Voting(voting.rounds, LastWriterWins.now(3)))
assert(voting.isOwner(using id1))
assert(!voting.isOwner(using id2))
Expand Down

0 comments on commit 3665f7c

Please sign in to comment.