* Adding (ice-9 suspendable-ports) support to https / custom ports
@ 2017-07-04 16:04 Christopher Allan Webber
2017-07-27 5:14 ` Christopher Allan Webber
0 siblings, 1 reply; 2+ messages in thread
From: Christopher Allan Webber @ 2017-07-04 16:04 UTC (permalink / raw)
To: guile-devel
Hiya,
For the project I'm working on, I'll need to have (ice-9
suspendable-ports) work with https. This is a bit more urgent than I
realized. I'm willing to attempt the work, but I don't even know where
to start.
Could someone give me pointers as to where to begin spelunking? I'm
guessing I have to both follow the lead of the suspendable-ports
adjustments to normal ports and also enable fnctl support for custom
ports? Specifically, I imagine that this needs to be supported:
(let ((flags (fcntl socket F_GETFL)))
(fcntl socket F_SETFL (logior O_NONBLOCK flags)))
Either that or we need some generalized procedure that can either do
this to a port which does set fnctl or... I don't know what it would do
for something which doesn't need to run that operation.
Advice appreciated!
- Chris
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: Adding (ice-9 suspendable-ports) support to https / custom ports
2017-07-04 16:04 Adding (ice-9 suspendable-ports) support to https / custom ports Christopher Allan Webber
@ 2017-07-27 5:14 ` Christopher Allan Webber
0 siblings, 0 replies; 2+ messages in thread
From: Christopher Allan Webber @ 2017-07-27 5:14 UTC (permalink / raw)
To: guile-devel
So since writing this initial email I had a conversation with Wingo and
have done a lot more research. I'm a lot more informed about what needs
to happen, but unfortunately, I'm also very stuck. This is unfortunate
because this is basically blocking the release of the test suite
relating to the standards work I'm doing, which was due out this
Tuesday! So any help is *greatly* appreciated.
So here's the situation:
- We have suspendable-ports which make asynchronous I/O work nicely and
cooperatively, yay
- HTTPS requests now work because we wrap underlying ports in gnutls
using custom-binary-i/o-ports, yay
- Oh no but the gnutls-wrapped ports aren't suspendable and block
- Oh no the ActivityPub test suite sends out a request to a foreign
server and the foreign server sends its own request to the test suite
before it gives a response but the test suite is blocked on its
initial request and oh no the whole thing is deadlocked oops, take
that cooperative model!
- To make things more complicated, when we call C code that calls into
Scheme code, we can no longer abort to a prompt, which is how
suspendable ports works. (For more on that see Wingo's blogposts
on delimited continuations and the Scheme/C stacks.)
Wingo gave some helpful advice on IRC:
<wingo> there are two general options. one is, if this port is always
operated on from scheme, *and* you arrange for it to implement the
read/write functions via the scm_read / scm_write members and not the
c_read/c_write --
<wingo> then in that case, the read/write functions written in scheme can
themselves use suspendable ports, transparently blocking.
<wingo> the second option is to change the read/write functions to allow them
to return -1 when they would block, and in that case you implement the
read_wait_fd / write_wait_fd methods.
<wingo> i suspect the latter is going to be easier but i don't know
I've been looking into solutions.
1. The first option would be the nicest if it were possible; we could
simply do the "set the port nonblocking with fcntl" dance on the
wrapped port, and when we try to write to the wrapped port, it should
automatically suspend.
Unfortunately there are problems with this route afaict:
- It wouldn't be possible to use custom-binary-i/o-ports anyway,
because scm_read/scm_write are properties not of the port instance
itself but of its port type, as far as I can tell. The whole
point of custom-binary-i/o-ports is to be able to set up
procedures on the instance, so I'm not sure how to get around
this.
- Even if we didn't do this, the low-level port-read/port-write
procedures in ports.c are, well, in C. So I'm afraid we're going
to pass through C anyway.
2. Okay, well let's add the read_wait_fd and write_wait_fd methods
(which would really just wrap the wrapped port's file descriptors)
and allow the existing read/write functions from the current
tls-wrapping port thing we've got to just return -1, indicating that
they'd like to suspend please so as to not block. In theory, this
would just be passed along from the underlying port. Okay, sounds
great! Except... well let's look at how the current tls-wrapping
code looks:
#+BEGIN_SRC scheme
(define (tls-wrap port server)
"Return PORT wrapped in a TLS connection to SERVER. SERVER must be a DNS
host name without trailing dot."
;; **** gnutls setup stuff here ***
;; @@: Not sure if this comment would help
;; FIXME: It appears that session-record-port is entirely
;; sufficient; it's already a port. The only value of this code is
;; to keep a reference on "port", to keep it alive! To fix this we
;; need to arrange to either hand GnuTLS its own fd to close, or to
;; arrange a reference from the session-record-port to the
;; underlying socket.
(let ((record (session-record-port session)))
(define (read! bv start count)
(define read-bv (get-bytevector-some record))
(if (eof-object? read-bv)
0 ; read! returns 0 on eof-object
(let ((read-bv-len (bytevector-length read-bv)))
(bytevector-copy! read-bv 0 bv start (min read-bv-len count))
(when (< count read-bv-len)
(unget-bytevector record bv count (- read-bv-len count)))
read-bv-len)))
(define (write! bv start count)
(put-bytevector record bv start count)
(force-output record)
count)
;; **** Some more stuff for close, etc here ****
(make-custom-binary-input/output-port "gnutls wrapped port" read! write!
get-position set-position!
close))))
#+END_SRC
Okay, so what are the issues here?
- In order to pass along whether or not these operations should
return -1 or not, we couldn't just call put-bytevector and
get-bytevector-some as we are now, since those would themselves
call code that looks like:
#+BEGIN_SRC scheme
(define (wait-for-readable port) ((current-read-waiter) port))
(define (read-bytes port dst start count)
(cond
(((port-read port) port dst start count)
=> (lambda (read)
(unless (<= 0 read count)
(error "bad return from port read function" read))
read))
(else
(wait-for-readable port)
(read-bytes port dst start count))))
#+END_SRC
We'd basically want to call port-read ourselves so we could see
whether or not to return -1. However,
put-bytevector/get-bytevector-some are pretty large, and that's a
lot of code to copy-pasta-and-kludgify just for this.
- Also, as you can see in the code above called by put-bytevector
and get-bytevector-some, that code itself (aside from doing
buffering and etc) is taking advantage of suspending to the
agenda.
- But maybe it's even possible to pull it off without put-bytevector
and friends, just by calling the low-level port-read/port-write
ourselves? Maybe we can do that, I'm not sure about that one?
Anyway, that's where I'm at. I have this sneaking suspicion there's a
more obvious answer sitting right in front of me, but I'm pretty new to
this side of Guile's guts. Any guidance welcome!
- Chris
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2017-07-27 5:14 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-07-04 16:04 Adding (ice-9 suspendable-ports) support to https / custom ports Christopher Allan Webber
2017-07-27 5:14 ` Christopher Allan Webber
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).