
A Model for Asynchronous Request Processing
and Dynamic Memory Management
for CTOS System Services
Jim Frandeen
date



Introduction	3
Synchronous Versus Asynchronous	3
The Synchronous Server Model	3
The Asynchronous Server Model	4
The Asynchronous Request Interface	4
BuildAsyncRequest	5
BuildAsyncRequestDirect	5
Passing a Variable Length Parameter List in PLM	5
Asynchronous Request Error Conditions	7
AsyncRequest	7
AsyncRequestDirect	7
Contexts	8
CreateContext	8
ResumeContext	9
TerminateContext	9
WaitLoop	10
CheckContextStack	10
Other Ways to Use Contexts	10
Handling System Requests	11
Termination and Abort Requests	11
SwapContextUser	12
Deinstallation	14
Memory Management	14
DS Allocation	14
COED Modules	15
AllocMemoryInit	16
Freeing Leftover Memory	16
HeapInit	17
HeapAlloc	17
HeapFree	18
Other Ways to Use the Heap Procedures	18
Using the AsyncServer Library Procedures	18
Debugging Aids	18
Contents of the AsyncServer Release Diskette	19
Restrictions	20
System Software Compatibility	20
Resource Requirements	20
AsyncServer Statistics	21
Error Code Summary	22
Server Program Example	23
Introduction
You have probably experienced the following kind of frustration while waiting your turn at a supermarket checkout counter. The person in front of you is writing a check, and you think the wait will be short. But the clerk must call the manager to approve the check. The manager is busy with another customer. Since you are only buying a pint of ice cream, you wish the clerk would use this idle time to handle your purchase. The clerk explains that he can only process one transaction at a time. The minutes drag by. The clerk idly jingles change in the cash drawer. The manager seems to be on hold. As your ice cream begins to melt, you wonder if there isn't a better way.
Unfortunately, most servers are designed along the lines of the supermarket checkout counter. The clerk (server) in this example is blocked at his default response exchange waiting for a synchronous request. This paper presents an alternative model for supermarkets and CTOS servers. In this model the server uses a new request interface to send asynchronous requests. When the server is not working, it is waiting at its service exchange, either for a new request or for a response to a request that it sent out.
Synchronous Versus Asynchronous
We conisder two models of execution. In the synchronous model, the server blocks each time it sends a request. In the asynchronous model, the server does not wait for a request to come back.
The Synchronous Server Model
The usual model of execution for a system service is the following:
DO FOREVER;
	erc = Wait(exchServ, @pRq);
	erc = ProcessRequest;
	rq.ErcRet = erc;
	erc = Respond(pRq);
END;
In this model the server waits for a request at its exchange. When it receives a request, it processes it, responds to the client, and goes back to the top of the loop to wait for the next request. 
All the work is done by the procedure called ProcessRequest. In some cases, there is very little work to be done, and the server can respond to the client immediately. In other cases, the server may need to send requests of its own. In a simple example, the server may need to send a Read request to the operating system. In a more complicated example, the server may need to send an entire document to a device driver. In either case, when the server is waiting for a synchronous request, it cannot process new requests that arrive at its service exchange. The problem is that the server spends time waiting for synchronous requests at its default response exchange when it could be doing useful work. This is the problem we wish to solve with a different model of execution.}
{The Asynchronous Server Model
In the asynchronous server model, the server rarely if ever sends synchronous requests. When the server sends a request, it always goes back to the top of its wait loop to wait for either a new request or a response to a request that it sent out.
DO FOREVER;
	erc = Wait(exchServ, @pRq);
	IF rq.exchResp = exchServ THEN
		CALL ProcessResponse;
	ELSE DO;
		erc = ProcessRequest;
		rq.ercRet = erc;
		erc = Respond(pRq);
	END;
END;}
{The Asynchronous Request Interface
This section describes a set of library procedures that make it easy to create an asynchronous server or even to convert an existing synchronous server to the new model. 
The beauty of the synchronous procedural interface is that it is easy to use. The user invokes the procedural interface by coding a procedure call of the form:
erc = RequestName(arg0, arg1,...,argn1);
The procedural interface builds a request block on the stack using the parameters passed and a set of tables in the OS addressed by the name of the request. The procedural interface then sends the request, waits for a response at the default response exchange, removes the request block from the stack, and continues program execution. Ideally, we would like for the asynchronous procedural interface to be as easy to use. The following are desiderata for the library routines to be defined.
1.	We need an easy way to build a request block, send it to the OS to be processed, and jump to the top of our wait loop.
2.	When a response to a request comes back, we need an easy way to continue execution where we left off when the asynchronous request was sent.}
{BuildAsyncRequest
The procedure BuildAsyncRequest works very much like the synchronous procedural interface. It builds a request block on the stack using the arguments passed by the user. It uses tables in the OS necessary to build the request block. When the request block has been built, it is sent to the operating system via the Request primitive. 
This is where the similarity ends. Instead of waiting at the user's default response exchange for the request to come back, the asynchronous procedural interface saves the state of the context that is running so that it can be restored later, and then jumps to the top of the user's wait loop to wait for the next request or a response to an asynchronous request that was sent out. The details of starting and resuming a context are discussed later.
In C, the user invokes the asynchronous procedural interface by coding a procedure call of the form:
erc = BuildAsyncRequest(arg0, arg1,...,argn1, rcRequestName);
where the last parameter is the request code. The other parameters are the same as in the synchronous procedural interface. Passing a variable length parameter list in PLM will be described below.}
{BuildAsyncRequestDirect
Servers often need to send a request to a specified exchange. The procedure BuildAsyncRequestDirect works just like BuildAsyncRequest, but it takes one more parameter  the target exchange. For example:
erc = BuildAsyncRequestDirect(arg0, arg1,...,argn1, rcRequestName, exchTarget);}
{Passing a Variable Length Parameter List in PLM
In PLM, there is only one way to call a procedure and pass a variable number of parameters  by using an indirect procedure call through a POINTER variable:
DECLARE procBuildAsyncRequest POINTER EXTERNAL;
DECLARE ercAsync ErcType EXTERNAL;

CALL procBuildAsyncRequest(arg0, arg1,...,argn1, rcRequestName);
erc = ercAsync;
In PLM, a procedure called indirectly may not return a value. For this reason, the erc is returned in the global variable ercAsync.} 
{There is another problem with using an indirect procedure call  the compiler does not perform any type conversion of parameters. The problem occurs when literals appear in the parameter list because the compiler does not know how to convert them. Consider the following example of a call to OpenFile. To use the synchronous procedural interface, the user might code:
erc = OpenFile(@fh, @rgbFileName, cbFileName, 0, 0, modeRead);
In this example, the first 0 represents a pointer value (pbPassword), and the second 0 represents a word value (cbPassword). The compiler knows this because the procedure OpenFile has been explicitly declared. To use the asynchronous procedural interface, the user might code (erroneously):
CALL procBuildAsyncRequest(@fh, @rgbFileName, cbFileName, 
	0, 0, modeRead, rcOpenFile);
erc = ercAsync;
In this case, the compiler does not know how to convert the 0 values, so it pushes a byte value in each case. Needless to say, this will not work. There are several ways around this problem.}
{WORD values
The easiest way to explicitly call for a word value is to use the INT function. INT(0) will cause a word value to be pushed on the stack. Note that the last parameter passed to BuildAsyncRequest (the request code) must be a word value. For example:
DECLARE rcOpenFile LITERALLY 'INT(4)';
POINTER values
The easiest way to generate a zero pointer value is to declare a pointer variable with a value of zero and use it everywhere:
DECLARE pZero POINTER INITIAL(0);
	CALL procBuildAsyncRequest(@fh, @rgbFileName, cbFileName, 
		pZero, INT(0), modeRead, rcOpenFile);
	erc = ercAsync;
The problem of passing a zero pointer occurs in C as well as in PLM. 
The importance of carefully checking the validity of the variable length parameter list cannot be overemphasized. If the number and type of arguments passed is not correct, the calling program will die a horrible death.} 
{Asynchronous Request Error Conditions
The asynchronous request interface has two interesting error codes.
1. ercStackOverflow (4531) 
is returned if the the stack has overflowed or if the request block to be built would cause the stack to overflow. The global oCCB always points to the Context Control Block, and the stack starts at the bottom of the CCB, so it is a simple matter to test for stack overflow. When the test is made to assure that the request block will fit, all the overhead is included in the calculation, including 64 bytes that would be used to handle an interrupt. The problem of stack overflow can be avoided by allocating more stack space to a context (see the section of Context creation). 
2. ercNoSuchRc (31)
is returned if the request code passed as the last parameter cannot be found in the OS tables. This is a serious problem because the asynchronous request interface does not know how many words to remove from the stack before it returns. Control is returned to the caller, but program execution is rather unpredictable at this point since the state of the stack pointer is not valid.}
{AsyncRequest
Two more procedures are provided for users who wish to build their own request blocks. The procedure AsyncRequest uses the Request primitive to send a request block and then jumps to the user's wait loop. When the request block comes back, the Resume operation will cause execution to continue at the next statement.
erc = AsyncRequest(exch, pRq);
AsyncRequestDirect
The procedure AsyncRequest uses the RequestDirect primitive to send a request block to a specified exchange and then jumps to the user's wait loop. When the request block comes back, the Resume operation will cause execution to continue at the next statement.
erc = AsyncRequestDirect(exch, pRq);}
{Contexts
This section describes how contexts are used with the asynchronous server. We define a context as an instance of program execution along with all its local variables. Local variables are variables allocated on the stack. In this sense, a context is very much like a CTOS process. The difference is that a context may be created and terminated easily.
When the server receives a request, a context will be created to handle the request. When the server finishes processing the request, the context will be terminated, and any memory allocated to the context will be returned to the heap. When a server sends a request of its own, the state of the context will be saved so that it can be restored when the request comes back.
CreateContext
The procedure CreateContext allocates stack space for a context from a heap. The heap will be described later. The user codes:
erc = CreateContext(stackSize, userNum);
stackSize 			is the number of bytes to allocate for the stack of the context to be created. 
userNum 			is the user number to associated with the context. The user number is used by the aborttermination procedures described below. If the context is not to be associated with a client request, (e.g., it's a context to handle a Timer Request Block), the user number should be zero.
If no memory is available from the heap, ercHeapMemoryNotAvail (4533) will be returned. In general, it is a good idea to keep the stack size of a context small  300 bytes is a good size. Large blocks of data used by a procedure should be allocated from the heap when a procedure begins and freed when the procedure has finished. If one procedure that is rarely called uses a large amount of stack space for local variables, it causes all the stack sizes to be inflated, and this wastes memory. The heap allocation procedures are defined below.}
{When control returns from CreateContext with erc = ercOK, the stack pointer has been changed. This means the calling procedure must not use any local variables, because they will not be addressable when control returns from the call.
CreateContext allocates a Context Control Block from the heap and sets a global oCCB to point to it. The CCB has two parts to it  the header and the stack. The header contains the following information, which is accessed only by the AsyncServer library procedures:
pointers to a doubly linked list of active CCBs. 
a list of heap blocks allocated to the context. When the context is terminated, any heap blocks that still belong to the context are freed. 
a pointer to the client request being served by this context. CreateContext saves the global pRq in the CCB header so that it can be restored when the context is resumed. 
the total size of the CCB.
a seal which is used to check the CCB to assure that it does not get overwritten.
The stack portion of the CCB starts at the end of the CCB and grows towards the header.}
{ResumeContext
The procedure ResumeContext continues execution of a context where it left off the last time it called (Build)AsyncRequest(Direct). If a request was built on the stack, it is removed and the erc returned in the request block is stored in the global ercAsync. In any case, the program continues execution where it left off the last time execution was suspended. The user codes:
erc = ResumeContext;
ResumeContext does not take any arguments. It uses the global pRq to find the context to resume. Control does not return from ResumeContext unless an error is detected. If pRq does not point to a request sent out by (Build)AsyncRequest(Direct), ercContextNotFound (4532) is returned.}
{TerminateContext
The procedure TerminateContext is used to terminate a context and return its stack space to the heap. The user codes:
erc = TerminateContext;
TerminateContext does not take any arguments. It uses the global oCCB that points to the current Context Control Block. Any heap space that is still owned by the context is returned to the heap at this time. An erc is returned only if oCCB does not point to a valid context. TerminateContext jumps to the user's wait loop.}
{WaitLoop
This document has often referred to the user's wait loop. The user is required to have a procedure called WaitLoop. The format of the WaitLoop procedure is illustrated in the sample program at the end of this document.}
{CheckContextStack
All of the async request procedures check to be sure the context stack has not overflowed. If a user's procedure does not call any of the async request procedures, a stack overflow could go undetected. The procedure CheckContextStack is provided so that the user can validate the context stack.
erc = CheckContextStack;}
{Other Ways to Use Contexts
The program AsyncServer at the end of this document illustrates how the AsyncServer library procedures can be used by a server. In this example, a context is created by the procedure HandleRequest each time a request is received. 
In general, a context is associated with a request that is being processed, but this need not be the case. A context can also be created to service a timeout when a Timer Request Block is received. The WaitLoop procedure can create a context whenever it receives a message of any kind. The message need not be a request, and the pointer to the message need not be unique. The timeout procedure could send  N messages to the server exchange to cause N contexts to be created. When a context is created, the value of pRq (the pointer to the last message received) is saved in the new Context Control Block, and the value is restored whenever a context is resumed.} 
{Handling System Requests
The handling of abort requests, termination requests and swapping requests requires special consideration. The handling of system requests is often the most difficult and bugprone part of a system service. Two procedures are provided by the AsyncServer library to make the handling of system requests easier.
Termination and Abort Requests
The OS issues a termination or abort request for two reasons:
1.	To guarantee that no requests will be returned to the program after it has been terminated. 
2.	To guarantee that no outstanding requests will access memory that belongs to the program that is terminating.
When a system service receives one of these requests, it is required to Respond to all requests with the same user number and then to Respond to the aborttermination request. If the server does not have any contexts active for the user that is terminating, it can simply respond to the aborttermination request.}
{If the server has active context(s) for the user that is terminating, then the server has one outstanding client request for each context. There is also one asynchronous server request outstanding for each context. The asynchronous request was issued by the server on behalf of the client. The system service should respond to the client request as soon as possible so that it can respond to the aborttermination request. An important question to consider is the following?
Can the server respond to the outstanding client request before the outstanding asynchronous server request has finished?
The answer depends on requirement (2) above. Suppose for example, that the outstanding asynchronous request is reading to or from memory of the program that is terminating. If we responded to the  aborttermination request before the outstanding server request had finished, it could cause memory that belongs to some other program to be corrupted. We can thus answer the above question with a simple rule:
If all the pbcb pairs in the outstanding asynchronous server request point to data in the server's memory, then the request is safe. The server can respond to the client request before the asynchronous server request has finished.}
{The procedure TerminateContextUser responds to all client requests for a given user. It waits for all outstanding asynchronous server requests to return in a way that is transparent to the caller. The user codes:
erc = TerminateContextUser(userNum, erc)
The algorithm used by TerminateContextUser is as follows:
1.	If there are no active contexts for that user, control is simply returned to the caller. 
2.	If the user has outstanding context(s), it sets the terminated flag in the Context Control Block of each context to be terminated. For each context, it responds to the client request immediately if possible (if all the pbcb pairs point to server data). If it can respond to all the outstanding client requests immediately, it returns to the caller.
3.	If it must wait for one or more asynchronous server requests to finish, it jumps to the user's wait loop instead of returning to the caller. The context that called TerminateContextUser is now in a suspended state. The client request of this context is the aborttermination request.
4.	Each time an asynchronous request returns, its context is resumed. When a context that has been flagged terminated is resumed, the ResumeContext procedure calls TerminateContext instead of resuming it. It then responds to the client request if it is still outstanding. If this is the last outstanding client request, it resumes the context that called TerminateContextUser.} 
{SwapContextUser
The procedure SwapContextUser is designed to be used by procedures that handle swap requests. The OS issues a swap request for two reasons similar to aborttermination requests:
1.	To guarantee that no requests will be returned to the program after it has been swapped out.
2.	To guarantee that no outstanding requests will access memory that belongs to the program that is swapped out.
When a system service receives a swap request it is required to Respond to all requests with the same user number and then to Respond to the swap request. The procedure SwapContextUser works very much like TerminateContextUser. The user codes:
erc = SwapContextUser(userNum)}
{The difference is that with SwapContextUser, the server is not finished with the client request. When the user is swapped back in, the context must pick up where it left off. This is handled by the Context procedures as follows.
1.	The procedure SwapContextUser reponds to each client request with error code 37 (service not completed). This causes the OS to reissue the request when the program is swapped back into memory, transparently to the application program which originally issued the request.
2.	When the server sees a new request, it calls CreateContext. CreateContext checks to see if the user specified by the userNum parameter of CreateContext has a context that is waiting to be swapped back in.
3.	When the user has a context waiting to be swapped back in, the request can only be a request that is being reissued by the OS. The CreateContext procedure finds the context for that user that has the same request code as the request that just came in and resumes that context instead of creating a new context.
There is one important consideration about the way swap requests are handled. When control returns to a context after an asynchronous request returns, the client request being handled may not be the same client request the context started with. The request could have been reissued by the OS when the client program was swapped back in. In this case, the pointers in the client request block may not be the same as they were the first time the request was received. If the request came over the cluster, the request block was most likely received in a different buffer, and the pointers were constructed to point to data in a different buffer. This means that the server must not save away any pointers from the client request between asynchronous requests.} 
{Deinstallation
The server receives a request when it is time to deinstall the server. The sample program illustrates the steps required to deinstall the server:
1.	Unserve all requests. When the server was installed, it should have saved the exchange currently serving each request. Each request code should be served to restore it to the previous exchange.
2.	Each active context must be terminated. The user codes:
erc = TerminateAllOtherContexts(erc);
This procedure terminates all contexts except for the calling context. The effect of this procedure is to call TerminateContextUser for every active user, including the server. This causes any contexts that belong to the server to be shut down as well as client contexts. An example of a context that belongs to the server is a context that is handling a Timer Request Block. 
3.	Any further incoming requests must be rejected.
4.	The deinstall request must be handled. Usually, this requires returning the partition handle to the calling program so that the calling program can vacate the partition and remove it.}
{Memory Management
This section describes how memory is managed by the AsyncServer library procedures. The creation and termination of contexts depends on the use of a heap. When a context is created, a Context Control Block is allocated from the heap. The memory is returned to the heap when the context is terminated. 
The heap is allocated out of DS space. The AsyncServer library procedures were designed so that DS and SS are the same. This has a number of advantages. It means all pointers can be short pointers, code size is smaller and faster, and debugging is easier. 
DS Allocation
A server that uses the AsyncServer library procedures must use the DS allocation feature when the program is linked, i.e.,
[DS allocation?]   yes
The result is that the program is loaded into the high end of memory. Space below the program code can be used as a dynamically allocatable area containing data relative to DS.}
{COED Modules
The memory allocation procedures are designed to use the COED Hack if desired. In order to conserve memory space, code that is used only once for initialization can be either returned to the operating system or used for data space when the code is no longer needed. Object modules that are used in this fashion must be processed by the COED program:
Command coed          
Coed                  
  File list

The Coed program takes a list of object modules and changes every segment of class CODE to class COED. When the program is linked, all of the modules of class COED are placed at the bottom of memory so that they can be reallocated for data.}
{AllocMemoryInit
The procedure AllocMemoryInit is used to allocate memory out of DS space. This means that space is allocated from memory below the last CODE segment. Memory is allocated from three areas in the following order:
1.	A memory array in the module InitAlloc.
2.	When the memory array has been used, memory is allocated from any COED modules. The procedure AllocMemoryInit is in a COED module called InitAlloc, so there will always be at least one COED module.
3.	When no more COED space is available, additional memory is allocated from the OS by means of ExpandAreaSL. 
In any case, the memory allocated will be addressable by a short pointer. The user codes:
erc = AllocMemoryInit(cBytes, ppMemoryRet, fInit);
where
cBytes			is the count of bytes to be allocated.
ppMemoryRet			is a pointer to a 4byte memory address into which the pointer of the DS space allocated is returned. 
fInit			should be TRUE if the call is being made from a COED module and the data allocated will be initialized or used before all COED modules have finished execution. The purpose of this is to prevent the user from writing over code that will be executed. AllocMemoryInit will return an erc if fInit is TRUE and the next space to allocate is code space. The user can avoid this problem by allocating memory after all COED modules have finished execution.
When the user's program is linked, the module InitAlloc MUST be the first module in the list of modules. This causes the COED modules (including InitAlloc) to be placed in memory in the proper order.}
{Freeing Leftover Memory
Note that all memory must be allocated before the program makes the call to ConvertToSys. Before the call to ConvertToSys, the program may wish to deallocate any unused memory. This only occurs when the size of the COED modules is greater than the size of memory allocated from DS space. The sample program illustrates how to free unused memory by calling ShrinkAreaSL.}
{The Heap
This section describes the heap procedures. The heap is a linked list of free blocks of memory in DS space. Initially, the list contains only one large block of memory. When a request for heap space is made, the free list is searched until a block big enough to fulfill the request is found. If the block is exactly the size requested, it is unchained from the free list and returned to the caller. If the block is bigger than the amount requested, it is split into two parts. One part is returned to the caller and the other is returned to the free list. The free list is kept in order of increasing addresses so that blocks can be merged with adjacent blocks when they are returned to the free list.
HeapInit
The procedure HeapInit initializes the heap. The user must call HeapInit before any of the AsyncServer library procedures are used. The heap is initialized with a fixed amount of memory. Once allocated, the heap may not be deallocated, and its size may not change. This is not a disadvantage for servers, since they may not allocate any more memory once they have called ConvertToSys. The user codes:
erc = HeapInit(cBytes, pbHeap);
where
cBytes			is the number of bytes to be used for the heap.
pbHeap			is the pointer to the first byte of heap space. This is usually memory that has been allocated by AllocMemoryInit.}
{HeapAlloc
The procedure HeapAlloc allocates memory from the heap. The user codes:
erc = HeapAlloc(cBytes, ppMemoryRet);
where
cBytes			is the number of bytes to allocate.
ppMemoryRet			is a pointer to a 4byte memory address into which the pointer of the DS space allocated is returned. 
An erc is returned (ercNoHeapMemoryAvail = 4533) if there is not enough contiguous memory available in the heap to satisfy the request.}
{HeapFree
The procedure HeapFree returns memory to the heap. The user codes:
erc = HeapFree(pbMemory);
where
pbMemory			is the pointer to the block that was previously allocated from the heap.
An erc is returned (ercInvalidHeapBlock = 4534) if the offset passed to HeapFree is not the address of a valid heap block.}
{Other Ways to Use the Heap Procedures
The heap procedures are used by the AsyncServer library procedures described above to allocate a Context Control Block (i.e., stack space) each time a context is created. The user may also use the Heap procedures for allocating and freeing blocks of memory during program execution. Rather than storing all variables in the stack, it is more efficient to allocate storage for large blocks of data as needed.}
{Using the AsyncServer Library Procedures
Debugging Aids
The AsyncServer library includes procedures to log requests in a trace buffer as they are received and responded to. 
LogMsgIn: PROCEDURE EXTERNAL;
This procedure logs the message pointed to by the global pRq. This procedure is designed to be used by the server when it receives a message at its exchange.
LogRespond(pRq): PROCEDURE EXTERNAL;
This procedure logs the response to the request pointed to by the parameter pRq. This procedure is designed to be used by the server before it responds to a client request. It is also used by the library procedures SwapContextUser and TerminateContextUser before responding to outstanding client requests.
LogRequest(pRq): PROCEDURE EXTERNAL;
This procedure logs the Request pointed to by the parameter pRq. This procedure is called by (Build)AsyncRequest(Direct) before the Request(Direct) primitive is called.}
{The server may wish to use these procedures while debugging and eliminate them at a later time. The library Async.lib has two modules:
LogAsync			contains procedures and a trace buffer.
LogAsyncDmy			contains dummy procedures that do nothing. They are required to link the AsyncServer library procedures with the using server.}
{The trace buffer has the following format:
rgLog is an array of 50 entries. Each 9word entry has the following format:
code		offset 0:		This oneword entry has the following meaning:
AAAA		means message received at server exchange. This entry occurs when LogMsgIn is called.
BBBB 		means request was sent by (Build)AsyncRequest(Direct). This entry occurs when LogRequest is called.
CCCC 		means a response to an async request was received at the server exchange. This entry occurs when LogMsgIn is called.
FFFF 		means we responded to a client request. This entry occurs when LogRespond is called.
pRq		offset 2:		This twoword entry is the pointer to the message.
rq		offset 6:		This sixword entry is the contents of the request block header.
rgLog is a ring buffer. It starts filling from the last entry and fills towards the top of the buffer. This makes it more convenient to look at the buffer with the debugger. The pointer pLog points to the last entry used in the buffer. The convenient way to look at the log is to type
pLog CODE Right Arrow
then down arrow 8 times to see the last entry. To see the previous entry, type down arrow 9 more times, and so forth.}
{Contents of the AsyncServer Release Diskette
The AsyncServer release diskette contains the following files on the <ct> directory:
Async.lib					contains the object modules to be linked with applications that use the AsyncServer library procedures.
Async.edf					contains definitions to be used to compile PLM applications that use the AsyncServer library procedures.
Async.doc					contains this document.
AsyncServer.plm					contains the sample server program illustrated at the end of this document.}
LogAsync.plm					contains the trace log program as described in the section on debugging so that it can be easily modified to suit the user.
{Restrictions
The AsyncServer library procedures are not designed to be used with programs that use more than one process. The use of global variables (pRq, ercAsync, oCCB, and others) precludes use by more than one process. This should not be considered as a disadvantage. The AsyncServer procedures are designed to replace processes and eliminate the problems inherent with applications designed to use more than one process  synchronization and access to shared data.
The AsyncServer procedures do not preclude the use of synchronous requests or the standard procedural interface. The user may want to use synchronous requests in some cases.
System Software Compatibility
The AsyncServer procedures will run on NGENs with CTOS 9.1 or later. The AsyncServer procedures depend on the use of GetpStructure to access the necessary tables in the OS to build request blocks.
Resource Requirements
The AsyncServer procedures described in this document use about 1700 decimal bytes of memory total.}
{AsyncServer Statistics
The AsyncServer library procedures maintain statistics that are helpful when debugging and tuning servers that use these procedures. The global label AsyncStats is a label to look at while in the debugger or when looking at a dump. It is followed by the following statistics:
nStackOverflow					is the number of times stack overflow was detected by BuildAsyncRequest or BuildAsyncRequestDirect.
nCreateContextError					is the number of times CreateContext returned an erc because there was no heap space available to create a context.
nResumeContextError					is the number of times ResumeContext returned an erc because pRq did not point to a request that belonged to an active context.
minStackFree					is the minimum number of bytes free on the stack after building a request block. This is after leaving room for all request overhead and 64 bytes to handle an interrupt.
minHeapFree					is the minimum number of bytes free in the heap.
nBytesHeap					is the number of bytes allocated to the heap.
nBytesHeapFree					is the number of bytes currently free in the heap.
nCcbActive					is the number of contexts currently active.
nCcbActiveMax					is the maximum number of contexts that were active at any one time.}
Error Code Summary
Decimal Value			Meaning
	26			Returned by AllocMemoryInit if fInit is TRUE and the next space to be allocated is COED space. If the memory were allocated out of COED space, code could be written over. See the section on AllocMemoryInit.
	31			No such request code. Returned by BuildAsyncRequest or BuildAsyncRequestDirect if the request code is not recognized. The stack pointer is not valid when this erc is returned because the procedure does not know how many words to remove from the stack. 
	4531			Stack Overflow. Returned by BuildAsyncRequest or BuildAsyncRequestDirect if the request block to be built would overflow the stack. The context should be created with a bigger stack.
	4532			Context Not Found. Returned by ResumeContext if the request block pointed to by pRq does not belong to a context that is waiting for a request.
	4533			No Heap Memory Available. Returned by HeapAlloc if there is not enough contiguous memory available in the heap to satisfy the allocation request. The heap should be allocated more space initially.
	4534			Invalid Heap Block. Returned by HeapFree if the pointer to the block to free is not valid. Either the address is out of range or the header of the heap block has been corrupted. For example, a user might have overwritten one heap block into the next.
	4550			Can't find OS Tables. This occurs if the AsyncServer procedures can't access the tables in the OS to get the tables it needs to build request blocks. The OS may be too old to work with the AsyncServer procedures.
{Server Program Example
$MEDIUM OPTIMIZE(3)
AsyncServer:
DO;
*
	Title:  Template of Server that uses AsyncServer library procedures

	History: 
		08Apr87 Jim Frandeen: created
*
*
	External Definitions
*
$SET(SysLit,rqHeader,TrbType)
$INCLUDE(:f0:Ctostypes.edf)
$SET(AllocExch, ChangePriority, CheckErc, ConvertToSys, QueryRequestInfo)
$SET(Exit, FatalError, GetUserNumber, OpenRTClock, ResetStack, Respond)
$SET(SetPartitionName, ShrinkAreaSL, Wait, GetPartitionHandle, ServeRq, Check)
$INCLUDE(:f0:CtosLib.edf)
$SET(FsErc,RqErc)
$INCLUDE(:f0:Erc.edf)
*
	Library AsyncServer Definitions
*
$SET(All)
$INCLUDE(:f1:Async.edf)

*
	PUBLIC Variables 
*

DECLARE 
 cbHeap WORD PUBLIC INITIAL(2000)
,cDevices WORD INITIAL(3)
,defaultStackSize WORD PUBLIC INITIAL(300)
,fDebug FLAGTYPE PUBLIC INITIAL(TRUE)
,nRqCodes LITERALLY '2' * number of rqCodes we serve *
,priorityServ WORD PUBLIC INITIAL(20h)
,pZero POINTER PUBLIC INITIAL(0)
,rgbPartitionName(*) BYTE DATA('AsyncServer')
,saveSpBp (2) WORD PUBLIC
,pbHeap WORD PUBLIC
,rcOpenFileLL LITERALLY '97'
,rcRead LITERALLY '35'
,rgFileSpec(*) BYTE DATA('FileName')
,rgwOldExch(nRqCodes) WORD PUBLIC
,rgwRqs(nRqCodes) WORD PUBLIC INITIAL (
	* 
		list of request codes served by this server.
	*
	0)
,TimeRq TrbType PUBLIC
;
DECLARE
	 pToDeallocate POINTER * set up by InitAlloc * EXTERNAL 
	,cbFree WORD * set up by InitAlloc * EXTERNAL
	;

*
	PUBLIC Variables required by the AsyncServer procedures:
*

DECLARE ercAsync ErcType PUBLIC;
*
	The erc from BuildAsyncRequest or BuildAsyncRequestDirect is
	returned here.
*
DECLARE exchServ ExchType PUBLIC;
*
	This is the exchange where the server waits in its WaitLoop.
	This variable is stored in the exchResp field of the Request
	block that is build by BuildAsyncRequest or BuildAsyncRequestDirect.
*
DECLARE pRq POINTER PUBLIC;
*
	pRq points to the current client request. It is set by the 
	server in its WaitLoop by the call to Wait:

		erc = Wait(exchServ, @pRq);

	It is saved in the current Context Control Block by BuildAsyncRequest.
	It is restored by ResumeContext.
*

DeinstallServer:
	PROCEDURE ErcType PUBLIC REENTRANT;
		DECLARE erc ErcType
			,ercDiscard ErcType
			,i WORD
			,pRqBlk POINTER
			,rqBlk BASED pRqBlk STRUCTURE(rqHeader)
			,rq BASED pRq STRUCTURE(
				 rqHeader
				,pPhRet POINTER)
			;
		* 
			Unserve all requests by serving them to the exchange
			previously served.
		*
		DO i = 0 to nRqCodes  1;
			erc = ServeRq(rgwRqs(i), rgwOldExch(i));
			IF erc <> ercOK THEN CALL FatalServerError(erc);
			i = i + 1;
		END;
		* 
			Terminate all contexts except for the current context.
		*
		erc = TerminateAllOtherContexts(ercOk);
		IF erc <> ercOK THEN CALL FatalServerError(erc);

		*
			Reject all further incoming requests to  server's exchange.
		*
		DO WHILE erc = ercOk;
			erc = Check(exchServ, @pRqBlk);
			IF erc = ercOk THEN
			DO;
				rqBlk.ercRet = ercServiceNotAvail;
				CALL LogRespond(pRqBlk);
				erc = Respond(pRqBlk);
				IF erc <> ercOK THEN CALL FatalServerError(erc);
			END;
		END;
		*
			Return partition handle to calling program so that the
			calling program can vacate the partition and remove it.
		*
		erc = GetPartitionHandle(@rgbPartitionName, SIZE(rgbPartitionName), rq.pPhRet);
		RETURN erc;
	END DeinstallServer;
FatalServerError:
	PROCEDURE(erc) PUBLIC REENTRANT;
		DECLARE erc ERCTYPE
			;
	IF fDebug THEN CAUSE$INTERRUPT(3) * Call the Debugger *;
	CALL FatalError(erc) * Kill the system service *;

	END FatalServerError;
*
	HandleRequest: The request pointed to by rq is a new Request or a TRB.
	Given a request code, call the routine which processes the request.
	NOTE that this procedure is NOT REENTRANT. After the call to
	CreateContext, its stack pointer has changed, so it cannot access
	any local variables on the stack.
*
HandleRequest:
	PROCEDURE PUBLIC;
		DECLARE erc ERCTYPE
			,i WORD
			,rq BASED pRq STRUCTURE(rqHeader)
			;

		IF rq.rqCode = 0 THEN
		DO;
			CALL HandleTimer;
			RETURN;
		END * of Timer Request *;
		i = FINDW(@rgwRqs(0), rq.rqCode, LENGTH(rgwRqs));	
		IF i = 0FFFFh THEN
		DO;
			rq.ercRet = ercNoSuchRc;
			CALL LogRespond(pRq);
			erc = Respond(pRq);
			RETURN;
		END;
		erc = CreateContext(defaultStackSize, rq.userNum);
		IF erc <> ercOk THEN
		DO * Context could not be started because no heap space *;
			rq.ercRet = erc;
			CALL LogRespond(pRq);
			erc = Respond(pRq);
			RETURN;
		END * of context could not be started *;
		DO CASE i;

		*
			Call procedure to handle Request
			depending on Request code
		*
			erc = DeinstallServer * CASE 0 *;
			erc = ProcessRequest * CASE 1 *;

		END * CASE *;
		rq.ercRet = erc;
		CALL LogRespond(pRq);
		erc = Respond(pRq);
		erc = TerminateContext;
		* 
			Only returns if error  normally calls WaitLoop 
		*
		CALL FatalServerError(erc);
END HandleRequest;*
	HandleTimer: The request in rq is a Timer Request Block
	NOTE that this procedure is NOT REENTRANT. After the call to
	CreateContext, its stack pointer has changed, so it cannot access
	any local variables on the stack.
*
HandleTimer:
	PROCEDURE PUBLIC;
		DECLARE erc ERCTYPE
			;

		erc = CreateContext(defaultStackSize, 0);
		IF erc <> ercOk THEN
		DO * Context could not be started because no heap space *;
				TimeRq.cEvents = 0 * Reenable timer *;			
		END * of context could not be started *;
		ELSE
		DO  * Context started OK *;

			*
				Handle Timer
			*
			erc = TerminateContext;
			* Only returns if error  normally calls WaitLoop *
			CALL FatalServerError(erc);
		END  * of context started OK *;
	END HandleTimer;
Initialize:
	PROCEDURE PUBLIC REENTRANT;
		DECLARE i WORD
			,erc ErcType
			,userNumServ WORD
			;

		* 
			Allocate exchange for server.
		*
		CALL CheckErc (AllocExch(@exchServ));
		CALL CheckErc (ChangePriority(priorityServ));
		* 
			Serve Requests
		*
		DO i = 0 to nRqCodes  1;
			*
				Save the exchange previously served for this request
				so that it can be restored when the server is desinstalled.
			*
			CALL CheckErc(QueryRequestInfo(rgwRqs(i), @rgwOldExch(i), 2));
			CALL CheckErc(ServeRq(rgwRqs(i), exchServ));
			i = i + 1;
		END;
		*
			Allocate memory from DS space to be used for the heap.
		*
		CALL CheckErc(AllocMemoryInit(cbHeap, @pbHeap, FALSE));
		CALL CheckErc(HeapInit(cbHeap, pbHeap));
		*
			Initialize Timer Request Block, if necessary.
		*
		TimeRq.counter = 30;
		TimeRq.counterReload = 30;
		TimeRq.cEvents = 0;
		TimeRq.ExchResp = exchServ;
		TimeRq.rqCode = 0;
		CALL CheckErc(OpenRTClock(@TimeRq));
		*
			Allocate any other memory required for tables, etc from
			DS space.
		*

		IF cbFree <> 0 THEN 
			*
				Free leftover memory used by the initialization code. 
			*
			CALL CheckErc(ShrinkAreaSL(pToDeallocate, cbFree));
		CALL CheckErc(ConvertToSys);
		CALL Exit;
		* 
			CALL Exit must immediately follow ConvertToSys.
			SetPartitionName must come after ConvertToSys.
		*
		erc = GetUserNumber(@userNumServ);
		erc = SetPartitionName((userNumServ AND 0FFh), @rgbPartitionName, 
			SIZE(rgbPartitionName));
	END Initialize;
*
	This is a sample procedure to begin handling a request. Its stack is allocated
	when the context is created. Each time it sends an asynchronous Request, its
	stack pointer and the global pRq are saved in the Context Control Block, and
	control is passed back to WaitLoop.
	When the context is Resumed, the stack pointer and pRq are restored.
*
ProcessRequest:
	PROCEDURE ERCTYPE
		PUBLIC REENTRANT;
		DECLARE fh FHTYPE
			,rq BASED pRq STRUCTURE(rqHeader)
			,oData WORD
			,rgData BASED oData (1024) BYTE
			,(dFileSize, lfa) DWORD
			,(dataRet, cCopyReset) WORD
			,(erc, ercHeap) ERCTYPE
			;
		*
			Allocate buffer from the heap so we don't waste stack space.
		*
		erc = HeapAlloc(SIZE(rgData), @oData);
		IF erc = ercOK THEN
		DO;
			lfa = 0;
			* 
				Send asynchronous Request to open file.
			*
			CALL procBuildAsyncRequest(@fh, @rgFileSpec, 
				INT(SIZE(rgFileSpec)), pZero, INT(0), modeRead, INT(rcOpenFileLL));
			*
				ResumeContext causes control to continue here when Request
				comes back. The stack and pRq are as before we sent the Request.
			*
			erc = ercAsync;
			DO WHILE ((erc = ercOK) AND (lfa < dFileSize));
				CALL procBuildAsyncRequest(fh, @rgData, SIZE(rgData), lfa, @dataRet, 
					INT(rcRead));
				erc = ercAsync;
				IF ((erc = ercOK) OR (erc = ercEOM)) THEN
				DO;
					IF (lfa + dataRet) > dFileSize THEN dataRet = dFileSize  lfa;

				END;
			END * of DO WHILE *;
			* 
				Free heap space allocated for buffer.
			*
			ercHeap = HeapFree(oData);
			IF ercHeap <> ercOK THEN CALL FatalServerError(ercHeap);
		END * of heap allocated *;
		RETURN (erc);
END ProcessRequest;
*
	TimeOut: when the timer issues a request, lets do whatever polling that is necessary.
*
TimeOut:
	PROCEDURE PUBLIC REENTRANT;
		DECLARE i WORD
			,erc ERCTYPE
			;

		DO i = 0 TO cDevices  1;
			;
		END;
		TimeRq.cEvents = 0;
	END TimeOut;
*
	WaitLoop: Main process loop. 
	It waits for a request or a response to come in.
*
WaitLoop:
	PROCEDURE PUBLIC REENTRANT;
		DECLARE erc ErcType
			,rq BASED pRq STRUCTURE(rqHeader)
			;

		erc = ResetStack(.saveSpBp);
		DO FOREVER;
			erc = Wait(exchServ, @pRq);
			CALL LogMsgIn;
			IF erc = ercOK THEN
			DO;
				IF (rq.exchResp = exchServ) AND (rq.rqCode <> 0) THEN
				*
					The request in rq is a response to an asynchronous
					request that we created and sent out. Handle the response.
				*
					erc = ResumeContext;
				*
					Only returns if error was detected.
				*
				ELSE 
				*
					We have a new Request or a Timer Request Block.
				*
					CALL HandleRequest;
			END * of erc = ercOk *;
			IF erc <> ercOK THEN CALL FatalServerError(erc);
		END * of DO FOREVER *;
	END WaitLoop;
* 
	Main program 
*
DO;
	CALL Initialize;
	CALL WaitLoop;
END;
END AsyncServer;
EOF
