MAINSAIL STREAMS User's Guide, Chapter 7

previous   next   top   contents   index   framed top   this page unframed


7. Interprocess Communication: Socket Streams

Socket streams are used for two-way interprocess communication. They do not have semantics normally associated with TTY streams: no echo occurs, there are no special keyboard interrupt characters, and there is no explicit flow control or baud rate.

The communicating processes must be aware that they are communicating with another process and they must agree on a protocol for exchange of information, including who speaks first, how the conversation is terminated, what kind of buffering is used, the required size of the buffers, what kind of objects are written (e.g., characters or storage units), and what those objects mean. This communications protocol is distinct from and “built on top of” the underlying network protocol used to carry the bytes between the processes.

Creation of sockets for client-server communication is described in Section 7.3. Creation of child processes is described in Section 7.4.

A high-level facility for remote MODULE creation that does not require the user to deal explicitly with interprocess communication is described in Chapter 5.

7.1. Meaning of $eos on Socket Streams

On both input and output, $eos means that process at the other end has broken (hung up) the connection.

7.2. Socket-Specific Fields of $stream

The socket-specific fields of $stream are shown in Table 7–1.

Table 7–1. Socket-Specific Fields of the Stream Record
STRING  $myHostName,    # Our host's name
        
$hisHostName,   # Other end's host's name
        
$serverName,    # The server we are talking toor
                        # "" 
if we are not talking to a
                        # 
server.
        
$protocolName;  # The network protocol nameor ""
                        # 
if the socket is a simulated
                        # 
internal socket.

7.3. Servers and Clients: Network Protocol MODULEs and the SERVICE Stream Prefix

This section describes a low-level interface to server/client communication. Before a server can be run, an entry must be made in the services table as described in Appendix A.

It should rarely be necessary for the typical programmer to write a low-level server; the RPC facilities described in Chapter 5 should be sufficiently general for most applications and are much simpler to use than the facilities described in this chapter.

7.3.1. Terminology and Conventions

A “service” is a set of functions available under a global (system- or network-wide) name; like a file name, the service name can be used by any process that wants to make use of the service. The service functions are available through a “high-level protocol” (defined by the author of the service) that specifies the semantics of data transferred to and from the service. The data are transmitted according to a “low-level protocol” (or “network protocol” or just “protocol”) specified by the operating system; a given service may be available only through a single low-level protocol or through several different low-level protocols.

A “server” is a process that provides one or more services. When the process wishes to start handling the functions of a given service, it must make calls to the operating system to inform it that is ready to do so. In MAINSAIL, this is done with the $bindService and $acceptClient calls.

A “port” is a handle (used on the server end) that represents an active service. It is represented by a POINTER to a record of the MAINSAIL CLASS $port. In a process that wishes to become a server for a particular service, the normal sequence is to verify that it is legal to become a server for that service by calling $bindService, and then, if $bindService returned a non-Zero port, to pass the port to $acceptClient. $acceptClient is typically called in a loop; each time it produces a stream, a new coroutine is created to talk through the stream.

A “client” is a process that makes use of a service. It communicates with the service through a stream. It can use the stream prefix SERVICE to request a connection to the service on any available low-level protocol, or it can use a stream MODULE named for a low-level protocol to specify the low-level protocol (this may be useful where a service supports more than one low-level protocol).

A “connection” is the two-way channel between a client and a service. It is represented as a stream on each end; the server stream is obtained from the call to $acceptClient. If a server expects to receive more than one connection at a time, it must set up a scheduled coroutine for each possible connection.

7.3.2. Services Table

A global table of registered services, the services table, is maintained on each node. All services must be registered in this table before they can be used. The possible table formats are described in Section A.1.

7.3.3. The Client End

Example 7–2. Client Access to a Service
$openStream(st,"service>serviceName");

    
or:

$openStream(st,"service(host)>serviceName");

Clients may open a stream to a service using the stream prefix SERVICE. At a minimum, the service name must be provided. The host name may also be given. If the host name is omitted, $openStream automatically determines the host on which the service is available. SERVICE automatically maps to an appropriate network protocol MODULE, e.g., TCP. The available network protocol MODULEs are listed in Appendix B.

Examples:

The network protocol MODULE to use to talk to the service may be given instead of using the generic SERVICE stream MODULE, although it is rarely necessary to be so specific:

NOTE: Up until MAINSAIL Version 15.14, the implementation required the following syntax to specify a host name:

service>host:serviceName
tcp>host:serviceName

instead of:

service(host)>serviceName
tcp(host)>serviceName

This obsolescent syntax is still supported for UNIX, but is not portable, and its use in new code is discouraged.

7.3.4. The Server End

Figure 7–3. Server PROCEDUREs
STRING
PROCEDURE   $myHostName;

STRING
PROCEDURE   $canonicalHostName
                        (
STRING hostName);

STRING
PROCEDURE   $canonicalUserID
                        (
STRING userID,
                             
hostName);

INTEGER
PROCEDURE   $getHosts   (STRING serviceName;
                         
MODIFIES STRING ARRAY(1 TO *)
                             
hosts;
                         
PRODUCES OPTIONAL STRING eMsg);

INTEGER
PROCEDURE   $getProtocols
                        (
STRING hostAndServiceName;
                         
MODIFIES STRING ARRAY(1 TO *)
                             
prots;
                         
PRODUCES OPTIONAL STRING eMsg);

POINTER($port)
PROCEDURE   $bindService
                        (
STRING protocolAndServiceName;
                         
OPTIONAL LONG INTEGER timeOut;
                         
OPTIONAL BITS ctrlBits;
                         
PRODUCES OPTIONAL STRING eMsg);

LONG INTEGER
PROCEDURE   $unbindService
                        (
MODIFIES POINTER($portp;
                         
OPTIONAL LONG INTEGER timeout;
                         
OPTIONAL BITS ctrlBits);

LONG INTEGER
PROCEDURE   $acceptClient
                        (
POINTER($portp;
                         
PRODUCES POINTER($streamst;
                         
OPTIONAL LONG INTEGER timeout;
                         
OPTIONAL BITS ctrlBits);

BOOLEAN
PROCEDURE   $parseHostServiceName
                        (
STRING name;
                         
PRODUCES STRING host,service;
                         
PRODUCES OPTIONAL STRING eMsg);

A server typically uses $getProtocols to discover the network protocols it is expected to support for each service. It then starts a separate coroutine to handle each protocol/service pair. For each such pair, the server uses $bindService to bind itself to a port for the protocol and service and repeatedly calls $acceptClient to accept new clients on that port. Whenever $acceptClient returns a new stream, a new coroutine is created to talk through that stream and perform the service functions on the connection represented by the stream.

$myHostName returns the host name of the current host (this is the canonical name as specified in the services table). If the host name is not available, $myHostName returns the empty STRING. The $myHostName field of a socket stream st gives the host name of the system on which the program is running, i.e.:

st.$myHostName = $myHostName

$canonicalHostName returns the official name of the host named by hostName according to the MAINSAIL services table. It returns the original STRING if hostName is not in the table.

$canonicalUserID takes a STRING userID (as returned from the $userID system PROCEDURE) and a STRING hostName (as returned from the $myHostName STREAMS system PROCEDURE) and returns a user ID STRING unique at the current site, provided an appropriate entry has been set up in the XIDAK services table.

The user ID is mapped by an entry in the XIDAK services table. Currently, only the mslhosts format of the services table allows such an entry; you cannot use $canonicalUserId if you are using the xidakservices format of the table (see Section A.1). The mslhosts entry format is:

USERID canonicalName userID1/hostName1 ... userIDn/hostNamen

$canonicalUserID returns canonicalName if userID equals userIDi and hostName equals hostNamei. If there is no entry for the userID/hostName pair in the services table, $canonicalUserID returns userID.

$getHosts returns the number of host names that support the service serviceName; the actual host names are in the ARRAY hosts (so that the return value from a successful call to $getHosts is hosts.ub1). Using $getHosts, a server may determine if any other copies of itself might be running on other systems. $getHosts is also used internally when a client opens a stream with a name of the form service>xxx (where xxx is a service). In this case, if the service xxx is uniquely available on a single host, the host name is filled in automatically. $getHosts returns 0 with eMsg set to an appropriate error message if an error occurs.

$getProtocols returns the network protocols through which hostAndServiceName may be accessed from the current host. The call is provided for servers so they can set themselves up to service the appropriate set of network protocols.

$getProtocols modifies the input ARRAY (allocating it if NULLARRAY is passed) to contain all of the network protocols required to support the specified service on the specified host, and it returns the number of such protocols in the ARRAY. The information returned by $getProtocols is currently derived from the services table. If no protocols are available, $getProtocols returns 0, with eMsg set to an appropriate error message. hostAndServiceName may take one of the forms shown in Table 7–4.

Table 7–4. Valid hostAndServiceName Values ($getProtocols)
hostAndServiceName Returned Network Protocols
"" All that the current host speaks
"serviceName" All under which "serviceName" is available on the current host
"(host)>serviceName" All under which "serviceName" is available on the host with the given name
Note 1: The host name may be a valid MAINSAIL identifier, or it may be an internet address (e.g., "192.9.200.201").
Note 2: The service name is assumed to be a valid MAINSAIL identifier.

$bindService accepts a single network protocol and service name and returns a POINTER to a port. A server typically binds itself to one port for each network protocol that the host system supports.

The protocolAndServiceName argument has the format:

protocol>serviceName

The protocol> part is optional. If the protocol is omitted, the protocol defaults to the first one listed in the services table for the named service on the current host. The available protocol MODULEs are listed in Appendix B.

Figure 7–5 shows the user-visible fields of a $port record. The service name and host name are those resolved by the $bindService call. $lastError is set by $acceptClient if an error return is made.

Figure 7–5. Fields of $port
CLASS $port (
    
STRING
        
$serviceName,  # As supplied to/resolved by $bindService
        
$hostName;     # "    "      "    "      "       "
    
STRING
        
$lastError;    # Set by $acceptClient
);

If the service cannot be bound, $bindService returns NULLPOINTER with eMsg set to an appropriate error message. If timeout is a positive number, the call returns when that many milliseconds have elapsed. Specify a timeout value of $block to wait indefinitely.

The $unbindService PROCEDURE is used if the server decides to stop accepting clients on a port.

The possible return values from $unbindService are described in Section 10.1.2.

The $acceptClient PROCEDURE accepts a port (returned by $bindService) and produces a stream. It blocks until a client requests a connection to the server on the port p (the client uses $openStream to open its end). The server then talks to the client through the stream produced by $acceptClient. At the end of the interaction, the server and the client each call $closeStream on their respective streams to terminate the connection. $acceptClient returns standard stream error codes; i.e., $success($bindService(...)) returns TRUE if the service can be bound, otherwise FALSE, with p.$lastError set to an error message STRING. On a FALSE return, the value is either $error or $timedOut, if a positive timeout value (milliseconds) was given. Specify $block for timeout if the PROCEDURE should wait indefinitely.

A call to $acceptClient can raise the exception $tooManyOpenFilesStr if it is impossible to accept the client because no more stream handles are available. If the exception is handled by falling out of the handler, the call is aborted. If the exception is propagated, an error message is issued. If a handler of this exception does a $raiseReturn, it is assumed that the handler closed some files, so the $acceptClient is attempted again.

$parseHostServiceName parses a name into the STRINGs host and service, handling quoted names and the fact that the host name may be omitted. This PROCEDURE is rarely needed by user code. It returns TRUE if successful, FALSE with eMsg set to an explanation if it cannot parse the name.

7.3.5. STREAMS PROCEDUREs Supported by xidakservices

xidakservices supports three PROCEDUREs that provide information about available services:

Figure 7–6. Server PROCEDUREs Supported Only by xidakservices
INTEGER
PROCEDURE   $getRemoteModules
                        (
STRING hostAndServiceName;
                         
MODIFIES STRING ARRAY(1 TO *)
                             
mods;
                         
PRODUCES OPTIONAL STRING eMsg);

STRING
PROCEDURE   $getServerProperty
                        (
STRING hostAndServiceName;
                         
STRING propertyName;
                         
PRODUCES OPTIONAL STRING eMsg);

INTEGER
PROCEDURE   $getServersForRemoteModule
                        (
STRING remoteModuleName;
                         
MODIFIES STRING ARRAY(1 TO *)
                             
servers;
                         
PRODUCES OPTIONAL STRING eMsg);

These PROCEDUREs are similar to the routines $getHosts and $getProtocols in that they return information from the services table. They are not implemented for any lookup method except xidakservices (see Section A.1).

$getRemoteModules modifies mods to contain the names of the remote MODULEs supported by the server hostAndServiceName (according to the entry in xidakservices). It returns:

n where n is the number of remote MODULEs supported by hostAndServiceName if hostAndServiceName is found.
0 with eMsg NEQ "" if the service is not found or the service is ambiguous.
0 with eMsg = "" if the service has no remote MODULEs.

$getServerProperty gets the value of the property propertyName for the server hostAndServiceName. It returns:

the value if the value is found.
"" with eMsg NEQ "" if the server name is not found or the server name is ambiguous.
"" with eMsg = "" if the server does not have the property or the property's value is the empty STRING.

Both $getRemoteModules and $getServerProperty accept hostAndServiceName in either the form serviceName or the form hostName:serviceName. Omitting the host works only if the service is unique to one host; i.e., the same rules are applied as when you call:

$openStream(st,"service>" & hostAndServiceName,...);

or:

$newRemoteModule(hostAndServiceName,...);

$getServersForRemoteModule returns an ARRAY of multiclient servers in the xidakservices file that support a given remote MODULE. This PROCEDURE is intended to allow an application program to display a list of possible servers to a user.

SERVICE entries in the xidakservices table with an unspecified host name are returned as a single ARRAY element containing only the service name, rather than attempting to obtain the name of every host in the network and creating multiple ARRAY elements for them. Accessing such a server without prepending the host name gives an error message because service names without a host are ambiguous.

Currently, the only use MAINSAIL STREAMS and RPC make of the SUPPORTS clause in the SERVICE entry is to implement this PROCEDURE for the benefit of MAINSAIL applications.

Currently, C applications must parse $SERVICEDIR/xidakservices themselves if they want the information provided by these three MAINSAIL PROCEDUREs.

7.3.6. The Services Table

The server/client implementation currently depends on the availability of a file describing the services available on each host. The logical name (xidak services) or (service protocol table) (depending on the table format used) must be defined using a MAINEX ENTER subcommand in the MAINSAIL startup command file to specify the file containing the table.

The services table may eventually be replaced by a XIDAK name server. See Appendix A.

7.4. Child Process Creation: PROCESS and PTYPRO

In the parent/child process control model, a parent process creates one or more child processes to do its bidding. The children may in turn become parents of additional processes.

When the parent process exits, the children processes (and all of their children) are usually terminated if they are still active.

The parent/child process control model is similar to the scheduled coroutine model. In fact, it is possible to write programs that communicate through a stream with some other entity that is either a separate process or another coroutine in the current process.

7.4.1. Starting a Child Process Using PROCESS

Child processes are started using the $openStream PROCEDURE and the stream MODULE PROCESS. The name of the stream has the form:

process{(hostName)}>programName {programArgs}<eol>
{
userName}<eol>
{
password}<eol>
{
startupDirectory}<eol>
{
numberOfEnvironmentVariables}<eol>
{
environmentVariable1}<eol>
   .
   .
   .
{
environmentVariablen}

(where the curly brackets indicate optional elements; they are not part of the syntax).

On UNIX, an obsolescent alternate syntax allows the first line to be replaced with:

process>{hostName:}programName {programArgs}<eol>

or:

socpro>{hostName:}programName {programArgs}<eol>

but the UNIX syntax is not portable to other operating systems; its use in new code is discouraged.

The bracketed items are optional. Trailing blank lines can be omitted.

A new process is started on the current host by running the program stored in the file named by programName. If a host name is specified, the new process is started on that host instead of the current system. The gensrv server process must be running on the specified host if a host name is given.

PROCESS examines the ENTER and SEARCHPATH subcommands specified in the parent process and maps programName accordingly. On UNIX, it also uses the parent process's PATH environment variable.

If programArgs is given, those arguments are made available to the program, provided that program arguments are supported by the operating system.

The process is started with the same user ID as the current process, unless userName is given, in which case it is used to set the user ID for the process. password is required if the current process would not normally have the right to run as the specified userName.

Note: you must be superuser in order to specify non-null userName and password parameters.

If startupDirectory is specified, it becomes the current directory for the new process; otherwise, the new process's current directory is the same as the parent process's (if on the same machine) or the home directory for the user ID (if on a remote machine).

Environment variables can be passed to the child process. numberOfEnvironmentVariables is the number (in textual form) of environment variable lines that follow. Each environment variable line has the form:

VARIABLENAME=value

For example, to create the process named thisProgram, with arguments hello and goodbye, running under the user ID george, connected to /user/dir, with the environment variables ENV1 set to /usr/baz and ENV2 set to hostname, you would open the following stream:

$openStream(st,
    "
PROCESS>thisProgram hello goodbye" & eol &
    "
george" & eol &
    "
george's password" & eol &
    "/
user/dir" & eol &
    "2" & 
eol &
    "
ENV1=/usr/baz" & eol &
    "
ENV2=hostname");

In addition, programName itself can be an environment variable of the current program if it begins with a dollar sign on UNIX, or if it is surrounded by percent signs on Windows (other systems may follow other conventions for environment variables). For example, if the environment variable environVar is defined as foo, then on UNIX:

$openStream(st,"PROCESS>$environVar");

or on Windows:

$openStream(st,"PROCESS>%environVar%");

is equivalent to:

$openStream(st,"PROCESS>foo");

The $openStream call returns a socket stream that talks directly to the child's $tty stream. Thus, for the child, $isaTty($tty) returns FALSE. Input for the child may be supplied by calling $writeStream, and output from the child may be read using $readStream.

Characters written to the process stream are not echoed back. This form of process control is useful for direct interprocess communication. It is very similar to the use of pipes on UNIX.

7.4.2. Starting a Child Process Using PTYPRO

A child process may also be started using the $openStream PROCEDURE and the stream MODULE PTYPRO. The name of the stream is similar to that for PROCESS; it has the form:

ptypro>programName {programArgs}<eol>
{
userName}<eol>
{
password}<eol>
{
startupDirectory}<eol>
{
numberOfEnvironmentVariables}<eol>
{
environmentVariable1}<eol>
   .
   .
   .
{
environmentVariablen}

A new process is started on the current host by running the program stored in the file named programName. If a host name is given, the gensrv server process must be running on it. If neither programName nor programArgs is given, and the system supports running a “shell” as a child process, the standard system shell is run. The remaining arguments are optional and are interpreted as for PROCESS.

The $openStream call returns a PTY stream to talk to the child's $tty. Thus, for the child, $isaTty($tty) is TRUE. Talking to the PTY is much like sitting at a keyboard in front of a terminal screen. Characters written to the stream are echoed back, if the running program wants them to be.

This kind of connection has a specialized but useful application for system shells and windowing systems. The program that creates the process (e.g., the shell or window manager) serves merely as the intermediary between the actual user and the created process.

Special considerations of the PTY stream type are discussed in Section 11.9.

Many systems do not provide general pseudoterminal support. On such systems, PTYPRO is not available.

7.4.3. Starting a MAINSAIL Child Process

Figure 7–7. $executableBootName
STRING
PROCEDURE   $executableBootName
                        (
OPTIONAL STRING hostName);

$executableBootName returns the name of the standard MAINSAIL executable bootstrap on hostName (on the local system if hostName is omitted). It returns the empty STRING if the information is not available.

A MAINSAIL child process can be started on the current system with a stream name of the form:

"process>" & $executableBootName

If the operating system supports command line arguments, a particular MAINSAIL MODULE can be started in a child process using a stream name of the form:

"process>" & $executableBootName & " " & modName

where modName is the desired MAINSAIL MODULE.

Implementation Note

$executableBootName is currently implemented using a logical file name. $executableBootName calls lookUpLogicalName with the argument:

"(executable boot " & hostName &")"

For this call to succeed on systems other than the current host, the installer of MAINSAIL must define the standard bootstrap names of all host systems in MAINEX ENTER subcommands (typically in the local site.cmd file). The implementation may be changed in the future to use a more flexible mechanism.

7.4.4. Establishing an Additional Control Stream to a Cooperating Child

A cooperating child process is a program designed to talk to a parent, much as a server process is designed to talk to a client. It is written to use a predefined communications protocol for exchange of information with its parent.

Typically, the cooperative communication with the child takes place over a control stream that is separate from the child's $tty. This control stream is available to the child as the STREAMS system variable $parent and may be established by the parent using the two-POINTER form of $openStream, e.g.:

$openStream(childTty,childControl,"process>.....");

Typically, childTty is used by the child only for error messages and when the debugger is invoked in the child. The childControl stream is typically used for all cooperative communication between parent and child.

7.5. Terminating a Child Process

A child process created with $openStream may be terminated by closing the stream with $closeStream. If the process terminates by itself before the $closeStream call, the currently active or next I/O call to the process's stream returns with an error.

7.6. Process Control

If the started process is a cooperating child program designed to run as a child process, the parent and child can use a communications protocol of their choice. Any stream I/O mechanism may be used, including $packet I/O.

If the started process is a noncooperating child program connected to a PTY stream, a special character may be sent to the process to cause it to interrupt. This character is provided as a field of the CLASS $stream (see Chapter 11). Alternatively, the PROCEDURE $writeStreamInterrupt may be used to interrupt a noncooperating child process. If the child process has enabled interrupt handling, it may do something special based on the interrupt. Otherwise it will probably die and further reads and writes to its stream will return an error. This latter behavior can be the only effective way to kill a child of a child process on systems (e.g., if you run a process through rlogin on UNIX, the rlogin process can be terminated using $closeStream, but not the remote process started by rlogin).

No other process control is currently provided.

7.7. Examples

7.7.1. Sprouting a Print Job and Waiting for It to Exit

The simplest interaction with a child process is running a noninteractive program and waiting for it to complete. This form of child process control does not require scheduling.
Example 7–8 is an example of invoking a print program directly from a MAINSAIL program.

Example 7–8. Sprouting a Print Job and Waiting
PROCEDURE printFile (STRING name);
BEGIN
STRING s;
POINTER($streamchild;

$openStream(child,"process>print " & name);
WHILE $success($readStream(child,s,errorOK)) DO
    
write(logFile,s);
$closeStream(child);
END;

printFile uses a program called print to print a file given its name. Any terminal output generated by the print program is written to its parent process's logFile. When the print program terminates, $readStream returns an end-of-stream and the child stream is closed.

This simple example does not require scheduling of advanced STREAMS for the system on which it is run. Note, however, that the example makes no provision for input to the child. If the child program attempts to read from its terminal, it blocks until some input is supplied. The parent is also blocked in $readStream waiting for the child to output or terminate, so both processes may block indefinitely.

7.7.2. Sprouting an Interactive Child

The more general case of handling an interactive child process, that is, a process that performs terminal input, is shown in Example 7–9. The child is started much the way a program is started by a system shell. All of the child's output goes to the parent's terminal, and input from the parent's terminal goes to the child. This form of child process control requires scheduling.

runCmd runs a child program. The child's input and output are connected to the stream returned by $openStream, so the parent must handle both using two independent coroutines. reader is started as a coroutine to read what the child writes and output it to the parent's $tty. writer is started as a coroutine to read from the parent's $tty and write the keystrokes to the child process.

The parent might also perform processing of the I/O to the child, such as logging the child's output or supplying its input from a file. Also, typically the parent will want to enable and handle keyboard interrupts and pass them on to the child. Neither of these possibilities is shown.

If either the reader or writer detects the end of the child process, both the reader and writer are killed and runCmd returns to its caller in the parent.

Example 7–9. Sprouting an Interactive Child
POINTER($streampty;
POINTER($coroutinercwc;

DEFINE print(x) = [$writeStream($tty,x,$line)];

PROCEDURE reader;
BEGIN
STRING s;
WHILE $success($readStream(pty,s,errorOK)) DO print(s);
IF wc AND NOT $killedCoroutine(wcTHEN
    
$killCoroutine(wc);
$reschedule(delete);
END;



PROCEDURE writer;
BEGIN
STRING s;
DO $readStream($tty,s)
    
UNTIL NOT $success($writeStream(pty,s,errorOK));
IF rc AND NOT $killedCoroutine(rcTHEN
    
$killCoroutine(rc);
$reschedule(delete);
END;


PROCEDURE runCmd (STRING args);
BEGIN
STRING st;
OWN INTEGER nRun;
IF NOT $openStream(pty,"ptypro>"&args,errorOK,sTHENB
    
printError(s); RETURN END;

$HANDLEB
    
startupnRun.+1;
    
s := "RunReader" & cvs(nRun);
    
t := "RunWriter" & cvs(nRun.+1);
    
rc := $createCoroutine(thisDataSection,"reader",s);
    
$queueCoroutine(rc);
    
wc := $createCoroutine(thisDataSection,"writer",t);
    
$queueCoroutine(wc);
    
$waitForDescendants;
    
$closeStream(ptyEND
$WITHB
    
IF $exceptionName = $abortProcedureExcpt THENB
        
IF pty THEN $closeStream(pty); cleanup END;
    
$raise END;
END;


previous   next   top   contents   index   framed top   this page unframed

MAINSAIL STREAMS User's Guide, Chapter 7