Skip to content

send == 0: how to wait for socket to drain? #320

Description

@bscottm

I ran into this particular problem in @mvoidex's hsdev server while tracking down why hsdev would send incomplete responses. I was able to track the problem down to a reimplementation of sendAll:

            sendAll :: Socket -> BS.ByteString -> IO ()
            sendAll sock bs
                    | BS.null bs = return ()
                    | otherwise = do
                            sent <- Net.send sock bs
                            when (sent > 0) $ sendAll sock (BS.drop sent bs)

The subtle bug in this code is the send > 0: send can return 0 because the OS is waiting for a buffer to drain and the underlying descriptors are in non-blocking mode (GHC runtime "feature".)

One way around the send == 0 issue is to call Control.Concurrent.threadDelay. Optimally, you'd really want to call Control.Concurrent.threadWaitWrite, but you can't because converting the socket to a file descriptor is a one way process.

Is there a way to call threadWaitWrite without converting the socket to a descriptor?

Activity

  1. eborden commented on May 2, 2018

    @eborden
    Collaborator

    Hmm, this would possibly require dropping down to GHC.Prim and using waitWrite# on the Socket file descriptor.

  2. eborden commented on May 2, 2018

    @eborden
    Collaborator

    @bscottm Are you able to produce a minimal test case for this failure?

  3. bscottm commented on May 2, 2018

    @bscottm
    Author

    @eborden: I'll see what I can craft... Probably the best test case would be to take the echo server and send 66Kb buffers (at least for MacOS -- localhost/::1 buffering is 64Kb.)

    Receiver side is a bit trickier because the receiver's call to recv needs to consume less than 64kb at a time. In the SublimeHaskell plugin, the receiver consumes 10Kb at a time, looking for a newline (it's a JSON stream, newline is the separator between responses...)

    /cc @mvoidex

  4. kazu-yamamoto commented on May 7, 2018

    @kazu-yamamoto
    Collaborator

    What about this?

    sendAll :: Socket      -- ^ Connected socket
            -> ByteString  -- ^ Data to send
            -> IO ()
    sendAll sock bs
      | B.null bs = return ()
      | otherwise = do
          sent <- send sock bs
          if sent == 0 then do
              threadWaitWrite $ fromIntegral $ sockFd sock
              sendAll sock bs
            else if sent > 0 then
              sendAll sock (B.drop sent bs)
            else
              return ()
  5. bscottm commented on May 19, 2018

    @bscottm
    Author

    I have not been able to successfully reproduce the send == 0 issue.

  6. kazu-yamamoto commented on May 21, 2018

    @kazu-yamamoto
    Collaborator

    @bscottm Thanks!
    I will merge this into each versions.

  7. kazu-yamamoto commented on May 25, 2018

    @kazu-yamamoto
    Collaborator
    • sendAll
    • sendAllTo
    • sendMany
    • sendManyTo
    • sendAll for Lazy
  8. kazu-yamamoto commented on May 25, 2018

    @kazu-yamamoto
    Collaborator

    I did many git push -f. Sorry for your confusion.

  9. kazu-yamamoto commented on May 29, 2018

    @kazu-yamamoto
    Collaborator

    fixed via #321

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions