Why does write-bytes-avail-evt not return an error?

I am trying to find how how I can handle all the errors that can happen related to a TCP connection.

So I have written this server:

#lang racket/base
(require racket/tcp)
(define listener (tcp-listen 8000))
(define o (let-values ([(in out) (tcp-accept listener)]) out))
(displayln "Sleeping")
(sleep 1)
(displayln "Writing")
(define evt (write-bytes-avail-evt #"apple" o))
(define res (sync evt))
(displayln res)
(displayln "Flushing")
(flush-output o)

And I connect to it with this client (Python, sorry):

import struct, socket
s = socket.socket(socket.AF_INET6)
s.connect(("::1", 8000))
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
s.close() # provokes Connection Reset at host

Now, I would expect a failure to occur during sync'ing on the event, since the documentation for write-bytes-avail-evt says that

the event becomes ready when bytes are written (unbuffered) to the port.

But it seems like the error is only occurring during flushing, which I don't understand, since the writing was supposed to be 'unbuffered'. Isn't the flushing about writing the buffer? Is it another buffer? How do I turn it off?

Here is the output I get:

Sleeping
Writing
5
Flushing
error writing to stream port
  system error: Connection reset by peer; errno=104
  context...:
   body of "/home/janus/racket-tcp/serv.rkt"

Hej Janus,

As I read the output, this

...
(define evt (write-bytes-avail-evt #"apple" o))
(define res (sync evt))
(displayln res)

writes the 5 bytes #"apple" to the buffer of output port o succesfully.

Then the Python client closes the connection.

Then (flush-output o) attempts to flush a buffer - but since the port is closed, an error is signaled.

Using (file-stream-buffer-mode o) you can see that the default buffer of o is block.
Setting the buffer mode to 'none with (file-stream-buffer-mode o 'none) changes the
location of the error to what you expected:

Sleeping
Writing
error writing to stream port
  system error: Broken pipe; errno=32
  context...:
   body of "/Users/soegaard/tmp/racket-server.rkt"

Hej Jens Axel,

I don't understand how this is the ordering of events, since as the Python is written, the closing happens just after the connection is established. That is, immediately after the tcp-accept.

This is why I put the sleep there — I wanted to make sure the connection is already closed by the time the bytes are written.

(file-stream-buffer-mode o 'none)

This seems to be what I want. Thank you.

I suppose the correct parsing of this statement is that:

  • When unbuffered, the event becomes written when the bytes are written to the port.

That way, no assertion is made about what happens if the port is buffered. That would make it consistent with the behaviour that we're seeing. However, I do think it's a curious application of parenthesis.

I think this is a bug in write-bytes-avail-evt for Racket CS.

As @janus quotes from the documentation, the event should become ready only when bytes are written, as opposed to being buffered. The port's buffer mode is not supposed to matter. Along those lines, Racket BC behaves as expected without (file-stream-buffer-mode o 'none).

The bug appears to be simply a wrong flag passed to the port's underlying output function where a flag to disable buffering was intended. I'll push a repair. Meanwhile, (file-stream-buffer-mode o 'none) is a good workaround.

2 Likes