; Copyright (c) 1993-1999 by Richard Kelsey and Jonathan Rees. See file COPYING. ; Channel interrupt stuff. ; Install an interrupt handler that cells up the results of completed I/O ; operations and spawn a thread to cope with them. This is written so as ; to avoid having state in top-level variables, because their values are ; saved in dumped images. (define (initialize-channel-i/o!) (session-data-set! channel-wait-cells-slot '()) (session-data-set! channel-wait-count-slot 0) (set-interrupt-handler! (enum interrupt i/o-read-completion) (make-i/o-completion-handler (lambda (cell channel) (let ((old (cell-ref cell))) (cell-set! cell (cons (cons channel (car old)) (cdr old))))))) (set-interrupt-handler! (enum interrupt i/o-write-completion) (make-i/o-completion-handler (lambda (cell channel) (let ((old (cell-ref cell))) (cell-set! cell (cons (car old) (cons channel (cdr old))))))))) ; The warning message is printed using DEBUG-MESSAGE because to try to make ; sure it appears in spite of whatever problem's the I/O system is having. (define (make-i/o-completion-handler update-ready-cell) ;; Called with interrupts disabled. (lambda (channel status enabled-interrupts) (call-with-values (lambda () (fetch-channel-wait-cell! channel)) (lambda (thread-cell maybe-ready-cell) (cond ((and thread-cell (cell-ref thread-cell)) => (lambda (thread) (decrement-channel-wait-count!) (make-ready thread status))) ;; The problem with the debug message is that ;; WAIT-FOR-CHANNELS can trigger this warning in a perfectly ;; legimitate situation because of a race I don't know how to ;; avoid. --Mike ; (else ; (debug-message "Warning: dropping ignored channel i/o result {Channel " ; (channel-os-index channel) ; " " ; (channel-id channel) ; "}")) ) (cond ((and maybe-ready-cell (cell-ref maybe-ready-cell)) (update-ready-cell maybe-ready-cell channel))))))) ; Exported procedure (define (waiting-for-i/o?) (< 0 (channel-wait-count))) ; Block until the current I/O operation on CHANNEL has completed. ; This returns the result of the operation. ; ; This needs to be called with interrupts disabled. ; ; We install a DYNAMIC-WIND to abort the operation if the waiting thread is ; terminated. (define (wait-for-channel channel) (call-with-values (lambda () (fetch-channel-wait-cell! channel)) (lambda (thread-cell maybe-ready-cell) (if (and thread-cell (cell-ref thread-cell)) (begin (add-channel-wait-cell! channel thread-cell #f) (warn "channel has two pending operations" channel) (terminate-current-thread)) (let ((cell (make-cell (current-thread)))) (increment-channel-wait-count!) (set-thread-cell! (current-thread) cell) (add-channel-wait-cell! channel cell #f) (dynamic-wind nothing block (lambda () (with-interrupts-inhibited (lambda () (if (cell-ref cell) ;; we're being terminated (begin (fetch-channel-wait-cell! channel) (channel-abort channel) (wait-for-channel channel)))))))))))) (define (nothing) #f) (define (channel-check-waiter channel) (if (channel-has-waiter? channel) (begin (warn "channel has two pending operations" channel) (terminate-current-thread)))) (define (wait-for-channels read-channels write-channels timeout) ;; check if we're borked from the outset (for-each channel-check-waiter read-channels) (for-each channel-check-waiter write-channels) (let ((thread-cell (make-cell (current-thread))) (ready-channels-cell (make-cell (cons '() '()))) (ready-read-channels '()) (ready-write-channels '())) (if (or (not timeout) (register-dozer thread-cell timeout)) (begin ;; register us with every channel we're waiting for (set-thread-cell! (current-thread) thread-cell) (let ((signup (lambda (channel) (add-channel-wait-cell! channel thread-cell ready-channels-cell) (increment-channel-wait-count!)))) (for-each signup read-channels) (for-each signup write-channels)) ;; block (dynamic-wind nothing (lambda () (block) (disable-interrupts!) (let ((pair (cell-ref ready-channels-cell))) (set! ready-read-channels (car pair)) (set! ready-write-channels (cdr pair))) (cell-set! ready-channels-cell #f) (enable-interrupts!) (values ready-read-channels ready-write-channels)) ;; clean up (lambda () (let ((aborting? (and (cell-ref thread-cell) #t))) (with-interrupts-inhibited (lambda () ;; this ain't so great ... (let ((make-cleanup (lambda (ready-channels) (lambda (channel) (if (not (memq channel ready-channels)) (begin (fetch-channel-wait-cell! channel) (if (not aborting?) (decrement-channel-wait-count!) (begin (channel-abort channel) (wait-for-channel channel))))))))) (for-each (make-cleanup ready-read-channels) read-channels) (for-each (make-cleanup ready-write-channels) write-channels)))))))) ;; the timeout was zero or less (enable-interrupts!)))) ; Abort any pending operation on by OWNER on CHANNEL. ; Called with interrupts disabled. (define (steal-channel! channel owner) (call-with-values (lambda () (fetch-channel-wait-cell! channel)) (lambda (thread-cell maybe-ready-cell) (cond ((cell-ref thread-cell) => (lambda (thread) (cond ((eq? thread owner) (clear-thread-cell! thread) (decrement-channel-wait-count!) (channel-abort channel)) (else (warn "channel in use by other than port owner" channel thread owner) #f)))) (else #f))))) ; Have CHANNEL-READ and CHANNEL-WRITE wait if a pending-channel-i/o ; exception occurs. (define-exception-handler (enum op channel-maybe-read) (lambda (opcode reason buffer start count wait? channel . maybe-os-message) (if (= reason (enum exception pending-channel-i/o)) (wait-for-channel channel) (begin (enable-interrupts!) (apply signal-exception opcode reason buffer start count wait? channel maybe-os-message))))) (define-exception-handler (enum op channel-maybe-write) (lambda (opcode reason buffer start count channel . maybe-os-message) (if (= reason (enum exception pending-channel-i/o)) (wait-for-channel channel) (begin (enable-interrupts!) (apply signal-exception opcode reason buffer start count channel maybe-os-message))))) ; Two session slots ; - the number of threads waiting for I/O completion events ; - an alist mapping channels to cells for waiting threads (define channel-wait-count-slot (make-session-data-slot! 0)) (define (channel-wait-count) (session-data-ref channel-wait-count-slot)) (define (increment-channel-wait-count!) (session-data-set! channel-wait-count-slot (+ (channel-wait-count) 1))) (define (decrement-channel-wait-count!) (session-data-set! channel-wait-count-slot (- (channel-wait-count) 1))) (define channel-wait-cells-slot (make-session-data-slot! '())) ; Adding a cell and channel - the caller has already determined there is no ; existing cell for this channel. (define (add-channel-wait-cell! channel cell maybe-ready-channels-cell) (session-data-set! channel-wait-cells-slot (cons (cons channel (cons cell maybe-ready-channels-cell)) (session-data-ref channel-wait-cells-slot)))) ; This is just deleting from an a-list. (define (fetch-channel-wait-cell! channel) (let* ((cells (session-data-ref channel-wait-cells-slot)) (cell+ready-channels-cell (cond ((null? cells) #f) ((eq? channel (caar cells)) (session-data-set! channel-wait-cells-slot (cdr cells)) (cdar cells)) (else (let loop ((cells (cdr cells)) (prev cells)) (cond ((null? cells) #f) ((eq? channel (caar cells)) (set-cdr! prev (cdr cells)) (cdar cells)) (else (loop (cdr cells) cells)))))))) (cond (cell+ready-channels-cell => (lambda (pair) (let ((thread-cell (car pair)) (ready-cell (cdr pair))) (values thread-cell ready-cell)))) (else (values #f #f))))) (define (channel-has-waiter? channel) (and (assq channel (session-data-ref channel-wait-cells-slot)) #t))