Some practical Haskell
As I'm nearing the end of my time with my current employer I thought I'd put together some bits of practical Haskell that I've put into production. We only have a few services in Haskell, and basically I've had to sneak them into production. I'm hoping someone will find something useful. I'd be even happier if I get pointers on how to do this even better.
I've written about that earlier in three posts:
Final exception handler
After reading about the uncaught exception handler in Serokell's article I've added the following snippet to all the services.
main :: IO () main = do ... originalHandler <- getUncaughtExceptionHandler setUncaughtExceptionHandler $ handle originalHandler . lastExceptionHandler logger ... lastExceptionHandler :: Logger -> SomeException -> IO () lastExceptionHandler logger e = do fatalIO logger $ lm $ "uncaught exception: " <> displayException e
To make sure the platform we're running our services on is happy with a service
it needs to handle
SIGTERM, and when running it locally during development,
e.g. for manual testing, it's nice if it also handles
The following snippet comes from a service that needs to make sure that every
iteration of its processing is completed before shutting down, hence the
that's used to signal whether procession should continue or not.
main :: IO () main = do ... cont <- newIORef True void $ installHandler softwareTermination (Catch $ sigHandler logger cont) Nothing void $ installHandler keyboardSignal (Catch $ sigHandler logger cont) Nothing ... sigHandler :: Logger -> IORef Bool -> IO () sigHandler logger cont = do infoIO logger "got a signal, shutting down" writeIORef cont False
Due to some details about how networking works in our platform it's currently not possible to use network-based probing. Instead we have to use files. There are two probes that are of interest
- A startup probe, existance of the file signals that the service has started as is about being processing.
- A progress probe, a timestamp signals the time the most recent iteration of processing finished1.
I've written a little bit about the latter before in A little Haskell: epoch timestamp, but here I'm including both functions.
createPidFile :: FilePath -> IO () createPidFile fn = getProcessID >>= writeFile fn . show writeTimestampFile :: MonadIO m => FilePath -> m () writeTimestampFile fn = liftIO $ do getPOSIXTime >>= (writeFile fn . show) . truncate @_ @Int64 . (* 1000)
The actual probing is then done using a command that compares the saved timestamp with the current time. As long as the difference is smaller than a threshold the probe succeeds.