[comp.os.vms] AST interrupts and process state

RSD1901@TAMSIGMA.BITNET (Shane Davis) (01/12/88)

Hello again,

In lieu of a VMS Internals book (the one I had been using was borrowed and
the one from whom I borrowed it has taken a position elsewhere), I must
ask this question of the Info-VAX list.

Can a process in Local Event Flag Wait (LEF) state be interrupted by AST's
generated by $QIO requests? I do not want to complicate my program by nesting
hibernations and am not entirely sure if $WAKEing one hibernation will leave
the other pending hibernate intact or if it will wake the process altogether.
What I need to do is issue a $QIO to a mailbox and have its AST routine (as
given by the astadr parameter to the $QIO) interrupt the process for a short
time to let the sender of the mailbox message know that a resource is
unavailable while waiting for another QIO read request to a network (EXOS
TCP/IP is the software) to complete, signalling that a remote server has been
restarted. After the notification of the server restart has been received, the
program will then go back into HIB and wait for subsequent requests since the
resource is again available.

The routine in which all this is happening will also be entered from an AST
processing routine that was started while the process was under one $HIBER.
Thus, when the server is again reachable, the process should reenter HIB.
If I do use $HIBER to wait for the network QIO to complete, will a $WAKE
also elimintae the original $HIBER or only the one entered to wait for the
network?

The approach I'm using now is to use $SYNCH to wait for the network QIO to
complete, but writes to the forementioned mailbox must be processed even while
waiting for the network resource. So, after all this confusing explanation, I'll
restate my main question: can a process waiting for a single event flag be
interrupted by AST's? If not, will nested hibernations and wake-ups work?

After all this, I will probably just put a flag logical name in LNM$SYSTEM and
have it indicate to the mailbox message senders whether the resource is or is
not available.

Thanks in advance, and I hope you can think straight after my attempt at an
explanation of my dilemma. Please send responses to XPMAINT@TAMVENUS.BITNET.

--Shane Davis

LEICHTER@VENUS.YCC.YALE.EDU ("Jerry Leichter ", LEICHTER-JERRY@CS.YALE.EDU) (01/13/88)

The author starts off with a simple question:

	Can a process in Local Event Flag Wait (LEF) state be interrupted by
	AST's generated by $QIO requests?

The answer to this question, as written, is YES, but...

					  I do not want to complicate my
	program by nesting hibernations and am not entirely sure if $WAKEing
	one hibernation will leave the other pending hibernate intact or if it
	will wake the process altogether.

Still a simple question with a simple answer (see below), but the waters
getting murky...

					   What I need to do is issue a $QIO
	to a mailbox and have its AST routine (as given by the astadr
	parameter to the $QIO) interrupt the process for a short time to let
	the sender of the mailbox message know that a resource is unavailable
	while waiting for another QIO read request to a network (EXOS TCP/IP
	is the software) to complete, signalling that a remote server has been
	restarted. After the notification of the server restart has been
	received, the program will then go back into HIB and wait for
	subsequent requests since the resource is again available.

Looks good, absolutely standard use of QIO's and AST's.

	The routine in which all this is happening will also be entered from
	an AST processing routine that was started while the process was under
	one $HIBER.  

Now things have gotten completely murky, as it is no longer clear where and
when the proposed wait for an event flag is to take place.

		     Thus, when the server is again reachable, the process
	should reenter HIB.  If I do use $HIBER to wait for the network QIO to
	complete, will a $WAKE also eliminate the original $HIBER or only the
	one entered to wait for the network?

OK, we need to step back and understand how AST, event flags, and such work,
as once you start digging, there isn't enough information here to really
answer the question.

First off, a process has an "AST active" bit that can be either on or off.
When it is on, the process is running "in AST state"; it can receive no
further AST's (at least at the same our an outer access mode.  That needn't
concern us for normal user-mode progamming, though it explains why DCL can
receive a supervisor-mode CTRL/Y AST even while a program is executing in
user-mode AST state.)  When a process is NOT in AST state, it can usually be
interrupted by AST's.  There two exceptions:  SUSP(ended) processes, and
those that have disabled AST delivery with the SYS$SETAST() call.  Both
hibernation and LEF state can be interrupted by an AST.  So, the first answer
to the first question is yes - EXCEPT that saying that a process is in LEF
state doesn't give the whole picture.  It may have entered LEF state while
in AST state (by doing a QIOW or a SYNC or an explict EF wait).  In that case,
the "AST active" bit is still on, and no further AST's can be delivered.
(The same goes, of course, if the event flag wait was started after using
SYS$SETAST to disable AST delivery; or if the process was suspended while
waiting.)

Now for hibernation.  Hibernation is a very simple process state - a process
is either hibernating or not.  A hibernating process will come out of hiber-
nation when it is woken, or when an AST is delivered to it.  In the latter
case, the previous hibernation state is remembered, and the process will
resume hibernation after the AST routine exits.  (However, a WAKE will clear
the remembered hibernation state even while an AST is in progress, so another
process can always issue an effective WAKE - and a process can issue a WAKE
for itself while at AST level and thus "kick" its non-AST-level code.)  If
a process begins hibernating while in AST state, or with AST delivery
disabled, no AST can be delivered to it to break it out of its hibernation.
In that case, only a WAKE from another process will get it moving again.

Again, there are complications if you consider inner access modes.  Hiber-
nation is process-wide; it is not tied to access mode.  A process that issues
a $HIBER call in kernel mode will wake up if another process issues a user-
level $WAKE call for it.  On the other hand, while a process is in an inner
mode, it cannot receive any AST's for outer modes, so this particular avenue
of awakening from the hibernation may be blocked.  The fact that there's only
one level of hibernation has an important consequence, due to the fact that
DCL SPAWN and ATTACH rely on hibernation - processes may receive "unexpected"
wakeups.  Hence, correct code must NEVER assume that the simple fact that it
woke from hibernation means that some event it was waiting for has taken
place; it must check.  That is, the ONLY correct way to use hibernation for
synchronization is:

	sleep:	while (event-hasn't-happened)
			$HIBER()

	wake:	mark-event-as-happened;
		$WAKE(...)

So, hibernations don't nest, and hibernating while in AST state is almost
certainly not what you want to do.

Finally, how do hibernation and LEF state mix?  Well, since a process must
take a deliberate action to enter either state - i.e., it can only enter
either state from the CURR state - the answer, in a sense is, they don't.
However, there is one situation in which both can (sort of) apply:  Process
hibernates, receives an AST (temporarily breaking it out of hibernation),
then waits for an event flag.  In that case, the process is in LEF state
(with the AST active bit set); it's got a nested hibernation request waiting
for it once it exits from its AST routine, which it will presumably do after
its event flag wait completes.  Since the AST active bit is set, no further
AST's can be delivered to the process.

BTW, if I remember right, the mechanism for all this is very simple:  While
a process is hibernating, its stored PC points to the call to SYS$HIBER.  AST
delivery simply makes the process computable; when the AST routine completes,
the process returns to its stored PC, which calls SYS$HIBER again.  A WAKE
works because when applied to a process that is not currently hibernating -
as a process executing an AST is not - it sets a "wake pending" bit in the
process header.  When this bit is set, the next call to SYS$HIBER returns
immediately, without sleeping.
							-- Jerry

jeh@crash.cts.com (Jamie Hanrahan) (01/13/88)

In article <8801121623.AA23046@ucbvax.Berkeley.EDU> RSD1901@TAMSIGMA.BITNET 
(Shane Davis) writes:
> [questions re. AST deliverability in LEF state, nested HIBERs, etc.]

I'm a little brain-dead at the moment, and I didn't completely follow
your description of your application design.  I'm particularly puzzled
about what you meant by "nested $HIBERs".  (More on this later.)  However,
are the rules concerning AST delivery, HIBER/WAKE, etc.

An AST queued to a process will be delivered to that process (i.e. 
the AST procedure will be called) unless one or more of the following
conditions are true:

1.  The process already has an AST active at the same access mode (user,
supervisor, executive, kernel), or at a more privileged access mode, 
than the access mode of the AST.  

2.  The process is running or waiting at a more privileged access mode 
than that of the AST.  For instance, if you're looping in kernel mode,
or even if you're in HIB state as a result of a $HIBER call that you
did while in kernel mode, no user-, supervisor-, or executive-mode ASTs
can be delivered.

For processes that stay in user mode except for brief periods when
they call system services or RMS -- which includes most processes we
deal with -- the above rules boil down to:  The process must not already
be executing (or waiting) at AST level.  If it is, the next AST is queued, 
as are all subsequent ASTs.  Each gets delivered when the previous one RETs.  
(More on the "or waiting" part in a few moments.)

3.  The process is in a wait state that precludes AST delivery.  Of 
the wait states which programmers voluntarily put processes into, only
SUSPend precludes AST delivery; ASTs are deliverable in LEF, CEF, and HIB
states.  They are not deliverable in most (if not all; I don't have my
source fiche at hand) of the wait states into which the system 
"automatically" places processes, such as page fault, the infamous 
MWAIT state, etc., as in most such states the process is sitting at 
IPL 2, which inhibits the AST delivery mechanism.  

(The rules for the SUSPend state change in V5.  You'll be able to say
"suspend, but leave kernel-mode ASTs deliverable"; this allows a 
process suspended in this way to be deleted while blocking user-mode
ASTs (presumably the application's ASTs).  For most user-generated
code this will have little significance, except that they'll have a
way to kill off processes that get stuck in suspend.)

4.  The process has explicitly blocked AST delivery at the access mode
of the AST.  (It can do this by calling the $SETAST service from the
mode to be controlled.  Of course, it can later enable ASTs with the
same service, at which time any ASTs that got queued up in the interim
will be delivered, one at a time.  This is a good way to hold off AST
delivery while you process data gathered by the AST, or to avoid problems
with using non-AST-reentrant code when an AST might be delivered,
etc.)

5.  The process is executing at IPL 2 or above.  (Many system services,
$QIO for example, raise IPL to 2 temporarily to block AST delivery, even
special kernel ASTs.  They only do so for very short times.  As with 
$SETAST, as soon as IPL is lowered, any ASTs that were queued while IPL
was raised will be delivered, unless something else inhibits them.)

So, assuming that you are talking about all user-mode code, and you're not
playing with $SETAST, yes, ASTs are deliverable in LEF state, and also in 
HIB... with one important exception.  It's not really an exception, but 
it's not obvious:

IF YOU ENTER THE WAIT STATE (LEF, CEF, HIB) FROM CODE INVOKED VIA AST --

that is, if an AST was active when you entered the wait state; doesn't
matter whether the AST procedure itself, or an inner procedure which
the AST procedure called, calls the `wait' system service -- 

ASTs CANNOT BE DELIVERED WHILE YOU ARE IN THAT WAIT STATE.  

So, for instance, if you:

	(in non-AST code:)
	do something that will eventually cause an AST to be queued
	$HIBER
	
	(whatever it is that you did causes the AST to be queued)
	(the AST is delivered)
	(in your AST procedure:)
	do something that will eventually cause an AST to be queued
	$HIBER
	RETURN

You're stuck.  You called $HIBER at AST level; therefore ASTs can't
make you run.  Some other process will have to $WAKE you.  It doesn't matter
if you use two $HIBERs as shown here, or a $HIBER in the non-AST and a
$WAITFR in the AST... what matters is that you entered a wait state,
ANY wait state, from AST level.  This blocks ASTs at your current access 
mode.  

There might be some applications where you want this behavior.  As a general 
rule, don't call "wait" services from AST level.  

--------------------

Now, about nested HIBERs and all that... I think a brief explanation
of how wait states interact with ASTs is in order.  

When you use $SUSPND, $HIBER, or $WAITFR (or any of its variants,
including $SYNCH), there are three things to keep in mind.  First, there's
a bit (or bits) somewhere which, if set, will allow you to run.  For
$WAITFR and etc. the bits are event flags; for $SUSPND and $HIBER they
are the "resume pending" and "wake pending" bits, set by the $RESUME
and $WAKE services, respectively.  

Second, when you call any of these services, they *first* check the
appropriate bit(s) to see if you really should go into the wait state.

For instance, if someone tries to $WAKE you and you aren't hibernating,
your wake pending bit is set.  If you try to hibernate after that, $HIBER
checks (and clears) the wake pending bit, and you keep right on going.  
A subsequent $HIBER will succeed (unless another $WAKE occurred after the
first $HIBER).  Note that no count of outstanding wake requests is kept--
only the fact that there's at least one.  $SUSPND works the same way, 
only it uses a different bit.  $WAITFR, et al, don't clear the event
flags after checking them, but otherwise are the same.  

Third, and here's the fun part:  When you call any of these services, 
if you do in fact go into the desired wait state, THE STORED PROGRAM
COUNTER (PC) FOR YOUR PROCESS POINTS (effectively) TO THE CALL TO THE
SYSTEM SERVICE, NOT THE NEXT THING AFTER IT.  

What does this mean?  It means that whenever your process comes out of
a wait state, you call the system service that put you in the wait 
state AGAIN.  The reason for the wait is re-evaluated, if you will.  
You might go right back into the wait state, depending on what else
has happened.  

Let's see how this all works to make ASTs and wait services act the way
they do:

	(non-AST code)

	start an I/O, specifying an AST procedure
	$HIBER

	(AST procedure)
	have we done enough IOs?
	if yes
		$WAKE		(path a)
	else
		start the next one	(path b)
	endif
	RETURN

We've started the first I/O (from the non-AST code) and entered $HIBER.
(We'll assume that the "wake pending" bit was originally clear.)  The IO 
completes.

The delivery of the AST makes your process computable; that is, it takes
it out of HIB state.  Completely.  The only record that we were ever in
HIB state is that stored PC that's pointing to the call to the $HIBER
service.

So the process is made current.  Whups!  There's an AST to be delivered,
so the saved PC is ignored for now; instead the system climbs down to
user mode and CALLs your AST procedure.  The AST runs.  The first time
through it takes path b; it starts another IO, then RETs to its caller
(the AST delivery mechanism).  

There are no more ASTs queued, so the saved PC gets used to restart 
execution of your non-AST code... and the $HIBER service is called, and 
the "wake pending" bit is found to be clear, so back to hibernate state 
you go.  

Repeat until the AST procedure decides "Hold, enough!"  It calls $WAKE
and RETurns.  Now when you re-execute $HIBER it finds the "wake pending"
bit set (and clears it), so it doesn't go into $HIBER state.  It just
returns to its caller and you keep executing.  

Same thing if you'd used $WAITFR in the main program and $SETEF in the 
AST.  

Now, a related question to see if you understand the material... :-)
Suppose you:

	(non-AST code)
	$QIO, specifying an event flag and an AST
	$WAITFR the event flag

	(in the AST procedure)
	clear the event flag
	RETURN

What happens?  Well, when the I/O completes, the event flag gets set
(and your process is made computable as a result, but we're not done yet),
and the IOSB is filled in, and the AST gets delivered.  The AST procedure,
nasty thing that it is, clears the event flag, then returns to the system.
The process is now free to execute non-AST code.  Since the stored PC 
points to the $WAITFR call, and since the AST cleared the event flag,
back into the LEF (or CEF) state you go.  

I hope this helps.  Sorry for the length, but better to rattle something
off that's too long than decide that I don't have time to do it up right.
Perhaps I'll clean this up in the next few weeks and submit it to the
Pageswapper (newsletter of the DECUS VAX SIG); any comments or suggestions
for improvements will be welcomed.  

Oh... there *is* an optimization in the system for $WAKE out of the 
$HIBER state.  The $WAKE service checks your process and, if it's in
the $HIBER state, takes it out thereof, leaves wake pending clear, 
and prevents you from re-executing the $HIBER call, in order to save
a little bit of time (hiber/wake is supposed to be the fastest inter-
process synchronization technique, although in my tests waitfr/setef
was not noticeably slower.  Note that this optimization doesn't work
when $WAKE is called from within an AST executing in the context of
the process being awakened, as the process being "awakened" is current,
not hibernating, at the time;  $WAKE has no way to determine that you
"used to be" hibernating.