persist round versioned events to ease deployments - fixes #570

This commit is contained in:
Thibault Duplessis 2015-06-12 11:57:25 +02:00
parent 4bafa0da99
commit b97e7807d5
5 changed files with 98 additions and 25 deletions

View file

@ -6,7 +6,6 @@ import actorApi.map._
import akka.actor._
import akka.pattern.{ ask, pipe }
import makeTimeout.short
import scalaz.Monoid
trait ActorMap extends Actor {

View file

@ -33,7 +33,6 @@ final class Env(
scheduler: lila.common.Scheduler) {
private val settings = new {
val MessageTtl = config duration "message.ttl"
val UidTimeout = config duration "uid.timeout"
val PlayerDisconnectTimeout = config duration "player.disconnect.timeout"
val PlayerRagequitTimeout = config duration "player.ragequit.timeout"
@ -48,10 +47,11 @@ final class Env(
val CasualOnly = config getBoolean "casual_only"
val ActiveTtl = config duration "active.ttl"
val CollectionNote = config getString "collection.note"
val CollectionHistory = config getString "collection.history"
}
import settings._
lazy val history = () => new History(ttl = MessageTtl)
lazy val eventHistory = History(db(CollectionHistory)) _
val roundMap = system.actorOf(Props(new lila.hub.ActorMap {
def mkActor(id: String) = new Round(
@ -80,7 +80,7 @@ final class Env(
Props(new lila.socket.SocketHubActor[Socket] {
def mkActor(id: String) = new Socket(
gameId = id,
history = history(),
history = eventHistory(id),
lightUser = lightUser,
uidTimeout = UidTimeout,
socketTimeout = SocketTimeout,

View file

@ -4,38 +4,74 @@ import scala.concurrent.duration.Duration
import actorApi._
import akka.actor._
import reactivemongo.bson._
import lila.db.Types.Coll
import lila.game.Event
import lila.socket.actorApi.GetVersion
private[round] final class History(ttl: Duration) {
/**
* NOT THREAD SAFE
* Designed for use within a sequential actor
*/
private[round] final class History(
load: Fu[VersionedEvents],
persist: VersionedEvents => Unit) {
private var version = 0
private val events = lila.memo.Builder.expiry[Int, VersionedEvent](ttl)
// private var version = 0
private var events: VersionedEvents = _
def getVersion = version
// TODO optimize
def getVersion: Int = {
waitForLoadedEvents
events.headOption.??(_.version)
}
// none if version asked is > to history version
// none if an event is missing (asked too old version)
def getEventsSince(v: Int): Option[List[VersionedEvent]] =
def getEventsSince(v: Int): Option[List[VersionedEvent]] = {
waitForLoadedEvents
val version = getVersion
if (v > version) None
else if (v == version) Some(Nil)
else {
val events = (v + 1 to version).toList flatMap get
(events.size == (version - v)) option events
}
def addEvents(xs: List[Event]) = xs map { e =>
version = version + 1
VersionedEvent(
version = version,
typ = e.typ,
data = e.data,
only = e.only,
owner = e.owner,
watcher = e.watcher,
troll = e.troll) ~ { events.put(version, _) }
else events.filter(_.version > v).some
}
private def get(v: Int): Option[VersionedEvent] = Option(events getIfPresent v)
def addEvents(xs: List[Event]): VersionedEvents = {
waitForLoadedEvents
val vevs = xs.foldLeft(List.empty[VersionedEvent] -> getVersion) {
case ((vevs, v), e) => (VersionedEvent(e, v + 1) :: vevs, v + 1)
}._1
events = (vevs ::: events) take History.size
vevs.reverse ~ persist
}
private def waitForLoadedEvents {
if (events == null) events = load.await.reverse
}
}
private[round] object History {
val size = 30
def apply(coll: Coll)(gameId: String): History = new History(
load = load(coll, gameId),
persist = persist(coll, gameId) _)
private def load(coll: Coll, gameId: String): Fu[VersionedEvents] =
coll.find(BSONDocument("_id" -> gameId)).one[BSONDocument].map {
_.flatMap(_.getAs[VersionedEvents]("e")) | Nil
}
private def persist(coll: Coll, gameId: String)(vevs: List[VersionedEvent]) {
coll.uncheckedUpdate(
BSONDocument("_id" -> gameId),
BSONDocument("$push" -> BSONDocument(
"e" -> BSONDocument(
"$each" -> vevs,
"$slice" -> -History.size))),
upsert = true
)
}
}

View file

@ -26,4 +26,40 @@ case class VersionedEvent(
else if (owner && m.watcher) false
else if (troll && !m.troll) false
else only.fold(true)(_ == m.color)
override def toString = s"Event $version $typ"
}
private[round] object VersionedEvent {
def apply(e: Event, v: Int): VersionedEvent = VersionedEvent(
version = v,
typ = e.typ,
data = e.data,
only = e.only,
owner = e.owner,
watcher = e.watcher,
troll = e.troll)
import lila.db.BSON
import reactivemongo.bson._
implicit val versionedEventHandler = new BSON[VersionedEvent] {
def reads(r: BSON.Reader) = VersionedEvent(
version = r int "v",
typ = r str "t",
data = r.strO("d").fold[JsValue](JsNull)(Json.parse),
only = r boolO "o" map Color.apply,
owner = r boolD "ow",
watcher = r boolD "w",
troll = r boolD "r")
def writes(w: BSON.Writer, o: VersionedEvent) = BSONDocument(
"v" -> o.version,
"t" -> o.typ,
"d" -> (o.data != JsNull).option(Json stringify o.data),
"o" -> o.only.map(_.white),
"ow" -> w.boolO(o.owner),
"w" -> w.boolO(o.watcher),
"t" -> w.boolO(o.troll))
}
}

View file

@ -6,6 +6,8 @@ import lila.socket.WithSocket
package object round extends PackageObject with WithPlay with WithSocket {
private[round] type Events = List[Event]
private[round] type VersionedEvents = List[VersionedEvent]
}
package round {