Comment 2 for bug 1904722

Revision history for this message
Richard M Kreuter (kreuter) wrote :

Oh, I hadn't noticed that SBCL has null-ary MAKE-BROADCAST-STREAM return a singleton. That accounts for the CLOSE behavior.

However, while I see a benefit of having a distinguished "null output" sink, but does it need to be a singleton returned by the (apparent) constructor? A variable in SB-EXT would suffice, and would be portably emulatable on other implementations:

--
(defpackage "MY-PACKAGE"
  #+sbcl
  (import-from "SB-EXT" "*NULL-OUTPUT*") ;say
  ...)

(in-package "MY-PACKAGE")

#-sbcl ; SBCL already has this
(defvar *null-output* (make-broadcast-stream))
--

Anyway, the reason I care is that among some interface invariants that are desirable to have and seem to be intuitive readings of the standard include:

a. when CLOSE runs to completion, the stream is closed afterward, and
b. when a stream is closed, OPEN-STREAM-P returns false.
c. CLOSE does nothing on a closed stream.

Why these invariants? Well, I like to make sure streams get closed eventually, and in programs that create a dynamic number of streams, that's easier to do when CLOSE and OPEN-STREAM-P have those guarantees.

That said, I'd agree that if a distinguished stream is to serve as a global "null output" sink, closing that stream would likely cause trouble, so I can understand why you've made CLOSE a no-op for the singleton broadcast stream.

OTOH, it's already the case that there are a number of distinguished global streams that it's bad to close (the values of *stdin*, *stdout*, *stderr*, *tty*, for example; and if synonym streams get their own distinct open-ness as discussed in 1904257, then any of the standard I/O customization variables). If /that/ problem is worth solving for, ISTM an orthogonal mechanism equivalent to the following could be nice to have.

--
(defvar *never-close-streams* ())

(define-condition never-close-error (error)
  ((stream :initarg :stream :reader never-close-error-stream))
  (:report (lambda (error stream)
             (format stream "~S should not normally be closed."
                     (never-close-error-stream error)))))

(defmethod close :before ((stream stream))
  (when (member stream *never-close-streams*)
    (cerror "Continue" 'never-close-error :stream stream)))

;; User interface.
(defun never-close (stream)
  "Make it an error to try to close STREAM. Note that this does not persist across
SAVE-LISP-AND-DIE; use an *INIT-HOOK* function to reinstate."
  (pushnew stream *never-close-streams*))

;; Then stream-init can put *stdin*, *stdout*, *stderr*, *tty*, and
;; so forth onto *never-close-streams*, and stream-deinit could
;; reset *no-close-streams* to nil.
--

Untested, but if you're interested I can elaborate on this orthogonal mechanism. Let me know?