-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Revisiting gracefulClose with STM racing #589
Revisiting gracefulClose with STM racing #589
Conversation
@kazu-yamamoto There are stilla couple things I don't understand, you need to elaborate:
Where is this It is only shortly mentioned in your post, but I don't really understand what scenario/implementation that's referring to. Thus, I do not currently understand why we can't just do timeout lingerTimeout (forever (recv s))
Can you outline the (problematic) Approach 4 with sample code, so that I can understand it, and its problem? I also don't understand what's wrong with the current code (which is Approach 3). Is that explained somewhere? What's the current problem, is there an open issue I missed? The commit message should mention what the problem is that the commit solves. Thank you -- I'm eager to participate in this! |
Sorry for your confusion. This is about approach 2.
77ee187 is the commit to remove the final code of approach 4.
Unnecessary delay happens. For instance, if we delay 30 us but a packet arrives after 10us, we wait for unnecesary 20 us.
Thank you for the suggestion. I will. |
Yes, that I understand, but in the post it only says:
I don't understand which timeout lingerTimeout (forever (recvUntilEof s)) Why doesn't that work? |
Suppose we define:
We cannot use this function as follows:
|
Maybe we (in warp, and users in general) should not use the function that way anyway! Because then you get an uninterruptible cleanup action that runs for a long time. You keep pressing Ctrl+C and the program does not terminate. Shouldn't we write the function so that the second async exception immediately calls For example, inlining the definition of -- | The first async exception will initiate graceful closing,
-- a second one will close the socket (which may result in
-- TCP connection resets of the other side).
withInterruptibleGracefulClose :: (Socket -> IO a) -> IO a
withInterruptibleGracefulClose action = do
mask $ \restore -> do
s <- openSocket
let gracefulCloseOnExceptionClose = (gracefulClose s) `onException` (close s)
r <- restore (action s) `onException` (restore gracefulCloseOnExceptionClose)
_ <- restore gracefulCloseOnExceptionClose
return r That said, I do see the appeal of having a function that can be used as you say, with bracket (openSocket ...) gracefulClose $ \sock ... To make terminology clear, we would then have to declare that Interruptible functionsDocs of
And Applying
IdeaSo, could we use For example, could we do gracefulClose s tmout0 = sendRecvFIN `E.finally` close s
where
sendRecvFIN = do
-- Sending TCP FIN.
ex <- E.try $ shutdown s ShutdownSend
case ex of
Left (E.SomeException _) -> return ()
Right () -> do
-- Giving CPU time to other threads hoping that
-- FIN arrives meanwhile.
yield
-- Waiting TCP FIN.
-- Make the graceful-close waiting `interruptible`
-- even when `gracefulClose` is called in `mask`, e.g. as
--
-- bracket (openSocket ...) gracefulClose $ \sock ...
--
-- If the timeout part gets interrupted by an async exception,
-- the `finally` above will close the socket.
interruptible (timeout lingerTimeout recvUntilEof) (Note |
Your research is great! Sorry but it seems to me that I misunderstand The reason why I misunderstand is probably that I use import Control.Exception
-- import UnliftIO.Exception
import Control.Concurrent
import System.Timeout
main :: IO ()
main = bracket newEmptyMVar (timeout 3000000 . takeMVar) $ \_ -> return () |
Since |
The reason |
I have already removed the dependency for |
@kazu-yamamoto Not sure that's a good idea, see below:
@edsko That behaviour is arguably the correct thing to do in most cases, and the reason why using Most uses of This is quite nicely explained in https://tech.fpcomplete.com/haskell/tutorial/exceptions/#sync-vs-async
The example shows exactly the case discussed in this issue: "timeout + delay-on-interruption" In most application code, the additional delay should not happen; an async exception should reliably kill the entire thread. That should be the default; thus But @kazu-yamamoto Thus my recommendation is be to stick to |
As a heavy user of
That said, I would like to go with only |
I'm going to adopt the asynchronous-exception-less programming with |
I think any choice you make for |
I found no problems in my field testing. |
First of all, please read Implementing graceful-close in Haskell network library. This PR revisits "Approach 4: callbacks of the IO/Timer manager".
What we know so far:
timeout
cannot be used in the resource-release clause ofbracket
. This is because an asynchronous exception, which is used intimeout
, cannot be delivered in the clause.The approach 4 uses
MVar
. Even we usetryPutMVar
, the IO manager is sometime blocked. This results in so manyCLOSE_WAIT
s on Linux. This name is confusing but it appeared that they represent entries of the listen queue, not established connections waiting close on Linux. Anyway, it is a clear evidence of the blocked IO manager.@khibino suggested me to use STM actions both for timeout and recv-ready. Since they are a single action for earch, they are not blocked. And they can be composed because they are STM actions. This PR implements his idea instead of using
MVar
.I deployed this
network
library on my test server. So far, I don't see anyCLOSE_WAIT
.Cc: @larskuhtz @snoyberg @fosskers @nh2