Skip to content

close does not throw on ECONNRESET #329

Description

@eborden

Viktor ([email protected]) wrote from haskell-cafe:

In older releases of Network, I was initially surprised that
closing a connected TCP socket could throw an exception,
and so I had to take some care with:

        bracket (socket ...) -- create a stream socket
                (try to do something with) -- catch I/O errors here
                (close) -- have to try here too!

to avoding crashing when close fails. The reason close failed,
is that if the caller is the last writer on the socket, then it
is possible that the kernel queued the last write, but got an RST
response from the peer when trying to deliver the data to the
remote destination. This is reported during close(2):

[ECONNRESET] The underlying object was a stream socket that was
shut down by the peer before all pending data was
delivered

The original Network.Socket code converted this into an exception.
That had pros and cons. On the one hand, users naïvely assuming
that "close" can't fail won't find their code unexpectedly blowing
up. On the other hand users relying on the OS to report failure
of the final write will no longer get the delayed exception
(it is a feature that write(2) completes before the data is
acknowledged by the remote peer, otherwise TCP could not stream).

I am vaguely inclined to think that restoring the exception might
be the right thing, but on the other hand, if the last write is
important to deliver, it should perhaps be, and often is, more
properly confirmed at the application layer. So for many
applications the potential of an exception on close is an
annoyance.

One might also note that the documentation never covered the
possibility of such an exception, and the two code samples at
the top of the Network.Socket online docs don't suggest any
need to catch "close" errors. So this is likely a poorly
understood corner case.

With that said, what do you think? Should the Haskell Socket
"close" throw an exception when the native socket library
close returns an error? Or leave confirmation of the last
application data payload to the remote reader?

Activity

  1. eborden commented on Jun 24, 2018

    @eborden
    CollaboratorAuthor

    For the historic perspective, close has only ever thrown on -1. This behavior
    was introduced in 2010 (fdd420f). Other return
    codes have been ignored all the way back to 1.0 as c_close was used directly
    and its return code was discarded.

    Moving forward, it is an interesting question of what the "right" thing to
    do is. For compatability it is probably prudent to not introduce a new exception.
    If ensuring all data was recieved is integral to your application then I hope
    that you'd be aware of this before closing a socket. However not throwing the
    exception could lead to tricky bugs and difficult debugging sessions.

  2. kazu-yamamoto commented on Jun 25, 2018

    @kazu-yamamoto
    Collaborator
  3. vdukhovni commented on Jun 25, 2018

    @vdukhovni

    @eborden For the historic perspective, close has only ever thrown on -1.

    On Unix systems, the only possible return values from close(2) are 0 and -1, so "only ever on -1" amounts to all failures of the underlying system call unless I'm missing something.

    RETURN VALUES
         The close() function returns the value 0 if successful; otherwise the
         value -1 is returned and the global variable errno is set to indicate the
         error.
    

    And yes, this is rather a judgement call. Not sure which is the more correct behaviour. The previous exception behaviour was undocumented, so you're at liberty to choose compatibility or avoid surprising to some exceptions on close.

    Perhaps the solution is to just document that exceptions were thrown in older releases, but no longer are. Then see if anyone complains. I think that ECONNRESET is not "reliable", in a successful close does not guarantee the data made it all the way to the peer application and got dealt with. So one might argue that an unreliable failure indication is not especially useful...

  4. kazu-yamamoto commented on Jun 25, 2018

    @kazu-yamamoto
    Collaborator

    What about providing two APIs?

    • close which drops errors -- most applications should use this
    • safeClose (or something) which throws an exception on failure.
  5. vdukhovni commented on Jun 25, 2018

    @vdukhovni

    Two functions might make sense, and this forces the issue on documentation, since the difference would need to be explained, and complete documentation is a good thing to have. The only issue is naming, I'm not sure that safeClose is an intuitive name. My son suggests close'. What do you think?

  6. kazu-yamamoto commented on Jun 25, 2018

    @kazu-yamamoto
    Collaborator

    close' is fine with me. Of course, document should be well-written.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions