better handle blocking IO and monitor it by name

pull/5761/head
Thibault Duplessis 2019-12-13 22:08:50 -06:00
parent cfa31551fc
commit e5880aba38
12 changed files with 54 additions and 35 deletions

View File

@ -51,7 +51,7 @@ object Environment
def isChatPanicEnabled = env.chat.panic.enabled
def blockingReportNbOpen: Int = env.report.api.nbOpen.awaitOrElse(10.millis, 0)
def blockingReportNbOpen: Int = env.report.api.nbOpen.awaitOrElse(10.millis, "nbReports", 0)
def NotForKids(f: => Frag)(implicit ctx: Context) = if (ctx.kid) emptyFrag else f

View File

@ -3,11 +3,12 @@ package lila.base
import akka.actor.ActorSystem
import scala.util.Try
import lila.common.Chronometer
import LilaTypes._
import ornicar.scalalib.Zero
import scala.collection.BuildFrom
import scala.concurrent.duration._
import scala.concurrent.{ ExecutionContext => EC, Future }
import scala.concurrent.{ ExecutionContext => EC, Future, Await, blocking }
final class PimpedFuture[A](private val fua: Fu[A]) extends AnyVal {
@ -104,30 +105,39 @@ final class PimpedFuture[A](private val fua: Fu[A]) extends AnyVal {
fua
}
def await(duration: FiniteDuration): A =
scala.concurrent.Await.result(fua, duration)
def await(duration: FiniteDuration, name: String): A =
Chronometer.syncMon(_.blocking.time(name)) {
if (duration.toMillis >= 100) blocking {
Await.result(fua, duration)
} else {
Await.result(fua, duration)
}
}
def awaitOrElse(duration: FiniteDuration, default: => A): A =
def awaitOrElse(duration: FiniteDuration, name: String, default: => A): A =
try {
scala.concurrent.Await.result(fua, duration)
await(duration, name)
} catch {
case _: Exception => default
}
def awaitSeconds(seconds: Int): A =
await(seconds.seconds)
def withTimeout(duration: FiniteDuration)(implicit ec: EC, system: ActorSystem): Fu[A] =
withTimeout(duration, LilaException(s"Future timed out after $duration"))
def withTimeout(duration: FiniteDuration, error: => Throwable)(implicit ec: EC, system: ActorSystem): Fu[A] = {
def withTimeout(
duration: FiniteDuration,
error: => Throwable
)(implicit ec: EC, system: ActorSystem): Fu[A] = {
Future firstCompletedOf Seq(
fua,
akka.pattern.after(duration, system.scheduler)(Future failed error)
)
}
def withTimeoutDefault(duration: FiniteDuration, default: => A)(implicit ec: EC, system: ActorSystem): Fu[A] = {
def withTimeoutDefault(
duration: FiniteDuration,
default: => A
)(implicit ec: EC, system: ActorSystem): Fu[A] = {
Future firstCompletedOf Seq(
fua,
akka.pattern.after(duration, system.scheduler)(Future(default))
@ -137,8 +147,8 @@ final class PimpedFuture[A](private val fua: Fu[A]) extends AnyVal {
def delay(duration: FiniteDuration)(implicit ec: EC, system: ActorSystem) =
lila.common.Future.delay(duration)(fua)
def chronometer = lila.common.Chronometer(fua)
def chronometerTry = lila.common.Chronometer.lapTry(fua)
def chronometer = Chronometer(fua)
def chronometerTry = Chronometer.lapTry(fua)
def mon(path: lila.mon.TimerPath) = chronometer.mon(path).result
def monTry(path: Try[A] => lila.mon.TimerPath) = chronometerTry.mon(r => path(r)(lila.mon)).result
@ -189,7 +199,7 @@ final class PimpedFutureOption[A](private val fua: Fu[Option[A]]) extends AnyVal
def getOrElse(other: => Fu[A])(implicit ec: EC): Fu[A] = fua flatMap { _.fold(other)(fuccess) }
def map2[B](f: A => B)(implicit ec: EC): Fu[Option[B]] = fua.map(_ map f)
def dmap2[B](f: A => B): Fu[Option[B]] = fua.map(_ map f)(EC.parasitic)
def dmap2[B](f: A => B): Fu[Option[B]] = fua.map(_ map f)(EC.parasitic)
}
// final class PimpedFutureValid[A](private val fua: Fu[Valid[A]]) extends AnyVal {

View File

@ -506,6 +506,9 @@ object mon {
object bus {
val classifiers = gauge("bus.classifiers").withoutTags
}
object blocking {
def time(name: String) = timer("blocking.time").withTag("name", name)
}
type TimerPath = lila.mon.type => Timer
type CounterPath = lila.mon.type => Counter

View File

@ -4,7 +4,7 @@ import com.github.ghik.silencer.silent
import reactivemongo.api._
import reactivemongo.api.commands.Command
import scala.concurrent.duration._
import scala.concurrent.{ Await, Future }
import scala.concurrent.Future
import dsl.Coll
import lila.common.Chronometer
@ -36,10 +36,10 @@ final class Db(
private val dbName = uri.db | "lichess"
private lazy val db: DefaultDB = Chronometer.syncEffect(
Await.result(
driver.connect(uri, name.some).flatMap(_ database dbName),
5.seconds
)
driver
.connect(uri, name.some)
.flatMap(_ database dbName)
.await(5.seconds, s"db:$name")
) { lap =>
logger.info(s"MongoDB connected to $dbName in ${lap.showDuration}")
}

View File

@ -2,6 +2,7 @@ package lila.hub
import akka.pattern.ask
import play.api.data._
import scala.concurrent.duration._
import actorApi.captcha._
import lila.common.Captcha
@ -28,7 +29,7 @@ trait CaptchedForm {
import scala.language.reflectiveCalls
def validateCaptcha(data: CaptchedData) =
getCaptcha(data.gameId) awaitSeconds 2 valid data.move.trim.toLowerCase
getCaptcha(data.gameId).await(2 seconds, "getCaptcha") valid data.move.trim.toLowerCase
def captchaFailMessage = Captcha.failMessage
}

View File

@ -108,7 +108,7 @@ final class Syncache[K, V](
private def waitForResult(k: K, fu: Fu[V], duration: FiniteDuration): V = {
try {
lila.common.Chronometer.syncMon(_ => recWait) {
fu await duration
fu.await(duration, "syncache")
}
} catch {
case _: java.util.concurrent.TimeoutException =>

View File

@ -2,6 +2,7 @@ package lila.message
import play.api.data._
import play.api.data.Forms._
import scala.concurrent.duration._
import lila.security.Granter
import lila.user.{ User, UserRepo }
@ -17,11 +18,11 @@ final private[message] class DataForm(
Form(
mapping(
"username" -> lila.user.DataForm.historicalUsernameField
.verifying("Unknown username", { fetchUser(_).isDefined })
.verifying("Unknown username", { blockingFetchUser(_).isDefined })
.verifying(
"Sorry, this player doesn't accept new messages", { name =>
Granter(_.MessageAnyone)(me) || {
security.canMessage(me.id, User normalize name) awaitSeconds 2 // damn you blocking API
security.canMessage(me.id, User normalize name).await(2 seconds, "pmAccept") // damn you blocking API
}
}
),
@ -31,7 +32,7 @@ final private[message] class DataForm(
)({
case (username, subject, text, mod) =>
ThreadData(
user = fetchUser(username) err "Unknown username " + username,
user = blockingFetchUser(username) err "Unknown username " + username,
subject = subject,
text = text,
asMod = mod.isDefined
@ -46,7 +47,7 @@ final private[message] class DataForm(
)
)
private def fetchUser(username: String) = userRepo named username awaitSeconds 2
private def blockingFetchUser(username: String) = userRepo.named(username).await(1 second, "pmUser")
}
object DataForm {

View File

@ -3,6 +3,7 @@ package lila.report
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation._
import scala.concurrent.duration._
import lila.user.{ User, UserRepo }
@ -21,7 +22,7 @@ final private[report] class DataForm(
val create = Form(
mapping(
"username" -> lila.user.DataForm.historicalUsernameField.verifying("Unknown username", {
fetchUser(_).isDefined
blockingFetchUser(_).isDefined
}),
"reason" -> text.verifying("error.required", Reason.keys contains _),
"text" -> text(minLength = 5, maxLength = 2000),
@ -30,7 +31,7 @@ final private[report] class DataForm(
)({
case (username, reason, text, gameId, move) =>
ReportSetup(
user = fetchUser(username) err "Unknown username " + username,
user = blockingFetchUser(username) err "Unknown username " + username,
reason = reason,
text = text,
gameId = gameId,
@ -49,7 +50,8 @@ final private[report] class DataForm(
)(ReportFlag.apply)(ReportFlag.unapply)
)
private def fetchUser(username: String) = userRepo named username awaitSeconds 2
private def blockingFetchUser(username: String) =
userRepo.named(username).await(1.second, "reportUser")
}
private[report] case class ReportFlag(

View File

@ -3,6 +3,7 @@ package lila.security
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints
import scala.concurrent.duration._
import lila.common.{ EmailAddress, LameName }
import lila.user.{ TotpSecret, User, UserRepo }
@ -14,7 +15,8 @@ final class DataForm(
authenticator: lila.user.Authenticator,
emailValidator: EmailAddressValidator,
lameNameCheck: LameNameCheck
)(implicit ec: scala.concurrent.ExecutionContext) extends lila.hub.CaptchedForm {
)(implicit ec: scala.concurrent.ExecutionContext)
extends lila.hub.CaptchedForm {
import DataForm._
@ -67,7 +69,7 @@ final class DataForm(
)
)
.verifying("usernameUnacceptable", u => !lameNameCheck.value || !LameName.username(u))
.verifying("usernameAlreadyUsed", u => !userRepo.nameExists(u).awaitSeconds(4))
.verifying("usernameAlreadyUsed", u => !userRepo.nameExists(u).await(3 seconds, "signupUsername"))
private val agreementBool = boolean.verifying(b => b)

View File

@ -46,7 +46,7 @@ final class EmailAddressValidator(
def uniqueConstraint(forUser: Option[User]) = Constraint[String]("constraint.email_unique") { e =>
val email = EmailAddress(e)
val (taken, reused) = (isTakenBySomeoneElse(email, forUser) zip wasUsedTwiceRecently(email)) awaitSeconds 2
val (taken, reused) = (isTakenBySomeoneElse(email, forUser) zip wasUsedTwiceRecently(email)).await(2 seconds, "emailUnique")
if (taken || reused) Invalid(ValidationError("error.email_unique"))
else Valid
}
@ -73,7 +73,7 @@ final class EmailAddressValidator(
// the DNS emails should have been preloaded
private[security] val withAcceptableDns = Constraint[String]("constraint.email_acceptable") { e =>
val ok = hasAcceptableDns(EmailAddress(e)).awaitOrElse(100.millis, {
val ok = hasAcceptableDns(EmailAddress(e)).awaitOrElse(90.millis, "dns", {
logger.warn(s"EmailAddressValidator.withAcceptableDns timeout! ${e} records should have been preloaded")
true
})

View File

@ -5,7 +5,6 @@ import akka.stream.scaladsl._
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import play.api.libs.json._
import scala.concurrent.Await
import scala.concurrent.duration._
import chess.format.pgn.Tag
@ -110,7 +109,7 @@ final class StudySearchApi(
date
case _ =>
logger.info("Reset study index")
Await.result(c.putMapping, 20 seconds)
c.putMapping.await(10.seconds, "studyMapping")
parseDate("2011-01-01").get
}
logger.info(s"Index to ${c.index.name} since $since")

View File

@ -2,6 +2,7 @@ package lila.team
import play.api.data._
import play.api.data.Forms._
import scala.concurrent.duration._
import lila.db.dsl._
@ -28,7 +29,7 @@ final private[team] class DataForm(
Fields.gameId,
Fields.move
)(TeamSetup.apply)(TeamSetup.unapply)
.verifying("This team already exists", d => !teamExists(d).awaitSeconds(2))
.verifying("This team already exists", d => !teamExists(d).await(2 seconds, "teamExists"))
.verifying(captchaFailMessage, validateCaptcha _)
)