rossabaker on main
pre scalafix cleanup Pick low-hanging deprecated fru… Remove unused import and 56 more (compare)
rossabaker on 0.22
Graceful shutdowns for ember-se… Use SignallingRef to signal ser… Scalafmt and 22 more (compare)
rossabaker on main
remove blocking unsafeRunSync i… do not cancel Merge pull request #4194 from y… (compare)
rossabaker on 0.21
rewrite AuthenticationSpec with… (compare)
rossabaker on 0.22
Pick low-hanging deprecated fru… Remove unused import Stop publishing http4s-testing and 1 more (compare)
rossabaker on main
use a different EC for munit T… Merge pull request #4195 from y… (compare)
rossabaker on 0.21
Backport munit ports and fix re… fix fix again and 1 more (compare)
rossabaker on main
use blocking where necessary to… Merge pull request #4197 from y… (compare)
Kleisli
shared
, I can add more details if you need.
fine
. I mean, I was just trying to make it work so I am not sure if I am doing something horrible (like never use IO.never or do it like this because of the reasons etc). I just wanted to get quick feedback. Thanks in advance!object CarAdvertServer extends IOApp {
override def run(args: List[String]): IO[ExitCode] = new HttpServer[IO].server().use(_ => IO.never).as(ExitCode.Success)
}
class HttpServer[F[_]: ConcurrentEffect: ContextShift: Timer] {
implicit val runner: Sync[F] = Sync[F]
private val dbConfig: DatabaseConfig = ???
def server(): Resource[F, Server[F]] =
for {
connEc <- ExecutionContexts.fixedThreadPool[F](dbConfig.connections.poolSize)
txnEc <- ExecutionContexts.cachedThreadPool[F]
flywayDatabaseMigrator = FlywayDatabaseMigrator[F]
xa <- flywayDatabaseMigrator.dbTransactor(dbConfig, connEc, Blocker.liftExecutionContext(txnEc))
carAdvertRepository = PostgresCarAdvertRepository[F](xa)
carAdvertService = CarAdvertService[F](carAdvertRepository)
carAdvertRoutes = {
implicit val httpErrorHandler: HttpErrorHandler[F] = new HttpErrorHandler[F]
CarAdvertHttpRoutes[F](carAdvertService)
}
_ <- Resource.liftF(flywayDatabaseMigrator.migrate(xa))
server <- BlazeServerBuilder[F]
.bindHttp(9000, "0.0.0.0")
.withHttpApp(carAdvertRoutes.routes.orNotFound)
.resource
} yield server
}
Timeout
middleware given some HttpRoutes
. This should return a Kleisli
. Then I want to go from this Kleisli
back to HttpRoutes
to pass to function f
. I understand HttpRoutes
is also a Kleisli
but it takes an OptionT
which makes it hard to make the transformation. @Daenyth mentioned yesterday it would be easy to operate on HttpApp
but I didn't quite get it. Any help please?import org.http4s.server.middleware.Timeout
def f(routes: HttpRoutes[IO]) = ???
def g(routes: HttpRoutes[IO]) = {
val timeout = Timeout(0.milliseconds, ServiceUnavailable("time out"))(<how to pass routes here>)
f(<how to go from timeout Kleisli back to HttpRoutes>)
}
seeing response times go through the roof under load without any real CPU usage at all.
@coltfred if you're not doing computation, that would indicate you're doing IO, so some possibilities are
You'll need to monitor your app to understand what the case is. That being said, your described setup of fixed thread pool for acquiring connections, cached thread pool for db work, a good connection pool (Hikari) and passing the "global/computation" thread pool to blaze is a standard setup which works. Blaze will not perform blocking IO on that pool
using 10 db connections
That's usually calculated depending on the number of cores and type of storage of the database server and also it's configuration, but in practice this number is fine (it's not outrageously high even for a smallish db server) (I tend to use 4, same magnitude)
Hey all, the way I log trace log as follows at the moment.
private val logger: Logger = LoggerFactory.getLogger(this.getClass)
case GET -> Root / username =>
for {
user <- userService.findUser(UserName(username))
_ <- Sync[F].delay(logger.trace("Retrieving user [{}]", username))
response <- user.fold(H.handle, x => Ok(x.asJson))
} yield response
Is there a better way to do it (like more functional?)
@coltfred
I'm seeing response times go through the roof under load
Also I guess you need to define what "under load" means. For example, when I've done load tests against a DB-backed service, at some point I'll see response times start to grow exponentially, but that usually just shows that the load I'm exercising in that test is unrealistic.
I.e. if you're hitting a db-backed service continuously with 100000 concurrent actors without any backoff and your database has 2 physical cores, yes, you are going to see response times even higher that a well-configured http server response timeout, but that doesn't really surprise or say anything
mapK(OptionT.liftK))
is how to go from the Timeout
middleware to HttpRoutes
. It took me a good day of asking around to figure this out. So far my experience with http4s shows that without a very good understanding of all the cats abstractions it is pretty hard to use.
Timeout
is polymorphic in its Request
and Response
effect types, like all of http4s. OptionT[F, ?]
is one effect type you can use, which is how HttpRoutes
is defined.
Timeout
which in my case is Kleisli[IO, Request[IO], Response[IO]]
to a function that expects HttpRoutes[IO]
which is a Kleisli[OptionT[IO, ?], Request[IO], Response[IO]]
. It requires a natural transformation unless there is a simpler way.
HttpRoutes
in Timeout
.
HttpRoutes
after.
object Ex {
import cats.implicits._
import cats.effect._
import org.http4s._
import org.http4s.server.middleware._
def a[F[_]]: HttpRoutes[F] = ???
def b[F[_]: Concurrent: Timer]: HttpRoutes[F] = Timeout(4.seconds)(a[F])
}
@SystemFw my code looks like so
override def serverResource(routes: HttpRoutes[IO]): Resource[IO, Server[IO]] = {
val errorResponse = ErrorResponse(List(ErrorResponse.Error("Response timed out")))
super.serverResource(
Timeout(0.milliseconds, ServiceUnavailable(errorResponse.asJson))(routes.orNotFound)
.mapK(OptionT.liftK))
}
I am passing the output of Timeout
to serverResource
which takes HttpRoutes[IO]
. Also I'm using the Timeout
apply
method that takes a custom response. Not sure if this affects type inference but it does not accept routes
so I use routes.orNotFound
.
-Ypartial-unification
in scalacOptions
.
-Ypartial-unification