improved the setup for the front-end method so that

printing to stdout and stderr is safe and to better document
the issues
This commit is contained in:
Robby Findler 2011-02-14 13:50:07 -06:00
parent 8e94ce49e4
commit e3c26a2aa4
3 changed files with 75 additions and 16 deletions

View File

@ -1312,7 +1312,7 @@ TODO
(let ([run-on-user-thread (lambda (t) (let ([run-on-user-thread (lambda (t)
(queue-user/wait (queue-user/wait
(λ () (λ ()
(with-handlers ((exn? (λ (x) (printf "~s\n" (exn-message x))))) (with-handlers ((exn? (λ (x) (oprintf "~s\n" (exn-message x)))))
(t)))))]) (t)))))])
run-on-user-thread)) run-on-user-thread))
@ -1653,10 +1653,39 @@ TODO
(let ([lang (drracket:language-configuration:language-settings-language user-language-settings)] (let ([lang (drracket:language-configuration:language-settings-language user-language-settings)]
[drr-evtspace (current-eventspace)] [drr-evtspace (current-eventspace)]
[s (make-semaphore 0)]) [s (make-semaphore 0)])
(define-values (sp-err-other-end sp-err) (make-pipe))
(define-values (sp-out-other-end sp-out) (make-pipe))
(define io-chan (make-channel))
;; collect the IO to replay later
(thread
(λ ()
(let loop ([ports (list sp-err-other-end sp-out-other-end)]
[io '()])
(cond
[(null? ports) (channel-put io-chan io)]
[else
(apply sync
(map (λ (port) (handle-evt
port
(λ (_)
(define byte (read-byte port))
(if (eof-object? byte)
(loop (remq port ports) io)
(loop ports (cons (cons port byte)
io))))))
ports))]))))
(run-in-evaluation-thread (run-in-evaluation-thread
(λ () (λ ()
(let/ec k (let/ec k
(parameterize ([error-escape-handler (λ () (k (void)))]) ;; we set the io ports here to ones that just collect the data
;; since we're blocking the eventspace handler thread (and thus IO to
;; the user's ports can deadlock)
(parameterize ([error-escape-handler (λ () (k (void)))]
[current-output-port sp-out]
[current-error-port sp-err])
(cond (cond
;; this is for backwards compatibility; drracket used to ;; this is for backwards compatibility; drracket used to
;; expect this method to be a thunk (but that was a bad decision) ;; expect this method to be a thunk (but that was a bad decision)
@ -1667,7 +1696,21 @@ TODO
;; this is the backwards compatible case. ;; this is the backwards compatible case.
(send lang first-opened)]))) (send lang first-opened)])))
(semaphore-post s))) (semaphore-post s)))
(semaphore-wait s))
;; wait for the first-opened method to finish up
(semaphore-wait s)
;; close the output ports to get the above thread to terminate
(close-output-port sp-err)
(close-output-port sp-out)
;; duplicate it over to the user's ports, now that there is
;; no danger of deadlock
(for ([i (in-list (reverse (channel-get io-chan)))])
(write-byte (cdr i)
(if (eq? (car i) sp-err-other-end)
(get-err-port)
(get-out-port)))))
(send context enable-evaluation) (send context enable-evaluation)
(end-edit-sequence) (end-edit-sequence)

View File

@ -440,7 +440,7 @@ This method is the same as
} }
@defmethod[(on-execute [settings settings] @defmethod[(on-execute [settings settings]
[run-in-user-thread ((-> void) -> void)]) [run-on-user-thread ((-> void) -> void)])
vod]{ vod]{
This method is the same as This method is the same as
@method[drracket:language:language<%> on-execute]. @method[drracket:language:language<%> on-execute].
@ -628,7 +628,7 @@ default settings obtained via
} }
@defmethod*[([(first-opened [settings settings]) void?])]{ @defmethod[(first-opened [settings settings]) void?]{
This method is called after the language is initialized, but This method is called after the language is initialized, but
no program has yet been run. It is called from the user's no program has yet been run. It is called from the user's
@ -637,8 +637,8 @@ eventspace's main thread.
See also See also
@method[drracket:rep:text% initialize-console]. @method[drracket:rep:text% initialize-console].
Calling this method should not raise an exception (or otherwise Calling this method should not escape.
try to escape). DrRacket calls this method in a @racket[parameterize] DrRacket calls this method in a @racket[parameterize]
where the @racket[error-escape-handler] is set to an escaping where the @racket[error-escape-handler] is set to an escaping
continuation that continues initializing the interactions window. continuation that continues initializing the interactions window.
Thus, raising an exception will report the error in the user's Thus, raising an exception will report the error in the user's
@ -646,7 +646,14 @@ interactions window as if this were a bug in the user's program.
Escaping in any other way, however, can cause DrRacket to fail Escaping in any other way, however, can cause DrRacket to fail
to start up. to start up.
Contrary to the method contract space, DrRacket will also invoke this Also, IO system will deadlock if the @racket[first-opened] method
does IO on the user's IO ports, so the calling context of
@racket[first-opened] sets the @racket[current-output-port] and
@racket[current-error-port] to ports that just collect all of the
IO that happened and then replay it later in the initialization of the
user's program.
Contrary to the method contract spec, DrRacket will also invoke this
method if it has zero arguments, passing nothing; the zero argument method if it has zero arguments, passing nothing; the zero argument
version is for backwards compatibility and is not recommended. version is for backwards compatibility and is not recommended.
@ -932,7 +939,7 @@ the settings for this language.
} }
@defmethod[(on-execute [settings settings] @defmethod[(on-execute [settings settings]
[run-in-user-thread ((-> any) -> any)]) [run-on-user-thread ((-> any) -> any)])
any]{ any]{
The @scheme[on-execute] method is called on DrRacket's The @scheme[on-execute] method is called on DrRacket's
eventspace's main thread before any evaluation happens eventspace's main thread before any evaluation happens
@ -1015,13 +1022,16 @@ that error message into the definitions window.}
] ]
The @scheme[run-in-user-thread] arguments accepts thunks and The @scheme[run-on-user-thread] arguments accepts thunks and
runs them on the user's eventspace's main thread. These runs them on the user's eventspace's main thread.
thunks must not raise an exceptions (or DrRacket itself will The output ports are not yet
get stuck). In addition, the output ports are not yet
functioning, so print outs should be directed to the functioning, so print outs should be directed to the
original DrRacket output port, if necessary. original DrRacket output port, if necessary.
This thunk is wrapped in a @racket[with-handlers] that
catches all exceptions matching @racket[exn:fail?] and
then prints out the exception message to the original
output port of the DrRacket process.
} }
@defmethod[(order-manuals [manuals (listof bytes?)]) @defmethod[(order-manuals [manuals (listof bytes?)])

View File

@ -111,7 +111,9 @@ The @scheme[complete-program?] argument determines if the
how it finishes). how it finishes).
} }
@defmethod[#:mode augment (on-execute [run-on-user-thread (-> any)]) any]{ @defmethod[(on-execute [run-on-user-thread (-> any)]) any]{
Use @scheme[run-on-user-thread] to initialize the user's parameters, etc.
Called from the DrRacket thread after the language's Called from the DrRacket thread after the language's
@method[drracket:language:language<%> on-execute] @method[drracket:language:language<%> on-execute]
@ -119,7 +121,11 @@ The @scheme[complete-program?] argument determines if the
special values have been setup (the ones registered special values have been setup (the ones registered
via @scheme[drracket:language:add-snip-value]). via @scheme[drracket:language:add-snip-value]).
Use @scheme[run-on-user-thread] to initialize the user's parameters, etc. Do not print to @racket[current-output-port] or @racket[current-error-port]
during the dynamic extent of the thunk passed to @racket[run-on-user-thread] becuase
this can deadlock. IO is still, in general, fine, but the @racket[current-error-port]
and @racket[current-output-port] are set to the user's ports that print
into the interactions window and are not in a good state during those calls.
} }