How do I cause a WARP server to terminate?
Asked Answered
S

1

5

I have an HTTP application server that needs to exit when handling a certain request under certain conditions (in order to be restarted by a supervisor).

Given a main like:

import Network.Wai.Handler.Warp (run)

main :: IO ()
main = do
  config <- readConfig
  run (portNumber config) (makeApp config)

and a handler something like:

livenessServer1 :: UTCTime -> FilePath -> Server LivenessProbeAPI1
livenessServer1 initialModificationTime monitorPath = do
  mtime <- liftIO $ getModificationTime monitorPath
  case mtime == initialModificationTime of
    True  -> return $ Liveness initialModificationTime mtime
    False -> throwError $ err500 { errBody = "File modified." }

How do I cause the process to end after delivering the 500 response?

Sopping answered 23/8, 2017 at 12:38 Comment(1)
Use something like error "Oops!" to throw an error, as by default Warp doesnt handle errors.Trunk
A
11

I'm on my phone right now, so I can't type exact code for you. But the basic idea is to throw your Warp thread an async exception. That may sound complicated, but the easiest way to approach it is to use the race function from the async library. Something like this:

toExitVar <- newEmptyMVar
race warp (takeMVar toExitVar)

And then in your handler, when you want Warp to exit:

putMVar toExitVar ()

EDIT A day later and I'm back at my computer, here's a fully worked example:

#!/usr/bin/env stack
-- stack --resolver lts-9.0 script
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types
import Control.Concurrent.Async
import Control.Concurrent.MVar

main :: IO ()
main = do
toDie <- newEmptyMVar
race_ (takeMVar toDie) $ run 3000 $ \req send ->
    if pathInfo req == ["die"]
    then do
        putMVar toDie ()
        send $ responseLBS status200 [] "Goodbye!"
    else send $ responseLBS status200 [] "Still alive!"
Ambala answered 23/8, 2017 at 17:46 Comment(6)
Thanks. This seems interesting but my Haskell abilities are too limited to actually understand what it means or how to integrate it into my app.Sopping
I've added a full example now that I'm back at my computer.Ambala
What happens if race_ kills the warp thread before it gets to send? Does warp guarantee that the response will still be send in that case?Gert
No, there are no such guarantees. If you want graceful shutdown, you can create the listening socket yourself and then close it, but you'll still need to figure out when it's the right time to exit the process.Ambala
How do you figure out when the right time is? Clearly there are no absolutely guarantees possible that the peer has received the data sent - but it is at least possible to wait for the local send buffer to empty. Does Wai/Warp expose that information?Sopping
No it doesn't. We could theoretically add some kind of bookkeeping for all of the open worker threads, but this has never been a case people have asked to be addressed. Instead, it's common to either leave the server running indefinitely (waiting for machine failure or a new deployment), or just let any in-flight requests to die and let the client resubmit.Ambala

© 2022 - 2024 — McMap. All rights reserved.