-
Notifications
You must be signed in to change notification settings - Fork 10.9k
ServiceExplained
The Guava Service
interface represents an object with an operational state,
with methods to start and stop. For example, webservers, RPC servers, and timers
can implement the Service
interface. Managing the state of services like
these, which require proper startup and shutdown management, can be nontrivial,
especially if multiple threads or scheduling is involved. Guava provides some
skeletons to manage the state logic and synchronization details for you.
The normal lifecycle of a Service
is
-
Service.State.NEW
to -
Service.State.STARTING
to -
Service.State.RUNNING
to -
Service.State.STOPPING
to Service.State.TERMINATED
A stopped service may not be restarted. If the service fails where starting,
running, or stopping, it goes into state Service.State.FAILED
.
A service can be started asynchronously using startAsync()
if the service
is NEW
. So you should structure your application to have a unique place
where each service is started.
Stopping the service is analogous, using the asynchronous stopAsync()
method. But unlike startAsync()
, it is safe to call this method multiple
times. This is to make it possible to handle races that may occur when shutting
down services.
Service also provides several methods to wait for service transitions to complete.
-
asynchronously using
addListener()
.addListener()
allows you to add aService.Listener
that will be invoked on every state transition of the service. N.B. if a service is notNEW
when the listener is added, then any state transitions that have already occurred will not be replayed on the listener. -
synchronously using
awaitRunning()
. This is uninterruptible, throws no checked exceptions, and returns once the service has finished starting. If the service fails to start, this throws anIllegalStateException
. Similarly,awaitTerminated()
waits for the service to reach a terminal state (TERMINATED
orFAILED
). Both methods also have overloads that allow timeouts to be specified.
The Service
interface is subtle and complicated. We do not recommend
implementing it directly. Instead please use one of the abstract base classes
in guava as the base for your implementation. Each base class supports a
specific threading model.
The AbstractIdleService
skeleton implements a Service
which does not need
to perform any action while in the "running" state -- and therefore does not
need a thread while running -- but has startup and shutdown actions to perform.
Implementing such a service is as easy as extending AbstractIdleService
and
implementing the startUp()
and shutDown()
methods.
protected void startUp() {
servlets.add(new GcStatsServlet());
}
protected void shutDown() {}
Note that any queries to the GcStatsServlet
already have a thread to run in.
We don't need this service to perform any operations on its own while the
service is running.
An AbstractExecutionThreadService
performs startup, running, and shutdown
actions in a single thread. You must override the run()
method, and it must
respond to stop requests. For example, you might perform actions in a work loop:
public void run() {
while (isRunning()) {
// perform a unit of work
}
}
Alternately, you may override in any way that causes run()
to return.
Overriding startUp()
and shutDown()
is optional, but the service state will
be managed for you.
protected void startUp() {
dispatcher.listenForConnections(port, queue);
}
protected void run() {
Connection connection;
while ((connection = queue.take() != POISON)) {
process(connection);
}
}
protected void triggerShutdown() {
dispatcher.stopListeningForConnections(queue);
queue.put(POISON);
}
Note that start()
calls your startUp()
method, creates a thread for you, and
invokes run()
in that thread. stop()
calls triggerShutdown()
and waits for
the thread to die.
An AbstractScheduledService
performs some periodic task while running.
Subclasses implement runOneIteration()
to specify one iteration of the task,
as well as the familiar startUp
and shutDown()
methods.
To describe the execution schedule, you must implement the scheduler()
method. Typically, you will use one of the provided schedules from
AbstractScheduledService.Scheduler
, either
newFixedRateSchedule(initialDelay, delay, TimeUnit)
or
newFixedDelaySchedule(initialDelay, delay, TimeUnit)
, corresponding to the
familiar methods in ScheduledExecutorService
. Custom schedules can be
implemented using CustomScheduler
; see the Javadoc for details.
When you need to do your own manual thread management, override
AbstractService
directly. Typically, you should be well served by one of the
above implementations, but implementing AbstractService
is recommended when,
for example, you are modeling something that provides its own threading
semantics as a Service
, you have your own specific threading requirements.
To implement AbstractService
you must implement 2 methods.
-
doStart()
:doStart()
is called directly by the first call tostartAsync()
, yourdoStart()
method should perform all initialization and then eventually callnotifyStarted()
if start up succeeded ornotifyFailed()
if start up failed. -
doStop()
:doStop()
is called directly by the first call tostopAsync()
, yourdoStop()
method should shut down your service and then eventually callnotifyStopped()
if shutdown succeeded ornotifyFailed()
if shutdown failed.
Your doStart
and doStop
, methods should be fast. If you need to do
expensive initialization, such as reading files, opening network connections, or
any operation that might block, you should consider moving that work to another
thread.
In addition to the Service
skeleton implementations, Guava provides a
ServiceManager
class that makes certain operations involving multiple
Service
implementations easier. Create a new ServiceManager
with a
collection of Services
. Then you can manage them:
-
startAsync()
will start all the services under management. Much likeService#startAsync()
you can only call this method once, if all services areNEW
. -
stopAsync()
will stop all the services under management. -
addListener
will add aServiceManager.Listener
that will be called on major state transitions. -
awaitHealthy()
will wait for all services to reach theRUNNING
state. -
awaitStopped()
will wait for all services to reach a terminal state.
Or inspect them:
-
isHealthy()
returnstrue
if all services areRUNNING
. -
servicesByState()
returns a consistent snapshot of all the services indexed by their state. -
startupTimes()
returns a map fromService
under management to how long it took for that service to start in milliseconds. The returned map is guaranteed to be ordered by startup time.
While it is recommended that service lifecycles be managed via ServiceManager
,
state transitions initiated via other mechanisms do not impact the
correctness of its methods. For example, if the services are started by some
mechanism besides startAsync()
, the listeners will be invoked when appropriate
and awaitHealthy()
will still work as expected. The only requirement that
ServiceManager
enforces is that all Services
must be NEW
when
ServiceManager
is constructed.
- Introduction
- Basic Utilities
- Collections
- Graphs
- Caches
- Functional Idioms
- Concurrency
- Strings
- Networking
- Primitives
- Ranges
- I/O
- Hashing
- EventBus
- Math
- Reflection
- Releases
- Tips
- Glossary
- Mailing List
- Stack Overflow
- Android Overview
- Footprint of JDK/Guava data structures