[Openmcl-devel] *terminal-io* issues
gb at clozure.com
Sat Jul 12 17:22:50 EDT 2008
The global, static value of *TERMINAL-IO* is a TWO-WAY-STREAM whose
input comes from file descriptor 0 and whose output goes to file
descriptor 1. Ordinarily (when not running as a standalone .app),
these file descriptors are pipes or sockets or ptys that have some
other process (a terminal program, emacs) on the other end of the
connection. Other standard streams are initially SYNONYM-STREAMS
to *TERMINAL-IO*; when a thread starts up, it does something like:
(let* ((*terminal-io* *terminal-io*)
A listener thread in the IDE will (slightly) later do something
(setq *terminal-io* (stream-associated-with-listener-window))
and the synonym-streams to *TERMINAL-IO* will also use the
listener-window stream in that thread.
In general, if a number or threads are using the same I/O stream,
output to that stream can be messy (unless those threads adopt
some higher-level protocol to decide who writes when), but it's
generally tractable (at least in that low-level locking occurs
and all output from all streams will eventually get there, in
some order.) If two or more threads try to read from an input
stream at the same time, it's generally not possible to ensure
that a thread will receive input intended for it unless some
higher-level protocol is used. (In the case of CCL and input
from *TERMINAL-IO*, the protocol is the one described in
In a standalone .app, these file descriptors are set up differently
(and less ... usefully).
If we list open files in the (os-level) process, we see:
[~] gb at dervish> lsof -p 53396
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
[... lots of other things ...]
dx86cl64 53396 gb 0r CHR 3,2 0t0 66958340 /dev/null
dx86cl64 53396 gb 1u REG 14,2 0 6979158 /private/tmp/logfilextHibS
dx86cl64 53396 gb 2u REG 14,2 0 6979158 /private/tmp/logfilextHibS
e.g., file descriptor 0 is open on /dev/null, and descriptors 1 and 2
are redirected to a temporary file. So, the initial (global, static)
value of *TERMINAL-IO* in a standalone .app isn't very interesting
or useful: the process as a whole has no useful standard input and
non-interactive standard output and error. A background thread
doesn't really have a good way of interacting with the user (via
a break loop), so it announces that it needs access to the input
side of *TERMINAL-IO*, then waits (forever) for the privilege of
reading from /dev/null. In the long term, this (the useless
global value of *TERMINAL-IO*) needs a good solution.
One short-term, not-entirely-bad solution (when one can anticipate
the need to debug background threads) is to run the IDE without
letting LaunchServices redirect its standard I/O. (You get the
same effect by doing (require "COCOA"), but this way starts up
I did this in a shell; you can certainly do it in an Emacs shell
buffer, and there may be better ways.
[src/ccl-dev] gb at dervish> Clozure\ CL.app/Contents/MacOS/dx86cl64
And the IDE should start up, with file descriptors 0,1,and 2 still
attached to the "terminal".
In the IDE, I did:
? (process-run-function "loser" (lambda () (error "I've fallen, and I can't get up!")))
#<PROCESS loser(6) [Active] #x3000417DCBCD>
and back in the terminal, I see:
> Error: I've fallen, and I can't get up!
> While executing: #<Anonymous Function #x3000417DD29F>, in process loser(6).
;;; #<PROCESS loser(6) [Active] #x3000417DCBCD> requires access to Shared Terminal Input
In this environment, the thread that "owns" the input side of *TERMINAL-IO*
is the initial thread, which happens to be busy running the Cocoa event
loop. We'd like to interrupt it, so that we can "yield" ownership of
A ^C (or SIGINT signal) ordinarily has the effect of forcing a break
loop in whatever process owns the terminal input resource. (SLIME
and other things can specify some other thread to receive ^C interrupts,
but unless we started the ide in some way that involved SLIME, we
can ignore that issue for now.) If I type a ^C in the terminal, I
^C> Break: interrupt signal
> While executing: GUI::HANDLE-INVOKING-LISP-FUNCTION, in process Initial(0).
> Type :GO to continue, :POP to abort, :R for a list of available restarts.
> If continued: Return from BREAK.
> Type :? for other options.
1 > :proc
6 : loser [semaphore wait] (Requesting terminal input)
5 : Listener [Toplevel Read]
4 : housekeeping [Active]
0 : -> Initial [Active]
1 > :y 6
;;; Shared Terminal Input is now owned by #<PROCESS loser(6) [Active] #x3000417DCBCD>
> Type :POP to abort, :R for a list of available restarts.
> Type :? for other options.
1 > :b
(16F13EA8) : 0 (FUNCALL #'#<Anonymous Function #x3000417DD29F>) 37
(16F13EB8) : 1 (RUN-PROCESS-INITIAL-FORM #<PROCESS loser(6) [Active] #x3000417DCBCD> '(#)) 717
(16F13F48) : 2 (FUNCALL #'#<(:INTERNAL %PROCESS-PRESET-INTERNAL)> #<PROCESS loser(6) [Active] #x3000417DCBCD> '(#)) 397
(16F13F98) : 3 (FUNCALL #'#<(:INTERNAL THREAD-MAKE-STARTUP-FUNCTION)>) 293
1 > :q
;;;control of Shared Terminal Input restored to #<APPKIT-PROCESS Initial(0) [Active] #x30004042C30D>
1 > :go
and we're back in the event loop.
Better than nothing. Perhaps not a whole lot better, but better.
On Sat, 12 Jul 2008, Raffael Cavallaro wrote:
> I've been running into problems where multiple concurrent threads
> need access to *terminal-io*. What is the workaround for this - should
> I locally rebind *terminal-io* for each thread? If so, to what - some
> newly created stream?
>  specifically, in the IDE, the console window pops up informing me
> for each newly created thread that it requires access to shared
> terminal input, shortly thereafter the IDE hangs and must be force
> quit, or it just dies by itself, usually the former. Often the IDE
> locks up before the console window can display any text, but you can
> see what It would have displayed in the system log (Console.app, eg.).
> Raffael Cavallaro, Ph.D.
> raffaelcavallaro at mac.com
> Openmcl-devel mailing list
> Openmcl-devel at clozure.com
More information about the Openmcl-devel