racket/port: add a flushing tidy callback for reencode-output-port

This change solves a long-standing problem that reencoded output
was not flushed on exit, especially in the case that the
current output port is reencoded.
This commit is contained in:
Matthew Flatt 2014-05-07 07:27:46 -06:00
parent 1bd604073a
commit c33707329c
3 changed files with 92 additions and 64 deletions

View File

@ -421,7 +421,10 @@ the buffer when they are part of an incomplete encoding sequence.
The resulting output port does not support atomic writes. An explicit
flush or special-write to the output port can hang if the most
recently written bytes form an incomplete encoding sequence.}
recently written bytes form an incomplete encoding sequence.
When the port is buffered, a @tech{tidy callback} is registered with
the current custodian (see @secref["custodians"]) to flush the buffer.}
@defproc[(dup-input-port [in input-port?]

View File

@ -746,47 +746,53 @@
(void))
;; Check buffer modes:
(let ([i (open-input-string "abc")]
[o (open-output-string)])
(test #f file-stream-buffer-mode i)
(test #f file-stream-buffer-mode o)
(let ([ei (reencode-input-port i "UTF-8")]
[eo (reencode-output-port o "UTF-8")])
(test 'none file-stream-buffer-mode ei)
(test 'block file-stream-buffer-mode eo)
(let ()
(define (check-buffering flush-output)
(let ([i (open-input-string "abc")]
[o (open-output-string)])
(test #f file-stream-buffer-mode i)
(test #f file-stream-buffer-mode o)
(let ([ei (reencode-input-port i "UTF-8")]
[eo (reencode-output-port o "UTF-8")])
(test 'none file-stream-buffer-mode ei)
(test 'block file-stream-buffer-mode eo)
(test (void) display 10 eo)
(test (void) display 12 eo)
(test (void) newline eo)
(test #"" get-output-bytes o)
(test (void) flush-output eo)
(test #"1012\n" get-output-bytes o)
(test (void) file-stream-buffer-mode eo 'line)
(test 'line file-stream-buffer-mode eo)
(test (void) display 13 eo)
(test #"1012\n" get-output-bytes o)
(test (void) newline eo)
(test #"1012\n13\n" get-output-bytes o)
(test (void) flush-output eo)
(test #"1012\n13\n" get-output-bytes o)
(test (void) display 10 eo)
(test (void) display 12 eo)
(test (void) newline eo)
(test #"" get-output-bytes o)
(test (void) flush-output eo)
(test #"1012\n" get-output-bytes o)
(test (void) file-stream-buffer-mode eo 'line)
(test 'line file-stream-buffer-mode eo)
(test (void) display 13 eo)
(test #"1012\n" get-output-bytes o)
(test (void) newline eo)
(test #"1012\n13\n" get-output-bytes o)
(test (void) flush-output eo)
(test #"1012\n13\n" get-output-bytes o)
(test (void) display 14 eo)
(test #"1012\n13\n" get-output-bytes o)
(test (void) file-stream-buffer-mode eo 'none)
(test #"1012\n13\n14" get-output-bytes o)
(test 'none file-stream-buffer-mode eo)
(test (void) display 15 eo)
(test #"1012\n13\n1415" get-output-bytes o)
(test (void) display 14 eo)
(test #"1012\n13\n" get-output-bytes o)
(test (void) file-stream-buffer-mode eo 'none)
(test #"1012\n13\n14" get-output-bytes o)
(test 'none file-stream-buffer-mode eo)
(test (void) display 15 eo)
(test #"1012\n13\n1415" get-output-bytes o)
(test #\a read-char ei)
(test #\b peek-char i)
(test (void) file-stream-buffer-mode ei 'block)
(test 'block file-stream-buffer-mode ei)
(test #\b read-char ei)
(test eof peek-char i)
(test #\c read-char ei)
(test eof read-char ei)))
(test #\a read-char ei)
(test #\b peek-char i)
(test (void) file-stream-buffer-mode ei 'block)
(test 'block file-stream-buffer-mode ei)
(test #\b read-char ei)
(test eof peek-char i)
(test #\c read-char ei)
(test eof read-char ei))))
;; (check-buffering flush-output)
(let ([c (make-custodian)])
(parameterize ([current-custodian c])
(check-buffering (lambda (o) (custodian-tidy-all c))))))
(err/rt-test
(port->bytes (reencode-input-port (open-input-bytes #"\xFF\xFF") "utf-8"))

View File

@ -1636,7 +1636,10 @@
[out-end 0]
[buffer-mode (or (file-stream-buffer-mode port) 'block)]
[debuffer-buf #f]
[newline-buffer #f])
[newline-buffer #f]
[cust (current-custodian)]
[tidy-callback #f]
[self #f])
(define-values (buffered-r buffered-w) (make-pipe 4096))
;; The main writing entry point:
@ -1649,7 +1652,9 @@
(flush-buffer-pipe #f enable-break?)
(flush-some #f enable-break?)
(if (buffer-flushed?)
0
(begin
(buffering! #f)
0)
(write-it s start end no-buffer&block? enable-break?))]
[no-buffer&block?
(case (flush-all #t enable-break?)
@ -1671,6 +1676,7 @@
[(and (eq? buffer-mode 'block)
(zero? (pipe-content-length buffered-r)))
;; The port system can buffer to a pipe faster, so give it a pipe.
(buffering! #t)
buffered-w]
[else
;; Flush/buffer from pipe, first:
@ -1687,6 +1693,7 @@
(write-it s start end #f enable-break?)
;; Buffer and report success:
(begin
(buffering! #t)
(bytes-copy! out-bytes out-end s2 start2 (+ start2 cnt2))
(set! out-end (+ cnt2 out-end))
(case buffer-mode
@ -1837,7 +1844,9 @@
[orig-out-end out-end])
(flush-some non-block? enable-break?)
(if (buffer-flushed?)
'done
(begin
(buffering! #f)
'done)
;; Couldn't flush everything. One possibility is that we need
;; more bytes to convert before a flush.
(if (and orig-none-ready?
@ -1857,6 +1866,14 @@
(= out-start out-end)
(zero? (pipe-content-length buffered-r))))
(define (buffering! on?)
(cond
[(and on? (not tidy-callback))
(set! tidy-callback (custodian-add-tidy! cust (lambda (e) (flush-output self))))]
[(and (not on?) tidy-callback)
(custodian-remove-tidy! tidy-callback)
(set! tidy-callback #f)]))
;; Try to flush immediately a certain number of bytes.
;; we've already converted them, so we have to keep
;; the bytes in any case.
@ -1913,28 +1930,30 @@
"could not create converter from ~e to UTF-8"
encoding))
(make-output-port
name
port
write-it
(lambda ()
;; Flush output
(write-it #"" 0 0 #f #f)
(when close?
(close-output-port port))
(bytes-close-converter c))
write-special-it
#f #f
#f void
1
(case-lambda
[() buffer-mode]
[(mode) (let ([old buffer-mode])
(set! buffer-mode mode)
(when (or (and (eq? old 'block) (memq mode '(none line)))
(and (eq? old 'line) (memq mode '(none))))
;; Flush output
(write-it #"" 0 0 #f #f)))])))))
(set! self
(make-output-port
name
port
write-it
(lambda ()
;; Flush output
(flush-output self)
(when close?
(close-output-port port))
(bytes-close-converter c))
write-special-it
#f #f
#f void
1
(case-lambda
[() buffer-mode]
[(mode) (let ([old buffer-mode])
(set! buffer-mode mode)
(when (or (and (eq? old 'block) (memq mode '(none line)))
(and (eq? old 'line) (memq mode '(none))))
;; Flush output
(write-it #"" 0 0 #f #f)))])))
self)))
;; ----------------------------------------