1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-15 16:20:17 +02:00

Support for non-blocking I/O

* doc/ref/api-io.texi (I/O Extensions): Document read_wait_fd /
  write_wait_fd members.
  (Non-Blocking I/O): New section.
* libguile/fports.c (fport_read, fport_write): Return -1 if the
  operation would block.
  (fport_wait_fd, scm_make_fptob): Add read/write wait-fd
  implementation.
* libguile/ports-internal.h (scm_t_port_type): Add read_wait_fd /
  write_wait_fd.
* libguile/ports.c (default_read_wait_fd, default_write_wait_fd): New
  functions.
  (scm_make_port_type): Initialize default read/write wait fd impls.
  (trampoline_to_c_read, trampoline_to_scm_read)
  (trampoline_to_c_write, trampoline_to_scm_write): To Scheme, a return
  of #f indicates EWOULDBLOCk.
  (scm_set_port_read_wait_fd, scm_set_port_write_wait_fd): New
  functions.
  (port_read_wait_fd, port_write_wait_fd, scm_port_read_wait_fd)
  (scm_port_write_wait_fd, port_poll, scm_port_poll): New functions.
  (scm_i_read_bytes, scm_i_write_bytes): Poll if the read or write would
  block.
* libguile/ports.h (scm_set_port_read_wait_fd)
  (scm_set_port_write_wait_fd): Add declarations.
* module/ice-9/ports.scm: Shunt port-poll and port-{read,write}-wait-fd
  to the internals module.
* module/ice-9/sports.scm (current-write-waiter):
  (current-read-waiter): Implement.
* test-suite/tests/ports.test: Adapt non-blocking test to new behavior.
* NEWS: Add entry.
This commit is contained in:
Andy Wingo 2016-05-20 14:51:51 +02:00
parent 8b6f4df3f4
commit 534139e458
9 changed files with 344 additions and 42 deletions

View file

@ -20,6 +20,7 @@
* Port Types:: Types of port and how to make them.
* R6RS I/O Ports:: The R6RS port API.
* I/O Extensions:: Implementing new port types in C.
* Non-Blocking I/O:: How Guile deals with EWOULDBLOCK.
* BOM Handling:: Handling of Unicode byte order marks.
@end menu
@ -2302,6 +2303,24 @@ It should write out bytes from the supplied bytevector @code{src},
starting at offset @code{start} and continuing for @code{count} bytes,
and return the number of bytes that were written.
@item read_wait_fd
@itemx write_wait_fd
If a port's @code{read} or @code{write} function returns @code{(size_t)
-1}, that indicates that reading or writing would block. In that case
to preserve the illusion of a blocking read or write operation, Guile's
C port run-time will @code{poll} on the file descriptor returned by
either the port's @code{read_wait_fd} or @code{write_wait_fd} function.
Set using
@deftypefun void scm_set_port_read_wait_fd (scm_t_port_type *type, int (*wait_fd) (SCM port))
@deftypefunx void scm_set_port_write_wait_fd (scm_t_port_type *type, int (*wait_fd) (SCM port))
@end deftypefun
Only a port type which implements the @code{read_wait_fd} or
@code{write_wait_fd} port methods can usefully return @code{(size_t) -1}
from a read or write function. @xref{Non-Blocking I/O}, for more on
non-blocking I/O in Guile.
@item print
Called when @code{write} is called on the port, to print a port
description. For example, for a file port it may produce something
@ -2384,6 +2403,74 @@ operating system inform Guile about the appropriate buffer sizes for the
particular file opened by the port.
@end table
@node Non-Blocking I/O
@subsection Non-Blocking I/O
Most ports in Guile are @dfn{blocking}: when you try to read a character
from a port, Guile will block on the read until a character is ready, or
end-of-stream is detected. Likewise whenever Guile goes to write
(possibly buffered) data to an output port, Guile will block until all
the data is written.
Interacting with ports in blocking mode is very convenient: you can
write straightforward, sequential algorithms whose code flow reflects
the flow of data. However, blocking I/O has two main limitations.
The first is that it's easy to get into a situation where code is
waiting on data. Time spent waiting on data when code could be doing
something else is wasteful and prevents your program from reaching its
peak throughput. If you implement a web server that sequentially
handles requests from clients, it's very easy for the server to end up
waiting on a client to finish its HTTP request, or waiting on it to
consume the response. The end result is that you are able to serve
fewer requests per second than you'd like to serve.
The second limitation is related: a blocking parser over user-controlled
input is a denial-of-service vulnerability. Indeed the so-called ``slow
loris'' attack of the early 2010s was just that: an attack on common web
servers that drip-fed HTTP requests, one character at a time. All it
took was a handful of slow loris connections to occupy an entire web
server.
In Guile we would like to preserve the ability to write straightforward
blocking networking processes of all kinds, but under the hood to allow
those processes to suspend their requests if they would block.
To do this, the first piece is to allow Guile ports to declare
themselves as being nonblocking. This is currently supported only for
file ports, which also includes sockets, terminals, or any other port
that is backed by a file descriptor. To do that, we use an arcane UNIX
incantation:
@example
(let ((flags (fcntl socket F_GETFL)))
(fcntl socket F_SETFL (logior O_NONBLOCK flags)))
@end example
Now the file descriptor is open in non-blocking mode. If Guile tries to
read or write from this file descriptor in C, it will block by polling
on the socket's @code{read_wait_fd}, to preserve the illusion of a
blocking read or write. @xref{I/O Extensions} for more on that internal
interface.
However if a user uses the new and experimental Scheme implementation of
ports in @code{(ice-9 sports)}, Guile instead calls the value of the
@code{current-read-waiter} or @code{current-write-waiter} parameters on
the port before re-trying the read or write. The default value of these
parameters does the same thing as the C port runtime: it blocks.
However it's possible to dynamically bind these parameters to handlers
that can suspend the current coroutine to a scheduler, to be later
re-animated once the port becomes readable or writable in the future.
In the mean-time the scheduler can run other code, for example servicing
other web requests.
Guile does not currently include such a scheduler. Currently we want to
make sure that we're providing the right primitives that can be used to
build schedulers and other user-space concurrency patterns. In the
meantime, have a look at 8sync (@url{https://gnu.org/software/8sync})
for a prototype of an asynchronous I/O and concurrency facility.
@node BOM Handling
@subsection Handling of Unicode byte order marks.
@cindex BOM