Skip to content

Socket close detection #212

Description

@winterland1989

This is actually a continuation of #169 and pr #211 , I'm starting a new thread because i realized there's one more problem we should get done: how should we detect a Socket close event.

The obvious way is to catch IOError when using read/write. in #211 we take this step further so that read/write on a closed Socket is guaranteed to throw an IOError, no matter who close the socket, which fix a bug of unix itself in some sense.

But what if we want to actively detect if a Socket is closed? let's say we're performing a expensive calculation to respond a waiting client, it's reasonable to periodically check if the Socket is still opened, otherwise we can cancel the calculation, but currently with network we can't do that. it's a interesting problem in other language too, please read this to get a understand of how complex of unix socket.

here's some thought:

  1. we should improve Exception situation in network. but i don't know how we can get backward compatibility if we change the exception type.
  2. we should replace the buggy read/write with new one, which guarantee to throw when Socket was closed, and we should document about throwing behavior clearly. A MVar is cheap comparing actual IO operation.
  3. we should provide exception free version for receiving functions, i.e. recvMaybe :: Socket -> Int -> Maybe ByteString. sadly i don't think we can do much for sending functions: returning a Maybe () looks silly.
  4. Currently isConnected is not ideal for reasons i listed above, we can provide a proper implementation with a new name, but i'm not sure if we can do it under windows.

In #211 , i proposed withConnectedSocket, but this function has a subtle problem: you can't use any operations which read the socket status inside it. the problem is inside withMVar, the MVar is empty, if we use anything which try to takeMVar, we produced a deed lock.

I'm not sure if we sure we should do these in another network-safe package, feel free to discuss about above points : ).

Activity

  1. kazu-yamamoto commented on Dec 18, 2017

    @kazu-yamamoto
    Collaborator

    Copying from #211:

    -- | If you're operating `Socket` in multithread environment,
    -- for example, use a timeout thread to close `Socket`. you have to
    -- make sure `Socket` is opened before read/write, 'withConnectedSocket'
    -- will run your action if `Socket` is still 'Connected', otherwise
    -- return the 'Socket' status instead.
    --
    -- Note, this will block other thread which try to close `Socket` by locking
    -- the status `MVar`.
    withConnectedSocket :: Socket -> (Socket -> IO a) -> IO (Either SocketStatus a)
    withConnectedSocket sock@(MkSocket _ _ _ _ statusVar) act =
      withMVar statusVar $ \status ->
         case status of
           Connected -> act sock
           _         -> return (Left status)
  2. kazu-yamamoto commented on Dec 18, 2017

    @kazu-yamamoto
    Collaborator

    @eborden I would like to do two things:

    • Merging withConnectedSocket to the network package
    • Updating documentation to describe that safer packages should be implemented separately based on withConnectedSocket
  3. kazu-yamamoto commented on Dec 28, 2017

    @kazu-yamamoto
    Collaborator

    withConnectedSocket is now in master.

  4. chrismwendt commented on May 16, 2020

    @chrismwendt

    I don't fully understand why, but withConnectedSocket was removed in ceba911 Perhaps this reasoning a few weeks prior is relevant? #286 (comment)

    What's the status of this functionality (detecting when a client disconnects/closes so that the server can kill an expensive computation)? A few other issues seem related but don't appear to have a definitive answer (e.g. #302).

    Another use case where cancellation is especially important is when using websockets. AFAICT the only workaround is to use a heartbeat (i.e. require the client to ping every 30s).

  5. kazu-yamamoto commented on May 17, 2020

    @kazu-yamamoto
    Collaborator

    I don't fully understand why, but withConnectedSocket was removed in ceba911 Perhaps this reasoning a few weeks prior is relevant? #286 (comment)

    The current Socket does not have status. We decided to not maintain socket status in Haskell side.

    What's the status of this functionality (detecting when a client disconnects/closes so that the server can kill an expensive computation)? A few other issues seem related but don't appear to have a definitive answer (e.g. #302).

    This is a famous issue of system call. Not Haskell problem. There is no way to detect socket status in the OS kernel without trying to send or receive data.

    Another use case where cancellation is especially important is when using websockets. AFAICT the only workaround is to use a heartbeat (i.e. require the client to ping every 30s).

    I don't know what you want to implement exactly. If you want to close idle connections, the timeout and recv can be used. If timeout is too expensive, you can try the time-manager package.

    Recently, I implemented very cheap timeout: https://github.com/kazu-yamamoto/quic/blob/master/Network/QUIC/Timeout.hs

  6. chrismwendt commented on May 17, 2020

    @chrismwendt

    Thanks for the definitive answer 🙇‍♂️

    There is no way to detect socket status in the OS kernel without trying to send or receive data.

    Is there a way that I could convince myself of this, too? It seems to work in Node.js:

    var WebSocketServer = require('websocket').server;
    var http = require('http');
    
    var server = http.createServer();
    server.listen(1337);
    
    wsServer = new WebSocketServer({ httpServer: server });
    
    wsServer.on('request', function(request) {
      var connection = request.accept(null, request.origin);
    
      connection.on('close', function() {
        console.log('CLOSE')
      });
    });
    $ websocat ws://localhost:1337
    Ctrl+C
    

    After Ctrl+C, the Node.js process prints "CLOSE".

    I'm not very familiar with sockets, so I'm probably not understanding something here. Does http://stefan.buettcher.org/cs/conn_closed.html provide any answers?

  7. kazu-yamamoto commented on May 17, 2020

    @kazu-yamamoto
    Collaborator

    I guess that the close callback is called when recv(2) returns 0.

  8. kazu-yamamoto commented on May 17, 2020

    @kazu-yamamoto
    Collaborator

    I think that node is using epoll(2). When epoll() tells that RX is available for a socket, recv(2) is called. RX gets available when either any data is received or EOF (TCP Fin) is received.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions