SearchExecutionContext

com.worxbend.ferrite.search.SearchExecutionContext
See theSearchExecutionContext companion object
@Singleton
final class SearchExecutionContext(system: ActorSystem) extends CustomExecutionContext

The dispatcher every blocking JDBC call in ferrite runs on.

Why not the default dispatcher. Play's default dispatcher is a fork-join pool sized to the number of cores and shared with request parsing, the Pekko HTTP server's own work and every non-blocking map in the application. A blocking getConnection/executeQuery parked on it does not just occupy a thread, it removes a thread from the pool that is supposed to be accepting requests — so a slow database stops the server answering /health, and the symptom is a failing liveness probe rather than a slow search. Isolating the blocking work means a database stall degrades exactly one feature.

Why not a virtual-thread executor. ADR §0, decision 8 settles this: an unbounded executor over virtual threads does not remove the queue, it relocates it from HikariCP — where it has a visible depth, a connectionTimeout and therefore a load-shedding point — into an invisible one with neither. JEP 491 makes virtual threads safe for blocking JDBC on JDK 25; it does not make them preferable where the bound is the resource you are protecting.

Why the size must equal maximumPoolSize. More threads than connections means the surplus threads block inside getConnection and the queue silently moves from the dispatcher (where it is configured and observable) to the pool (where waiting shows up as a connectionTimeout exception, i.e. an error rather than backpressure). Fewer threads than connections means connections that can never be used. database.search-dispatcher.thread-pool-executor .fixed-pool-size in application.conf is therefore pinned to database.read.maximum-pool-size, and both are 8; DatabaseConfigSuite in the persistence module owns the pool half of that pair.

ferrite reads only (ADR §1) and never runs a migration, so there is exactly one of these — a write dispatcher would be a pool of threads for work this service does not do.

Attributes

Companion
object
Graph
Supertypes
class CustomExecutionContext
trait ExecutionContextExecutor
trait Executor
trait ExecutionContext
class Object
trait Matchable
class Any
Show all

Members list

Value members

Inherited methods

override def execute(command: Runnable): Unit

Runs a block of code on this execution context.

Runs a block of code on this execution context.

Value parameters

runnable

the task to execute

Attributes

Definition Classes
CustomExecutionContext -> Executor -> ExecutionContext
Inherited from:
CustomExecutionContext
override def reportFailure(cause: Throwable): Unit

Reports that an asynchronous computation failed.

Reports that an asynchronous computation failed.

Value parameters

cause

the cause of the failure

Attributes

Definition Classes
CustomExecutionContext -> ExecutionContext
Inherited from:
CustomExecutionContext

Deprecated and Inherited methods

def prepare(): ExecutionContext

Prepares for the execution of a task. Returns the prepared execution context. The recommended implementation of prepare is to return this.

Prepares for the execution of a task. Returns the prepared execution context. The recommended implementation of prepare is to return this.

This method should no longer be overridden or called. It was originally expected that prepare would be called by all libraries that consume ExecutionContexts, in order to capture thread local context. However, this usage has proven difficult to implement in practice and instead it is now better to avoid using prepare entirely.

Instead, if an ExecutionContext needs to capture thread local context, it should capture that context when it is constructed, so that it doesn't need any additional preparation later.

Attributes

Deprecated
[Since version 2.12.0] preparation of ExecutionContexts will be removed
Inherited from:
ExecutionContext