/** * Copyright (C) 2009-2017 Lightbend Inc. */ package akka.cluster.ddata import akka.cluster.Cluster import akka.cluster.UniqueAddress import akka.util.HashCode import akka.annotation.InternalApi object LWWRegister { trait Clock[A] { /** * @param currentTimestamp the current `timestamp` value of the `LWWRegister` * @param value the register value to set and associate with the returned timestamp * @return next timestamp */ def apply(currentTimestamp: Long, value: A): Long } private val _defaultClock: Clock[Any] = new Clock[Any] { override def apply(currentTimestamp: Long, value: Any): Long = math.max(System.currentTimeMillis(), currentTimestamp + 1) } /** * The default [[LWWRegister.Clock]] is using max value of `System.currentTimeMillis()` * and `currentTimestamp + 1`. */ def defaultClock[A]: Clock[A] = _defaultClock.asInstanceOf[Clock[A]] private val _reverseClock = new Clock[Any] { override def apply(currentTimestamp: Long, value: Any): Long = math.min(-System.currentTimeMillis(), currentTimestamp - 1) } /** * This [[LWWRegister.Clock]] can be used for first-write-wins semantics. It is using min value of * `-System.currentTimeMillis()` and `currentTimestamp + 1`, i.e. it is counting backwards. */ def reverseClock[A]: Clock[A] = _reverseClock.asInstanceOf[Clock[A]] /** * INTERNAL API */ @InternalApi private[akka] def apply[A](node: UniqueAddress, initialValue: A, clock: Clock[A]): LWWRegister[A] = new LWWRegister(node, initialValue, clock(0L, initialValue)) def apply[A](initialValue: A)(implicit node: Cluster, clock: Clock[A] = defaultClock[A]): LWWRegister[A] = apply(node.selfUniqueAddress, initialValue, clock) /** * Java API */ def create[A](node: Cluster, initialValue: A): LWWRegister[A] = apply(initialValue)(node) /** * Java API */ def create[A](node: Cluster, initialValue: A, clock: Clock[A]): LWWRegister[A] = apply(initialValue)(node, clock) /** * Extract the [[LWWRegister#value]]. */ def unapply[A](c: LWWRegister[A]): Option[A] = Some(c.value) } /** * Implements a 'Last Writer Wins Register' CRDT, also called a 'LWW-Register'. * * It is described in the paper * A comprehensive study of Convergent and Commutative Replicated Data Types. * * Merge takes the register with highest timestamp. Note that this * relies on synchronized clocks. `LWWRegister` should only be used when the choice of * value is not important for concurrent updates occurring within the clock skew. * * Merge takes the register updated by the node with lowest address (`UniqueAddress` is ordered) * if the timestamps are exactly the same. * * Instead of using timestamps based on `System.currentTimeMillis()` time it is possible to * use a timestamp value based on something else, for example an increasing version number * from a database record that is used for optimistic concurrency control. * * The `defaultClock` is using max value of `System.currentTimeMillis()` and `currentTimestamp + 1`. * This means that the timestamp is increased for changes on the same node that occurs within * the same millisecond. It also means that it is safe to use the `LWWRegister` without * synchronized clocks when there is only one active writer, e.g. a Cluster Singleton. Such a * single writer should then first read current value with `ReadMajority` (or more) before * changing and writing the value with `WriteMajority` (or more). * * For first-write-wins semantics you can use the [[LWWRegister#reverseClock]] instead of the * [[LWWRegister#defaultClock]] * * This class is immutable, i.e. "modifying" methods return a new instance. */ @SerialVersionUID(1L) final class LWWRegister[A] private[akka] ( private[akka] val node: UniqueAddress, val value: A, val timestamp: Long) extends ReplicatedData with ReplicatedDataSerialization { import LWWRegister.{ Clock, defaultClock } type T = LWWRegister[A] /** * Java API */ def getValue(): A = value /** * Change the value of the register. * * You can provide your `clock` implementation instead of using timestamps based * on `System.currentTimeMillis()` time. The timestamp can for example be an * increasing version number from a database record that is used for optimistic * concurrency control. */ def withValue(value: A)(implicit node: Cluster, clock: Clock[A] = defaultClock[A]): LWWRegister[A] = withValue(node, value, clock) /** * Change the value of the register. */ def withValue(node: Cluster, value: A): LWWRegister[A] = withValue(node, value, defaultClock[A]) /** * Change the value of the register. * * You can provide your `clock` implementation instead of using timestamps based * on `System.currentTimeMillis()` time. The timestamp can for example be an * increasing version number from a database record that is used for optimistic * concurrency control. */ def withValue(node: Cluster, value: A, clock: Clock[A]): LWWRegister[A] = withValue(node.selfUniqueAddress, value, clock) /** * The current `value` was set by this node. */ def updatedBy: UniqueAddress = node /** * INTERNAL API */ @InternalApi private[akka] def withValue(node: UniqueAddress, value: A, clock: Clock[A]): LWWRegister[A] = new LWWRegister(node, value, clock(timestamp, value)) override def merge(that: LWWRegister[A]): LWWRegister[A] = if (that.timestamp > this.timestamp) that else if (that.timestamp < this.timestamp) this else if (that.node < this.node) that else this // this class cannot be a `case class` because we need different `unapply` override def toString: String = s"LWWRegister($value)" override def equals(o: Any): Boolean = o match { case other: LWWRegister[_] ⇒ timestamp == other.timestamp && value == other.value && node == other.node case _ ⇒ false } override def hashCode: Int = { var result = HashCode.SEED result = HashCode.hash(result, timestamp) result = HashCode.hash(result, node) result = HashCode.hash(result, value) result } } object LWWRegisterKey { def create[A](id: String): Key[LWWRegister[A]] = LWWRegisterKey(id) } @SerialVersionUID(1L) final case class LWWRegisterKey[A](_id: String) extends Key[LWWRegister[A]](_id) with ReplicatedDataSerialization