-
Notifications
You must be signed in to change notification settings - Fork 190
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
implementing gracefulClose. #417
Conversation
@Mistuke I implemented some stuffs for Windows but they are not perfect. Would you give look and consider how we can implement? |
I would love to implement |
Hi, I'm curious about this one as this is a general problem and applies to more or less all TCP-based protocols. I just want to make sure my current understanding of the topic is correct or maybe I'd learn something here. In the scenario you describe, the server sends GOAWAY and then immediately closes the socket (2 subsequent syscalls). Depending on how SO_LINGER is configured, the server's TCP stack transmits the GOAWAY data and then the socket goes into FIN_WAIT/TIME_WAIT/CLOSED state. The server is certainly no longer interested in client data as it initiated the active close. The client may or may not receive the GOAWAY data.
My question is: What improvement would it yield to additionally use the stream termination signalling on the TCP level? My suspicion is that the GOAWAY/GOAWAY mechanism has been introduced to overcome exactly this shortcoming of most TCP implementations (not all TCP stacks support or correctly implement shutdown). |
@lpeterse I think that you are confused.
What I tries to solve is that the current common pattern where
We can use In my understanding, the combination of |
@kazu-yamamoto I'm currently on holiday and traveling for the next 3 weeks and didn't take a laptop with me. But I'll take a look sometime tomorrow. |
Sorry I've been on vacation, I'll get to reviewing this soon. |
It appeared that |
The following code works as expected:
But I don't want to use |
@kazu-yamamoto sorry for the late reply. I'm currently on vacation somewhere up a mountain and have no laptop. I'll respond more in detail when I'm back this weekend. But this is a bit tricky to implement using the non-async interface. On Windows the blocking mode is determined by how the handle was opened. So in order to get You can 1) temporarily switch the handle into non-blocking mode using
Or 2) you can first peek the socket to see if any data is available and only call
The problem here though is that this call returning a 0 does not distinguish between no data being available and the socket being closed. Documentation for both of these can be found here https://docs.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls Probably switching the blocking mode is the easiest way. But it may give unexpected result if multiple functions access the same socket at the same time. |
@Mistuke Current implementation keeps non-blocking, not switch to blocking. |
@kazu-yamamoto I don't quite follow. It's non-blocking on Linux yes since |
@Mistuke Sorry. I'm not familiar with Windows. My comment above would not make sense. |
So the only problem I can see with converting the handle temporarily to non-blocking is that in a threaded program this may interfere with a blocking So I think |
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Control.Concurrent (forkFinally)
import Control.Exception (SomeException(..))
import qualified Control.Exception as E
import Control.Monad (forever, void)
import Data.ByteString.Builder (byteString)
import Network.HTTP.Types (ok200)
import Network.HTTP2.Server
import Network.Socket
main :: IO ()
main = runTCPServer "80" $ \s _peer -> runHTTP2Server s
where
runHTTP2Server s = E.bracket (allocSimpleConfig s 4096)
(\config -> run config server)
freeSimpleConfig
server _req _aux sendResponse = sendResponse response []
where
response = responseBuilder ok200 header body
header = [("Content-Type", "text/plain")]
body = byteString "Hello, world!\n"
runTCPServer :: String -> (Socket -> SockAddr -> IO a) -> IO a
runTCPServer port server = withSocketsDo $ do
addr <- resolve
E.bracket (open addr) close loop
where
resolve = do
let hints = defaultHints {
addrFlags = [AI_PASSIVE]
, addrSocketType = Stream
}
head <$> getAddrInfo (Just hints) Nothing (Just port)
open addr = do
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
setSocketOption sock ReuseAddr 1
withFdSocket sock $ setCloseOnExecIfNeeded
bind sock $ addrAddress addr
listen sock 1024
return sock
-- for "close"
loop sock = forever $ do
(conn, peer) <- accept sock
void $ forkFinally (server conn peer) (clear conn)
clear conn _ = close conn `E.catch` ignore
where
ignore (SomeException _) = return ()
-- for "gracefulClose"
{-
loop sock = forever $ do
(conn, peer) <- accept sock
void $ forkFinally (server conn peer) (const $ gracefulClose conn 5000)
-}
|
@Mistuke Any progress? |
Ah yes, I started last weekend but had to fix something in ghc (unrelated)
. I'll finish it up the coming weekend. Sorry for the delay!
…Sent from my Mobile
On Wed, Jul 31, 2019, 05:10 Kazu Yamamoto ***@***.***> wrote:
@Mistuke <https://github.com/Mistuke> Any progress?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#417?email_source=notifications&email_token=AAI7OKIEI4XXEKSW7A6IIE3QCEGDVA5CNFSM4H4YRZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD3GAIVA#issuecomment-516686932>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAI7OKJJ5GSYDTXMOXJKUVLQCEGDVANCNFSM4H4YRZ7Q>
.
|
I have a patch https://gist.github.com/Mistuke/1a69e57cd17c59f3e809274500cc5ec0 but have so far been unable to compile the example. having some cabal issues. Since I'm only after seeing if |
OK. I will try to make a test case only with |
So how do you compile the other testcase? Do you just install network globally? I was trying to use |
I install everything globally. I don't use repl. |
Credit: Tamar Christina <[email protected]>
@Mistuke I added your patch. Since Anyway, I also added a test case. Let's wait for AppVeyor. |
@Mistuke AppVeyor fails due to the new test case. Please check it out! |
that should do it. Sorry for the long delay! I'm a bit swamped with work these days. |
Credit: Tamar Christina <[email protected]>
@Mistuke I works well! Thanks! |
@eborden Would you review and merge this PR? |
@nh2 May I ask you to review this PR, too? |
Yes, I will take a look. |
Network/Socket/Shutdown.hs
Outdated
clock = 200 | ||
-- shutdown sometime returns ENOTCONN. | ||
-- Probably, we don't want to log this error. | ||
ignore (E.SomeException _e) = return () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd advise using E.IOException
instead to avoid ignoring async exceptions. Especially given the usage of threadDelay
, it's reasonable that an external thread may end up trying to kill this operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Here you're ignoring all exceptions, not only
ENOTCONN
; we'd have to checkerrno
for that. - While it may be acceptable that
close
ignores exceptions (seenetwork/Network/Socket/Types.hsc
Lines 169 to 175 in bbb66aa
-- | Close the socket. This function does not throw exceptions even if -- the underlying system call returns errors. -- -- If multiple threads use the same socket and one uses 'fdSocket' and -- the other use 'close', unexpected behavior may happen. -- For more information, please refer to the documentation of 'fdSocket'. close :: Socket -> IO () catch
ing this not only overclose s
but alsosendRecvFIN
. I don't think we should ingore exceptions ofsendRecvFIN
. It callsshutdown
, which has propererrno
raising behaviour, and we should re-raise those exceptions. At least all the ones that aren'tENOTCONN
, but even in that case I'm not sure why it's legitimate to not throw onENOTCONN
; the comment probably needs expansion. The user could also call accidentally callgracefulClose
on an unconnected socket; wouldn't we want to tell them that they did something wrong by raising theENOTCONN
? - Somewhat unrelatedly, @snoyberg and I think that even
close
should probably not silently discardEBADF
as it currently does, because that's a user error. (The othererrno
s leave the socket in undefined state so there it's probably more OK.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nh2 It seems to me that my experience was based on the previous wrong implementation using unsafe
calls. Using the current implementation, I don't need to ignore ENOTCONN
anymore since it does not occur in tests. So, let's remove ignore
.
r <- recvBufNoWait s buf bufSize | ||
let delay' = delay + clock | ||
when (r == -1 && delay' < tm) $ do | ||
threadDelay (clock * 1000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of looping with a threadDelay
, what if we had an MVar
which can be filled with either a MoreData
or TimeoutTripped
data constructor. We use registerTimeout
to set a timeout to putMVar
the TimeoutTripped
constructor, and registerFd
to putMVar
the MoreData
. Then we don't need an arbitrary delay length and avoid any kind of arbitrary delays or busy-looping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Mistuke GHC for Windows does not provide GHC.Event
. Should we use the previous busy-loop implementation for Windows?
-- application which can read it. So, let's stop receiving | ||
-- to prevent attacks. | ||
r <- recvBufNoWait s buf bufSize | ||
let delay' = delay + clock |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you're counting the time you asked to sleep, not the time that was actually slept (which can be a lot more when the system is busy).
I think if we implement loop-based sleeping (which we may not have to do based on @snoyberg's comment), we should re-check the time that has passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nh2 We have to use threadDelay
on WIndows due to IO manager's limitation. Though your point is absolutely right, network
should not depends on time
nor hourglass
unfortunately. Do you have any ideas to check the time using base
only?
@kazu-yamamoto yes that is the only way to get it to work for now. |
I rescued Windows and GHC 7.8 on Unix. CIs are now green. |
If |
Merged. Thank you all for reviewing! |
…!)" This reverts commit a3bbd89. The approach is not viable because of snoyberg/http-client#394 The fact that we don't have haskell/network#417 in our version of the network package probably doesn't help either.
…!)" This reverts commit a3bbd89. The approach is not viable because of snoyberg/http-client#394 The fact that we don't have haskell/network#417 in our version of the network package probably doesn't help either.
…!)" This reverts commit a3bbd89. The approach is not viable because of snoyberg/http-client#394 The fact that we don't have haskell/network#417 in our version of the network package probably doesn't help either.
…!)" This reverts commit a3bbd89. The approach is not viable because of snoyberg/http-client#394 The fact that we don't have haskell/network#417 in our version of the network package probably doesn't help either.
Suppose that we have an HTTP/2 server. When the server tries to close the connection, it sends a GOAWAY frame. And suppose that the server
close
s the socket immediately. Requests from the client may reach to the server after the socket is gone. In this case, TCP RST is sent from the server to the client. The client misunderstands that this communication failed.To close sockets gracefully, we should do:
shutdown(2) TX
to send TCP FINrecv(2)
to check if TCP FIN has been arrived from the peer. This operation should be timed out.close(2)
To implement this, many things are missing in the
network
library:getSocketOption
andsetSocketOption
forRecvTimeout
EWOULDBLOCK
.This PR implements all above. To keep the signatures of
getSocketOption
andsetSocketOption
, I took the Windows way. That is, the argument isInt
in milliseconds.