ORIGIN '~beta/basiclib/basicsystemenv'; LIB_DEF 'basicshell' '../lib'; (* * COPYRIGHT * Copyright Mjolner Informatics, 1992-98 * All rights reserved. * * Use xshell or shell as origin for distributed BETA programs. *) BODY 'private/shellBody'; INCLUDE '~beta/objectserver/ObjectSerializer'; INCLUDE '~beta/sysutils/envstring'; INCLUDE '~beta/process/commaddress'; INCLUDE 'private/rpc_interface'; --- lib:attributes --- (* GETSHELLENV * * Returns the unique shellEnv instance running. *) getShellEnv: (# theShellEnv: ^shellEnv; do shellEnv## -> objectPool.strucGet (# init::< (# do (failure, 'Program:descriptor must be a subpattern of shellEnv') -> stop #) #) -> theShellEnv[]; exit theShellEnv[] #); (* SHELLENV * * When making distributed BETA programs, the "program:descriptor" * SLOT in betaenv must be filled with a subpattern of shellEnv. This * is also the only instance of shellEnv allowed. *) shellEnv: systemenv (# (* SHELLTYPE * * Furtherbind to specify the kind of Shell. *) shellType:< Shell; theShell: @shellType; (* SHELLENVLIB * * Pattern definitions used as interfaces to remote objects must * be declared in the shellEnvLib SLOT. * * References to objects being instances of subpatterns of these * patterns may be exported for remote access without being * declared in this attribute slot, but at least the superpattern * containing the entrys to be called remotely should be declared * in ShellEnvLib, to make them visible in the client. *) <<SLOT shellEnvLib:attributes>>; (* REMOTEABLE * * Superpattern of all objects that may be accessed remotely. * * Methods to be called remotely must be non-virtual and be * subpatterns of entry. * * ping returns true if the object is currently accessible and * false otherwise. *) remoteable: (# entry: (# do (if isProxy then ... else INNER if); #); ping: booleanValue (# do ... #); (* private *) ri: ^remoteInfo; isProxy: @Boolean; <<SLOT remoteablePrivateEntries:attributes>>; do INNER #); (* SHELL * * Pattern describing executables whose instances are processes. * Shells should only be instantiated through ensemble.createShell * or started from the commandline. * * myEnsemble is a reference to the ensemble on which this shell * is running. * * kill kills the corresponding process. The onKill virtual is * called before killing the process. * * The INNER part of a shell has to pause once in a while for * shellEnv to be able to handle incoming requests. * This is due to the non-preemptive multitasking used. * * Even if INNER terminates, the process will not terminate before * kill has been called. (Of course nasty signals may do the job. *) shell: remoteAble (# myEnsemble: ^ensemble; onKill:< Object; kill: (# do ... #); ShellPrivate: @...; <<SLOT shellPrivateEntries:attributes>>; do INNER; #); (* NAMESERVER * * Performs mapping between logical object names and object * references. Subpatterns may perform this mapping differently. * * put saves an object reference under the name given. The * overWrite virtual is called if an object of that name is * already registered. If overWrite returns true, the existing * (name,objectref) pair is overwritten with the new one. * * get looks for an object with the given name and type. If no * matching object is found, notFound is called. If an object * with the right name, but wrong type is found, quaError is * raised. * * remove undoes put. * * NameServer is a remoteAble, but all public operations does * some work locally before calling remote. *) NameServer: remoteAble (# elementType:< RemoteAble; put:< (# overWrite:< BooleanValue; name: ^Text; obj: ^elementType; enter (obj[], name[]) do INNER #); get:< (# notFound:< Notification; quaError:< Exception; name: ^Text; type: ##object; obj: ^elementType; enter (type##, name[]) do INNER exit obj[] #); remove:< (# notFound:< Notification; name: ^Text; enter name[] do INNER #); <<SLOT NameServerAttributes:attributes>>; do INNER #); (* ENSEMBLE * * A representation of network hosts. * * hostname is the hostname of the host represented. * * createShell allows creation of shells on the host represented. * * The executable name without path is given by the "execName" * parameter, and the expected type of the shell created by * "instances" of this executable is given by the "shellType" * parameter. * * appsDir is the directory where executables are expected to * be found. You should also check the description of the * "shellEnv.defaultAppsDir" virtual. * * screenName names the redirection file for screen output from * the new shell. If screenName is not furtherbound, screen * output is redirected to the file specified in the shellEnv * of the shell created. * * The execNotFound exception is raised if the executable could * not be found. * * processCreationFailed is raised if the process could not be * created. * * typeError is raised if the shell created does not have the * expected type. * * Other kinds of errors raises the unknownError exception. * * Furtherbind environment and call addEnvVar for each * environment variable to be added to the environment of the * new shell. * * Furtherbind parameters and call addParam for each command * line parameter to be given to the new shell. * * ns is a NameServer with default knowledge of the ensembles in * the distributed environment. It is therefore possible to * lookup other ensembles using ns. Apart from this, ns provides * a flat namespace in which objects may be saved and retrieved * using ns.put and ns.get. ns.scanNames may be used to iterate * over the names explicitly registered in the ensemble using * ns.get. * * DO NOT CREATE INSTANCES OF ENSEMBLE ON YOUR OWN!!. *) Ensemble: shell (# hostName: ^Text; createShell: (# appsDir:< (# dir: ^Text do defaultAppsDir -> dir[]; INNER; exit dir[] #); screenName:< (# name: ^Text; do INNER exit name[] #); environment:< (# addEnvVar: (# name, value: ^Text; enter (name[],value[]) do ... #); (* private: *) env: ^Text; envCount: @Integer; do INNER exit (env[],envCount) #); parameters:< (# addParam: (# value: ^Text; enter value[] do ... #); (* private: *) params: ^Text; paramCount: @Integer; do INNER; exit (params[],paramCount) #); execNotFound:< Exception (# do INNER; (if not continue then 'createShell: executable not found.' -> msg.append; if); #); processCreationFailed:< Exception (# do INNER; (if not continue then ...; if); #); typeError:< Exception (# do INNER; (if not continue then 'createShell: typeError.' -> msg.append; if); #); unknownError:< Exception (# do INNER; (if not continue then 'createShell: unknownError.' -> msg.append; if); #); shellType: ##Shell; execName: ^Text; sh: ^Shell; enter (shellType##,execName[]) do ... exit sh[] #); ns: @NameServer (# put:: (# do ... #); get:: (# do ... #); remove:: (# do ... #); scanNames: (# current: ^Text; do ... #); #); (* private: *) <<SLOT ensembleAttributes:attributes>>; ensemblePrivate: @...; #); (* ERRORHANDLER * * The "error" pattern is an abstract super pattern for all * communication exception virtuals. The virtual subpatterns of * error thus corresponds to different kinds of network errors. * * When an errorHandler exception is raised that is not further * specified, the exception is automatically propagated to the * previous handler in the dynamic call chain. This chain of * errorHandlers is built by pushing an errorHandler onto the * front of the chain when the errorHandler is entered. The * propagation of an exception continues until either some * handler catches the exception (by further binding the * corresponding errorHandler virtual), or until the * globalHandler is reached. If even the globalHandler does not * catch the error, default action is to kill the current shell * process. By default each coroutine has its own dynamic * errorhandler chain. If the top of this chain is reached, * control is passed to the global handler, and not, for example, * to the handler chain of the coroutine that forked the active * coroutine. * * If the entered errorHandler (prevHandler) is not NONE, it will * be used as the previous handler instead of the currently * active errorHandler. This may be used to transfer errors * between different coroutines. * * To gracefully handle network errors, further bind the * corresponding error virtual. Within further bindings, one of * the nested patterns "ignore", "continue" or "abort" should be * called as the last action of the errorHandler. Note that if * there are imperatives following the call to e.g. "continue", * these imperatives will not be executed! * * abort: DEFAULT!! If abort is called and not further * specified, the remote call that failed is * aborted, and the shell killed. However, to * prevent the shell from being killed, it is * allowed to further specify the abort, and do a * "leave someLabel" inside the further * specification. For example: * * do myLabel: errorHandler * (# connectionFailed:: * (# do abort (# do leave myLabel #)#); * do server.op1; * ... * server.opn; * #); * * IT IS NOT ALLOWED TO LEAVE AN ERROR VIRTUAL * OUTSIDE THE SCOPE OF AN ABORT INSTANCE!!! * * ignore: Abort the failing remote call, but pretend as * if the remote call succeded. Control flow * continues after the remote call causing the * error. For example: * * do errorHandler * (# connectionFailed:: (# do ignore #); * do server1.op1; * ign: server2.op2; * ... * #); * * If the "server.op1" remote call fails, control * flow continues at the "ign:" label. This may of * course result in rather strange program * behaviour. It makes no sense to further specify * the ignore pattern since it never calls INNER. * * continue: Retry or continue the operation that caused the * error. Fx. in the case of a timeout, continue * means that the communication subsystem will * wait once again for the number of seconds * specified in the timeOutValue virtual in * effect. * * The network errors handled by the errorHandler virtuals are * described below. * * connectionFailed is raised when we fail to send a message to a * remote shell. * * connectionBroken is raised when message send succeded, but the * connection to the remote shell was broken before an answer * could be received. * * timeOut is raised if the remote shell failed to answer within * the time limit specified by timeOutValue. Default timeOutValue * is to wait for ever for answer when doing remote * calls. Furtherbind to limit the allowed waitingtime. * * serverOverload is raised if the remote shell was busy and * therefore refused to handle the request. The number of * concurrently allowed requests is set by * globalErrorHandler.concurrentRequestLimit. * * unknownObject is raised if the remote shell did not know the * object requested. This is a consequence of the remote shell * doing a withDraw on the object requested. Thus unknownObject * corresponds to detection of a distributed dangling reference. * * unknownPattern is raised if one of the objects sent to the * remote host was an instance of a pattern unknown there * (local=FALSE), or if the pattern of a returned object was not * known locally (local=TRUE). In the case of unknown pattern it * makes no sense to retry the request. * * wrongAnswer is raised if the answer from the remote shell does * not have the expected format. This could mean that the remote * shell is not the one we think it is, i.e. it could be another * process at the same port. * * NOTICE!! It is not allowed to do a "leave" from within the * dopart of an errorHandler. If it is necessary to leave the * scope of an errorHandler, use the "leaveHandler" pattern as * follows: * * do someLabel: errorHandler * (# * do ...; * leaveHandler (# do leave someLabel #) * #); * * If multiple errorHandlers are left this way, use the * leaveHandler nested inside the outermost errorHandler in the * dynamic call chain. *) errorHandler: (# <<SLOT errorHandlerLib:attributes>>; (* ERROR * * is the abstract super pattern of all network related * exceptions. *) error: (# <<SLOT errorHandlerErrorLib:attributes>>; abort: failureAction (# ... #); continue: failureAction (# ... #); ignore: failureAction (# ... #); theObj: ^remoteAble; theEntry: ^remoteAble.entry; cleanup: ^EH_cleanup; enter (theObj[], theEntry[], cleanup[]) do INNER #); (* NETWORK EXCEPTIONS *) connectionFailed:< E_failed; connectionBroken:< E_broken; unknownObject:< E_unknownObj; unknownPattern:< E_unknownPat; timeOut:< E_timeOut; serverOverLoad:< E_overload; wrongAnswer:< E_answer; (* TIMEOUTVALUE * * Further bind and set "sec" in order to change the default * "wait for ever" policy. *) timeOutValue:< V_timeOut; V_timeOut: (# sec: @Integer do ... exit sec #); E_failed: error (# do ... #); E_broken: error (# do ... #); E_timeOut: error (# do ... #); E_overload: error (# do ... #); E_answer: error (# do ... #); E_unknownObj: error (# do ... #); E_unknownPat: error (# local: @Boolean enter local do ... #); (* private: *) failureAction: (# callInner: @Boolean do INNER #); EH_cleanup: (# toDo: @Integer; fa: ^failureAction; enter (toDo,fa[]) do INNER #); prevHandler: ^errorHandler; enterHandler: @...; leaveHandler: (# ... #); enter prevHandler[] do enterHandler; INNER; leaveHandler; #); (* GLOBALERRORHANDLER * * Furtherbind globalErrorHandler to specify a global * errorHandler. * * concurrentRequestLimit is the maximum number of simultaneous * requests this shell will allow. -1 means no limit. If handling * a request would result in breaking this limit, the request * will be ignored and the client-side exception overLoadError * will be raised. * * workerPoolSize determines the size of the pool of workers to * handle incoming requests. A worker is a collection of * resources needed to handle a request. Because it is cheaper to * reuse these resources than to allocate new ones, the * workerPool keeps track of unemployed workers ready for * reuse. A reasonable value for workerPoolSize is probably the * expected mean number of requests handled simultaneously. If a * request is always executed to end without suspending * implicitly of explicitly, a single worker is adequate. *) globalErrorHandler:< errorHandler (# concurrentRequestLimit:< IntegerValue (# do -1 -> value; INNER #); workerPoolSize:< IntegerValue (# do 5 -> value; INNER #); #); globalHandler: @globalErrorHandler; (* DEFAULTAPPSDIR * * When using ensemble.createShell to start processes, this is * the default directory on the remote host in which the * executable is expected to be found. The default may be * overridden using this virtual. Alternatively the directory may * be changed individually on each createShell call by * furtherbinding the ensemble.createShell.appsDir virtual. * * As is the case when specifying INCLUDE and BODY paths in the * BETA fragment system, you may use '$' to specify machine * dependent executable paths. That is, assume "dir" is assigned * the value '/mydir/$/', and the remote host on which the new * shell is to be created is of type 'sun4s'. Then, the * "execName" parameter to createShell is appended to "dir", and * all occurrences of '$' in the resulting string then replaced * by 'sun4s' before it is used as the full path of an * executable. * * By default, the defaultAppsDir directory is * * '/usr/local/lib/beta/distribution/aps/$/' * * but this may be changed either by the environment variable * BETALIB, having default value * * '/usr/local/lib/beta/' * * or by furtherbinding the defaultAppsDir virtual and assigning * to "dir". *) defaultAppsDir:< (# dir: ^Text do '$(BETALIB)' -> expandEnvVar (# defaultValue::< (# do '/usr/local/lib/beta/' -> envvarvalue[] #) #) -> dir[]; (if (dir.length -> dir.inxGet)<>'/' then '/' -> dir.append; if); 'distribution/aps/$/' -> dir.append; INNER; exit dir[] #); (* DEFAULTSCREENNAME * * ShellEnv instances created by ensemble.createShell cannot use * the standard outputs of the process. * * To redirect output you may specify the name of a file by * furtherbinding defaultScreenName. If you miss to do so, output * from remotely started shells is put into /dev/null. * Alternatively screenName may be set individually for created * shells by using the ensemble.createShell.screenName virtual. * This allows the creator to override the defaultScreenName of * the shell created. * * stdout as well as stderr are redirected to the file named in * defaultScreenName or in ensemble.createShell.screenName. * Since output on stdout and stderr from remotely started shells * should normally be restricted to debugging output, stdout and * stderr are unbuffered in order to ensure that all output is * actually written to the file specified, and in the order the * output was written to stdout respectively stderr. * * If this shell is started from the commandline, * defaultScreenname has no effect, since stdout and stderr are * then used without modification. *) defaultScreenName:< (# name: ^Text; do INNER exit name[] #); (* DISTRIBUTIONDIR * * When using ensemble.createShell to start processes, * createShell needs to know the location of certain scripts. The * default location of these scripts is in a subdirectory of the * directory containing BETA distribution source files. * * By default the distribution directory is * '~beta/distribution/private' * * where ~beta is found by inspecting the BETALIB environment * variable. (Default value for BETALIB is * '/usr/local/lib/beta'). * * On order of increasing priority, the default may be changed in * one of the following ways: * * 1. Further binding the distributionDir virtual and assigning * to "dir". * * 2. Setting the BETA_DISTRIBUTIONDIR environment variable. *) distributionDir:< (# dir: ^Text; do '$(BETA_DISTRIBUTIONDIR)'->expandEnvVar (# defaultValue:: (# do (* BETA_DISTRIBUTIONDIR not set. If distributionDir * is not further bound, use default. *) INNER distributionDir; (if dir[]=NONE then '$(BETALIB)'->expandEnvVar (# defaultValue:: (# do '/usr/local/lib/beta/' -> envvarvalue[] #) #)->dir[]; (if (dir.length->dir.inxGet)<>'/' then '/'->dir.append if); 'distribution/private' -> dir.append; if); dir[]->envVarValue[]; #) #)->dir[]; (if (dir.length->dir.inxGet)<>'/' then '/'->dir.append if); exit dir[] #); (* ENSEMBLEPORT * * This release of the BETA distribution library uses TCP/IP for * all communication between distributed BETA processes. The only * system port number hardcoded into the distribution library is * the port number assigned to the ensemble shell (the * "ensembleDeamon" program started on the local host using the * "~beta/bin/startensemble" script). * * By default, the ensemble uses the port number 5193. However, * in order to allow several ensemble instances to run on the * same host without conflicting, e.g. in order to allow * different groups to run BETA distribution without sharing the * ensemble, this may be changed in one of the following ways: * * 1. Set the BETA_ENSEMBLE_PORT environment variable before * starting the distributed program supposed to use an * alternative port number. For example: * * setenv BETA_ENSEMBLE_PORT 5211 * * Note that this should be done before starting the * ensemble to use the alternative portnumber. Remember * that the ensemble.createShell.environment virtual may be * used to set the environment of shells started from other * shells. * * 2. Furtherbind the ensemblePort virtual found below. For * example: * * --- program:descriptor --- * shellEnv * (# ... * ensemblePort::< (# do 5211 -> value #) * ... * #) *) ensemblePort:< IntegerValue (# valueAsText: ^Text; do '$(BETA_ENSEMBLE_PORT)' -> expandEnvVar (# defaultValue::< (# do '5193' -> envvarvalue[] #)#) -> valueAsText[]; valueAsText.reset; valueAsText.getInt -> value; INNER; #); (* RSHPATH * * Currently ensemble.createShell depends on "rsh" in order to * start new shells on remote hosts. In order to support systems * with rsh installed in a non-standard directory, this virtual * allows for the specification of the location of the rsh system * command. * * rshpath should be specified to the full path of the "rsh" * ("remsh" on HP UX) system command. If rshpath is not further * specified, the system default is used. Usually there should be * no need for changing the rshpath. *) rshPath:< (# path: ^Text; do INNER exit path[] #); (* USERNAME * * Returns username of process owner. *) userName: @ (# t: ^Text; do (if t[]=NONE then ... if) exit t[] #); (* WITHDRAW * * Due to the lack of distributed garbage collection, we need a * way to explicitly withdraw the possibility of remote access to * objects whose reference has crossed the shell * boundary. Whenever that happens, the object reference is saved * in an internal table and is therefore never garbage collected. * * Calling withdraw with a local object whose reference has been * exported deletes the object from the internal table, thereby * making it possible to garbage collect the object unless other * local references exists. If a request to a withdrawn object * arrives from a client, it will fail with an 'unknownObject' * exception. This corresponds to following a distributed * dangling reference, and there is no way to avoid this without * distributed garbage collection. * * Proxy objects are garbage collected automatically as is any * ordinary object. *) withDraw: (# ra: ^remoteAble enter ra[] do ... #); (* TRACING OBJECT SERIALIZATIONS * * When performing a remote invocation, one or more objects are * serialized (marshalled) to be sent across the network * connection. In some cases large object graphs are serialized * this way. Currently there is no way of specifying a limitation * on this serialization traversal (as is possible in the * persistent store), and sometimes more objects than expected * gets serialized, leading to unexpected errors. Most often the * error message resulting is "components not handled", which is * triggered when trying to serialize an active object. To debug * problems like these, a number of patterns are offered below. * * Tracing is initiated by setting the "TraceSer" boolean to * TRUE. When this has been done, the "BeforeSer", "AfterSer" and * "AfterUnser" virtuals are called as described below: * * BeforeSer is called just before an object is about to be * serialized, either as a result of being sent in a remote * request, or as a result of being returned as a result * parameter. * * AfterSer is called when the object has been serialized. * * AfterUnser is called when some object received, either as part * of an incoming call, or as part of a the result received, has * been unserialized. * * Remoteable instances are not actually serialized. Instead a * network representation of the corresponding object reference * is sent. In case of a non-remoteable, the object is serialized * and all references it contains followed. *) TraceSer: @Boolean; BeforeSer:< (# o: ^Object enter o[] do INNER #); AfterSer:< (# o: ^Object enter o[] do INNER #); AfterUnser:< (# o: ^Object enter o[] do INNER #); (* EVERYTHING BELOW IS PRIVATE! *) senvPriv: @...; (* REMOTEABLETYPE * * The remoteAbleType is a network representation of remoteAble * subpatterns. The type represented includes the part of the * superpattern chain having origin in shellEnv, excluding * remoteAble as this is the basepattern for all patterns * represented. * * groupNames are the names of the groups corresponding to * groups. The path of the groupNames are not included. Instead * a check is made at startup time, that no two groups in the * executable have the same name, as this cannot be allowed. The * reason for this is to avoid the usual problems with pathnames, * but it means that no two program files can have the same name. * * groups are the indices in the local execGroupTable * corresponding to groupNames. If a groupName does not exist in * the local execGroupTable, group will be -1. * * protos are the indices of prototypes in the groups. * * bestKnown is the most specific superpattern of the represented * type that is known to the local shell and that has origin in * shellEnv. * * remoteAbleType instances are created by typeAllocator in * shellBody. *) remoteAbleType: (# groupNames: [1]^Text; groups: [1]@Integer; protos: [1]@Integer; last: @Integer; bestKnown: ##remoteAble; #); (* REMOTEINFO * * A specialization of ObjectTableElement containing address * information on the corresponding object ra. * * shellOID is the OID of the shell containing ra. * * shellAdr is the network address of the shell containing ra. * * netType is the network representation of the type of ra. * * ensembleAdr is the network address of the ensemble where a * remoteAble exists. ensembleName is the name of the ensemble. * * ensembleAdr and ensembleName are NONE unless the remoteInfo * corresponds to a shell or an ensemble. *) remoteInfo: ObjectTableElement (# shellOID: @OIDtype; shellAdr: ^portablePortAddress; netType: ^remoteAbleType; ensembleAdrAsText: ^Text; ensembleName: ^Text; enter (shellOID,shellAdr[],netType[], ensembleAdrAsText[],ensembleName[]) exit (shellOID,shellAdr[],netType[], ensembleAdrAsText[],ensembleName[]) #); initBeforeScheduler::< (# do ... #); isEnsemble:< BooleanValue; do ...; INNER; #)
10.1 Basicshell Interface | © 1993-2002 Mjølner Informatics |
[Modified: Tuesday March 9th 1999 at 15:23]
|