Creating an injectable TwitterServer¶
Lifecycle¶
If you haven’t already, take a look at the TwitterServer
lifecycle documentation.
You may also want to look at the util-app App
lifecycle documentation as well since
c.t.server.TwitterServer
is an extension of util-app App and thus inherits the util-app App
lifecycle.
Getting Started¶
To create an injectable c.t.server.TwitterServer
, first depend on the inject-server library. We
also recommend using Logback as your
SLF4J implementation. E.g.,
with sbt:
"com.twitter" %% "inject-server" % "24.2.0", "ch.qos.logback" % "logback-classic" % versions.logback,
For more information on logging with Finatra see: Introduction to Logging With Finatra.
Create a new class which extends c.t.inject.server.TwitterServer
:
Basic Example¶
An example of a simple injectable TwitterServer:
import com.twitter.inject.server.TwitterServer
object MyServerMain extends MyServer
class MyServer extends TwitterServer {
override protected def start(): Unit = {
// It is important to remember to NOT BLOCK this method.
...
}
}
This will use the inject framework to create an
injectable TwitterServer. Finatra provides c.t.inject.server.TwitterServer
as an injectable
version of the TwitterServer
c.t.server.TwitterServer
trait.
Specifically, this allows for the use of dependency injection in a TwitterServer with support for Modules allowing for powerful feature testing of the server.
Advanced Example¶
import com.twitter.inject.server.TwitterServer
object MyTwitterServerMain extends MyTwitterServer
class MyTwitterServer
extends TwitterServer {
override val modules = Seq(
MyModule1)
override protected def setup(): Unit = {
// Create/start a pub-sub component and add it to the list of Awaitables, e.g., await(component)
// It is important to remember to NOT BLOCK this method
...
}
override protected def warmup(): Unit = {
handle[MyWarmupHandler]()
}
@Lifecycle
override protected def postWarmup(): Unit = {
super.postWarmup()
// It is important to remember to NOT BLOCK this method and to call SUPER.
...
}
override protected def start(): Unit = {
// It is important to remember to NOT BLOCK this method
...
}
}
Overriding Server Lifecycle Functions¶
You can hook into the server startup lifecycle by overriding @Lifecycle
-annotated methods or
provided lifecycle callbacks.
@Lifecycle
-annotated Methods¶
postInjectorStartup - after creation and initialization of the Injector.
beforePostWarmup - this phase performs object promotion before the binding of any external interface (which typically means the server will start accepting traffic) occurs in postWarmup
postWarmup - the framework binds the external HTTP interface.
afterPostWarmup - signal the server is healthy, via Lifecycle.Warmup.warmupComplete.
Caution
When overriding a server @Lifecycle
-annotated method you MUST first call
super.lifecycleMethod() in your overridden implementation to ensure that the server correctly
completes the startup process.
Lifecycle Callbacks¶
setup() - called at the end of the postInjectorStartup() phase.
warmup() - allows for user-defined server warmup.
start() - called at the end of the server main before awaiting on any Awaitables.
The main points of entry to the c.t.inject.server.TwitterServer
are the lifecycle callbacks:
#setup
, #warmup
, and #start
.
TwitterServer#setup¶
The #setup() lifecycle callback method is executed at the end of the
TwitterServer#postInjectorStartup @Lifecycle
-annotated method
(see: Startup Lifecycle). That is, after the creation
of the Injector but before server warmup has been performed allowing for anything created or started
in this callback to be used in warmup and for instances to be promoted to old gen during object
promotion in the beforePostWarmup lifecycle phase.
Note: in the HttpServer
and ThriftServer
traits from finatra-http and finatra-thrift
respectively, routing is configured in the postInjectorStartup lifecycle phase. However any logic
in the #setup callback will executed after all installed modules have started (see:
TwitterModule Lifecycle) and before
HttpRouter or ThriftRouter configuration.
What Goes Here?¶
Any logic to execute before object promotion and before the server warmup is performed. This is thus before any external interface has been bound and thus before the server is announced as “healthy”.
Any exception thrown from this method will fail the server startup.
When overriding any lifecycle methods and callbacks, it is important to not perform any blocking operations as you will prevent the server from properly starting. If there is blocking work that must be done, it is strongly recommended that you perform this work in a FuturePool.
See the Finatra utility: c.t.finatra.utils.FuturePools
for creating named pools.
TwitterServer#warmup¶
For detailed information see HTTP Server Warmup, or Thrift Server Warmup.
TwitterServer#start¶
Any logic to be run after the server is reported as healthy, bound to an external interface, and before awaiting on any Awaitables is placed in the #start() method. This is typically starting long live background processes, starting any processor that should only be started once the external interface has been successfully bound to port and is accepting traffic, or any other work that must be completed as part of server startup. See the Awaiting Awaitables section for more information.
What Goes Here?¶
Work to happen after the server is bound to any external port, has performed warmup, object promotion, and is announced as “healthy”.
Any exception thrown from this method will fail the server startup.
When overriding any lifecycle methods and callbacks, it is important to not perform any blocking operations in your override as you will prevent the server from properly starting. If there is blocking work that must be done, it is strongly recommended that you perform this work in a FuturePool.
See the Finatra utility: c.t.finatra.utils.FuturePools
for creating named pools.
Awaiting Awaitables¶
If you have long-lived processes which your server starts that you want to ensure exit when the server
exits or trigger the server to exit if the process exits, you should register them as an c.t.util.Awaitable
using the c.t.inject.server.TwitterServer#await
callback function.
The purpose of using this callback is to entangle all the Awaitables within your server such that if any of the Awaitables exit, the entire server process exits. For example, when starting a regular HTTP or Thrift server, you have two ListeningServers in process: the TwitterServer HTTP Admin Interface and the started external server. If you await (block) on one of the servers and not the other, you can get into a case where the server not being awaited exits but the process continues to wait on the other server to satisfy the blocking Awaitable and thus does not exit.
Why is this bad?¶
As an example, if you await on just the external interface, the TwitterServer HTTP Admin Interface may exit because of an error, causing health checking and metrics reporting to fail but your server process would remain running until killed. Conversely, if you await on just the HTTP Admin Interface the external server may exit but the admin interface continues to report itself as healthy.
Register Awaitables¶
The way to ensure that the exiting of a single Awaitable triggers exiting of any other Awaitable
is to register each Awaitable with the server using the c.t.inject.server.TwitterServer#await
callback function. As the last step of the server startup lifecycle, the server will entangle
all given Awaitables.
Testing¶
For details see the Testing with Finatra section and the Finatra examples for detailed examples with tests.
More Information¶
For more information on the server lifecycle see the Application and Server Lifecycle section which contains details around the order of lifecycle events during startup and considerations during shutdown.