1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
# Trapperkeeper's Built-in Shutdown Service
The shutdown service is built-in to Trapperkeeper and, like the [configuration service](Built-in-Configuration-Service.md), is always loaded. It has two main responsibilities:
* Listen for a shutdown signal to the process, and initiate shutdown of the application if one is received (via CTRL-C or TERM signal)
* Provide functions that can be used by other services to initiate a shutdown (either because of a normal application termination condition, or in the event of a fatal error)
## Shutdown Hooks
A service may implement the `stop` function from the `Lifecycle` protocol. If so, this function will be called during application shutdown. The shutdown hook for any given service is guaranteed to be called *before* the shutdown hook for any of the services that it depends on.
For example:
```clj
(defn bar-shutdown
[]
(log/info "bar-service shutting down!"))
(defservice bar-service
[[:FooService foo]]
;; service initialization code
(init [this context]
(log/info "bar-service initializing.")
context)
;; shutdown code
(stop [this context]
(bar-shutdown)
context))
```
Given this service definition, the `bar-shutdown` function would be called during shutdown of the Trapperkeeper container (during both a normal shutdown or an error shutdown). Because `bar-service` has a dependency on `foo-service`, Trapperkeeper would also guarantee that the `bar-shutdown` is called *prior to* the `stop` function for the `foo-service` (assuming `foo-service` provides one).
## Provided Shutdown Functions
The shutdown service provides two functions that can be injected into other services: `request-shutdown` and `shutdown-on-error`. Here's the protocol:
```clj
(defprotocol ShutdownService
(request-shutdown [this] "Asynchronously trigger normal shutdown")
(shutdown-on-error [this service-id f] [this service-id f on-error]
"Higher-order function to execute application logic and trigger shutdown in
the event of an exception"))
```
To use them, you may simply specify a dependency on them:
```clj
(defservice baz-service
[[:ShutdownService request-shutdown shutdown-on-error]]
;; ...
)
```
### `request-shutdown`
`request-shutdown` initiates a shutdown of the application container
which will, in turn, cause all registered shutdown hooks to be called.
It is asynchronous and will eventually cause the `run` function to
return.
It accepts an optional argument which can be used to provide a map
specifying a process exit status and final messages like this:
```clj
{:puppetlabs.trapperkepper.core/exit`
{:status 3
:messages [["Unexpected filesystem error ..." *err*]]}}
```
which will finally be thrown from `run` as an `ex-info` of `:kind`
`:puppetlabs.trapperkepper.core/exit` like this:
```clj
{:kind :puppetlabs.trapperkepper.core/exit`
:status 3
:messages [["Unexpected filesystem error ..." *err*]]}}
```
The `:messages` should include any desired newlines, and when relying
on `:puppetlabs.trapperkepper.core/main`, the `:messages` will be
printed and `exit` will be called with the given `:status`.
### `shutdown-on-error`
`shutdown-on-error` is a higher-order function that can be used as a wrapper around some logic in your services; its functionality is simple:
```clj
(try
; execute the given function
(catch Throwable t
; initiate Trapperkeeper's shutdown logic
```
This has two main use-cases:
* "worker" / background threads that your service may launch
* a section of code that needs to execute in a service function, in which any error is so problematic that the entire application should shut down
`shutdown-on-error` accepts either two or three arguments: `[service-id f]` or `[service-id f on-error-fn]`.
`service-id` is the id of your service; you can retrieve this via `(service-id this)` inside of any of your service function definitions.
`f` is a function containing whatever application logic you desire; this is the function that will be wrapped in `try/catch`. `on-error-fn` is an optional callback function that you can provide, which will be executed during error shutdown *if* an unhandled exception occurs during the execution of `f`. `on-error-fn` should take a single argument: `context`, which is the service context map (the same map that is used in the lifecycle functions).
Here's an example:
```clj
(defn my-work-fn
[]
;; do some work
(Thread/sleep 10000)
;; uh-oh! An unhandled exception!
(throw (IllegalStateException. "egads!")))
(defn my-error-cleanup-fn
[context]
(log/info "Something terrible happened! Foo: " (context :foo))
(log/info "Performing shutdown logic that should only happen on a fatal error."))
(defn my-normal-shutdown-fn
[]
(log/info "Performing normal shutdown logic."))
(defservice yet-another-service
[[:ShutdownService shutdown-on-error]]
(init [this context]
(assoc context
:worker-thread
(future (shutdown-on-error (service-id this) my-work-fn my-error-cleanup-fn))))
(stop [this context]
(my-normal-shutdown-fn)
context))
```
In this scenario, the application would run for 10 seconds, and then the fatal exception would be thrown. Trapperkeeper would then call `my-error-cleanup-fn`, and then attempt to call all of the normal shutdown hooks in the correct order (including `my-normal-shutdown-fn`).
|