Primarily to avoid recovery to 'next event' at the wrong moment, ZEBRA needs to know in which phase the user program is at any given moment. We distinguish three phases:
signals to Zebra a change of phase, preset by MZEBRA to 'initialization'.
Normal operation phase: JPH .GE. 0
Termination phase: JPH .LT. 0
JPH = -1: reset the working space to be of zero length,
but leave the store as it is;
-2: reset the working space,
wipe all user short-range divisions;
-3: reset, wipe, and collapse upwards all short-range user
divisions to be of zero length, giving their space to
division 1 for use by termination routines; perform
clean-up garbage collection in all other divisions.
The program phase is recorded on the variable NQPHAS of ZSTATE and has the following significance :
NQPHAS = 0 initialisation phase
>0 = MAX(JPH,1): normal operation phase
<0 = JPH: termination phase
If the user whishes to subdivide the operation phase,
he can pick up the current state from this variable.
ZPHASE prints a log message at level -1 for major phase changes, and at level 2 for minor changes.
The routine ZEND, which has no parameters, is defined to be the entry-point for normal run termination. It is normally provided by the user to close files and print accumulated results. It is important that all closing down operations are done through this routine, if the user wants them to happen even in abnormal run termination. It would normally look like this:
SUBROUTINE ZEND
+CDE, ZSTATE.
CALL ZPHASE (-3) start termination
. . . any user termination code
CALL MZEND
IF (NQERR.NE.0) CALL ABEND
STOP
END
MZEND is a little routine which prints statistics about the usage of all divisions.
NQERR in ZSTATE is zero for normal run termination, it is non-zero if ZEND has been reached via ZFATAL. On some machines the recovery system of the machine expects the user to re-enter it. The KernLib routine ABEND (Z 035) looks after this.
The Zebra system contains a little default routine ZEND which is the above without any user termination.
A user routine similar to ZEND is defined for taking over control of fatal error termination. It is called ZABEND and it is described in the next paragraph. This should perform in there any extra operations needed for fatal termination and then it should transfer to ZEND for closing down.
is defined to be the entry point for fatal run termination.
is identical to ZFATAL, except that it prints a message, given in the parameter as a character string of not more than 30 characters, terminated by the character '.'.
These routines are supplied by the system;
they are protected against recovery loops,
and they must not be supplied by the user.
They should only be called when the run cannot usefully continue.
If the application program discovers such a fatal condition
it too should call ZFATAL or ZFATAM,
preceded with some diagnostic printing or
with loading to IQUEST some clue to the trouble.
and
This routine receives control from ZFATAL to handle fatal run termination. This routine may be supplied by the user.
The Zebra system contains the standard routine as follows:
SUBROUTINE ZABEND
+CDE, ZSTATE.
CALL ZPOSTM ('TCWM.')
IF (NQPHAS.LE.0) CALL ABEND
NQPHAS = -2
CALL ZEND
RETURN
END
This is not just a dummy, it causes a post-mortem dump, including a subroutine trace-back (if possible), followed by any normal user output programmed in ZEND. Transfer to ZEND takes place only if the break-down happened during normal operation, but not if the program is still in the initialisation phase or if it is already under ZEND control.
The parameter to ZPOSTM is passed from there to DZSNAP
to select the options for dumping the dynamic store,
see section
for details.
During normal operation any request from the user for space with MZWORK, MZLIFT et al. is satisfied, after garbage collection if necessary and possible. If however the request cannot be satisfied, the normal course of the program must be broken. To deliver the user from the burden of checking for success after each space request, the garbage collector sends control to the user at the entry-point QNEXT (via ZTELL and the KERNLIB routine QNEXTE), where he can program the recovery of the problem. Normally this will be to skip the current event and to continue with processing the next one.
Other Zebra packages, apart from MZ, and maybe the user himself, have similar problems. Therefore a general trouble control routine ZTELL has been included into Zebra. This is a switching routine with several modes of continuation, one of which is to send control to QNEXT. ZTELL can also be called by the user program, thus:
ID is an integer between 101 and 999,
ID's below 100 are reserved for system usage,
ID=99 for 'no memory left' from MZGARB.
IFLAG is a flag indicating whether the calling code can
accept a RETURN from ZTELL:
= 0 ZTELL may return;
= 1 the calling code is not capable to accept a RETURN;
= 2 fatal error, the run must stop.
ZTELL prints a message, sets up a reasonable exit mode as a function of ID and IFLAG into the little labelled common ZTELLC and calls ZTELUS to give the user a chance to modify this mode. On the obligatory return from ZTELUS it takes the selected exit as follows:
COMMON /ZTELLC/ ID, MODE
ID is a copy of the first parameter to ZTELL;
MODE is the selected exit mode:
= 0 RETURN to let the calling routine continue;
= 1 CALL QNEXTE to enter QNEXT
= 2 CALL ZFATAL
= 3 CALL ZEND
The exit mode to QNEXT is enabled only if NQPHAS in ZSTATE is larger than zero, indicating that the program is in the normal operation phase. During the initialisation or the termination phase of the program transfer is to ZFATAL instead, to avoid a program crash to be 'recovered' into normal operation.
The pre-loading of MODE is MODE=IFLAG for user calls (ID>100); and for system calls (ID<100) it is as shown in the diagnostics chapter for ZTELL.
Here is an example of a ZTELUS which is happy with the default modes, except that it wants to go to ZEND for ID=8:
SUBROUTINE ZTELUS
COMMON /ZTELLC/ ID,MODE
IF (ID.EQ.8) MODE=3
RETURN
END
The default subroutine ZTELUS on the library is a do-nothing dummy.
The default subroutine QNEXT goes straight to ZFATAL.
On most machines repeated recovery directly to QNEXT causes trouble with the Fortran trace-back and the subroutine stack. For this reason ZEBRA relies on the KERNLIB routine QNEXTE, which implies an organization for event processing as follows:
_________________________
| | Program flow with QNEXT recovery
| MAIN program |
| |
| CALL MZEBRA (0) |
| CALL MZSTOR (...) |
| |
| program | _________
| initialization | | |
| | | |
| CALL ZPHASE (0) | first entry recover | |
| CALL QNEXTE | ------ _________ _| |
|_________________________| `----> | | | |
| routine | | routine |
<---------------------------< | QNEXTE | <-- | ZTELL |
| |_________| |_ |
________|________________ | |
| | | |
| subr QNEXT | | |
| | | |
| 11 CALL MZWIPE (0) | requests | |
| read event | ----------------------------> | Zebra |
| IF (end) CALL ZEND | | system |
| process event | <---------------------------- | |
| output event | normal RETURN's | |
| GO TO 11 | | |
|_________________________| | |
| | |
________|________________ __________ | |
| | | | | |
| subr ZEND | <--- | subr | <--- | |
| | | ZABEND | | _| |
| CALL ZPHASE (-3) | |__________| | | |
| | | | routine |
| program | `---< | ZFATAL |
| termination | |_ |
|_________________________| |_________|
The initialisation part prepares the program to be ready
for execution and then calls itself
QNEXTE for entry to QNEXT
to process the 'next event',
being the first event in this case.
QNEXT loops internally to process all events.
CALL's from the processing program to the Zebra system are normally satisfied, and control comes back to the user with normal RETURN. Abnormal returns are either via ZFATAL to ZEND, or straight to ZEND, or to QNEXT via QNEXTE.
The Fortran version of QNEXTE is a simple CALL QNEXT followed by RETURN. If necessary on a given computer, QNEXTE is a machine-language or a C routine to unwind to itself the Fortran trace-back and the subroutine stack. So, if the user wishes at some point to abandon himself the curent event and to go to the next one, he should CALL QNEXTE and not QNEXT.
QNEXT is a user routine to the KERNLIB routine QNEXTE and has thus the usual problem of user routines called from a library routine in that it must be loaded explicitely:
either: compile it together with the other material
or: if it resides on a user library it must be
INCLUDEd explicitly, for example on the VAX with
$ LINK ... MYLIB/INC=QNEXT/LIB ...
This flow-diagram is only an example for the most common case of actual usage of Zebra. If one's program is not of the event-processing type one has to look at QNEXTE/QNEXT from a different angle: program flow from MAIN has to go to QNEXTE to initialize for re-entry. Entry and all re-entries are then to QNEXT, which has to control the further program flow according to some flags, conveniently ID in ZTELLC and NQPHAS in ZSTATE. ID is not initialized by MZEBRA, it is only changed by ZTELL which copies its first parameter to ID. This number is an integer in the range 1 to 99 for calls from the Zebra system. NQPHAS is initialized to zero by MZEBRA, it is then changed only by the user (or the default ZABEND) either directly or with ZPHASE.
CALL ZVERIF (IXDIV,IFLRTN,chTEXT)
with IXDIV 1) the index of a single division to be verified;
2) the index of the store to be verified;
= -1: all stores
(other negative values are reserved)
IFLRTN return flag, see below;
chTEXT identifying text to be printed if trouble.
This routine has two somewhat different modes of operation:
Normally ZVERIF checks all the data in and relevant to a complete store, or even several stores. It goes to ZFATAL if it finds trouble.
But if verification is for one single division only, only the banks of this division are checked and control is given back to the caller if the flag IFLRTN is non-zero, in which case the number of normally fatal conditions is delivered in IQUEST(1). This is intended to be used if one receives a data-structure in memory (with FZIN, say) which may be bad. Rather than letting the program run to crash one can realize the situation and wipe the division. This must be done with MZWIPE because this is the only way to get rid of the data without Zebra looking at them.
This routine inspects all link areas and all banks in the stores selected, performing the following checks:
(0) process and store control tables intact;
(1) bank chaining intact;
(2) a non-zero structural link must point to a true bank address;
(3) consistency of "up" and "origin" links;
(4) a link in a bank in division A pointing to division B
is checked for consistency with the cross-reference matrix;
(5) a non-zero reference link in a bank must not point
into a reserve area;
ZVERIF is used by the automatic verification procedure ZVAUTO, see next section.
The routines of this complex are provided as the ultima ratio regis for finding program errors which destroy data in a Zebra store by faulty handling of links; they consume a lot of CPU time.
With automatic verification switched on calls to the Zebra system transfer to ZVAUTY, which handles the verification process:
The "verify identifier" (VID) is constructed by counting the number of entries to ZVAUTY. The VID is a two-word object, because 32 bits may not be sufficient, it identifies every call to Zebra, it gives us a handle on where we are in the job, and it is available to the user via the common ZVFAUT to be printed together with the event number, for example. ZVAUTY will remember separately the VID for the last garbage collection, for the last MZWIPE, and for the last FZIN, for printing on fatal error.
In "active mode" ZVAUTY will call ZVERIF for the stores selected by the user initially, causing a transfer to ZFATAL in case of trouble, which in turn will transfer control to the user by calling ZABEND, where he may code any display wanted.
In "dummy mode" the time-consuming calls to ZVERIF will not take place, but the VID is checked to see whether the "action threshold" defined by the user is reached. If so, the mode is switched to "active", and the log-level is set for all stores to be verified. From this point on one may get rather a lot of output, tracing the execution of the program step-by-step.
Suppose now one has a particular run, "run A" say, of a lengthy production job which collapses with the memory destroyed. The reason for this will be somewhere in the past. One could re-submit the job switching on true automatic verification right away, but this could be excessively time consuming and produce much too much output. If so, it is better to re-submit the job first with "dummy verification" switched on by inserting a call to ZVAUTO at the point where one wants to start verification, after the initialization phase for example, and with the connection routine ZVAUTX supplied, "run B" say. Dummy verification goes through the same motion as active verification, but it does not call ZVERIF and hence costs only little more time than run A. This run should also end up in ZFATAL, which will now print the VID's remembered for the last garbage collection, for the last wipe, for the last FZIN, and maybe other VID's entered by the user with ZVAUTU.
One can now re-submit the job, "run C" say, having changed the call to ZVAUTO by giving the action threshold, thereby defining the moment when active verification will start.
Automatic verification is initiated with
CALL ZVAUTO (MSTORE,IDVTH1,IDVTH2,LOGLEV)
with MSTORE a bit-by-bit word indicating the stores to be verified,
store i = 0,1,2,... selected if bit (i+1) is set to one;
if MSTORE = 0: all stores
IDVTH1/2 the two-word VID at which true verification should start,
give both words zero if dummy verification only
("run B" in the discussion above);
give 0,-1 if active verification is to start right away;
LOGLEV set the log-level of the existing stores to be verified
to this value at the moment of changing to active mode,
give 2 for maximum logging.
To avoid loading the non-negligeable code of the ZVAUTO complex with normal production jobs, this trick is used: all relevant Zebra routines contain a conditional call to ZVAUTX (which does not take place if ZVAUTO has not been called), and the Zebra library contains a dummy routine ZVAUTX. But the true process is controlled by ZVAUTY, which is not normally loaded. The user makes the connection by supplying this routine:
SUBROUTINE ZVAUTX
CALL ZVAUTY
END
as part of the material to be compiled and linked.
In the list of "last VIDs remembered" there are 3 places for user triggered storage, which is done with
This will push the VID in position 2 to position 3, and the one in position 1 to position 2, entering the current VID into position 1. Thus, if one were to call ZVAUTU at the start of each event, one would see the start points of the last 3 events in a dump from ZFATAL.
The user has access to the verification parameters via this common:
COMMON /ZVFAUT/IQVID(2),IQVSTA,IQVLOG,IQVTHR(2),IQVREM(2,6)
IQVID the current VID
IQVSTA the verification status:
zero automatic verifying not running
-1 dummy verification
+1 active verification
IQVLOG the log level to be set at the activation threshold
IQVTHR the threshold VID
IQVREM(2,J) VID remembered in position J,
J = 1 last garbage collection
2 last call to MZWIPE
3 last call to FZIN
4 last call to ZVAUTU
5 last-but-one call to ZVAUTU
6 last-but-two call to ZVAUTU
Examples:
Do not forget to supply the connection routine ZVAUTX !
CALL ZVAUTO (7,0,0,0)
to start a dummy verification run for store numbers 0, 1, 2;
"run B" of the discussion above.
Supposing that the ZFATAL output of this run tells us the
VID of the last-but-one event, by changing to
CALL ZVAUTO (7,0,123456,2)
we may start "run C" to give detailed logging for the
last two events.
CALL ZVAUTO (1,0,-1,0)
to start an active verification run for store number 0;
one might do this on a limited data sample before going
into production with a new program, just to verify that
the program is alright.
Global CERN library references
/user/goossens/cnasall/cnasbibl,/user/goossens/cnasall/textproc}