#define capIsPreemptive (unsigned long) 0x00000100
/* new parameters */
enum {
piServiceList } ;
/* Memory Functions */
void* WSAPI_AllocatePoolMemory(WSAPI_CommandPBPtr pb, long size) ;
void* WSAPI_ReallocatePoolMemory(WSAPI_CommandPBPtr pb, void *ptr, long new_size) ;
void WSAPI_FreePoolMemory(WSAPI_CommandPBPtr pb, void* ptr) ;
void* WSAPI_ReallocateMemory(WSAPI_CommandPBPtr pb, void *ptr, long new_size) ;
/* Custom Command PBs */ WSAPI_ErrorCode
WSAPI_NewCommandPB(WSAPI_CommandPBPtr *customPB) ;
WSAPI_ErrorCode WSAPI_DisposeCommandPB(WSAPI_CommandPBPtr *customPB) ;
/* Service API */
typedef unsigned long (*WSAPI_ServiceFunc)(WSAPI_CommandPBPtr pb, void *arg) ;
WSAPI_ErrorCode WSAPI_RegisterService(WSAPI_CommandPBPtr pb, char *name, WSAPI_ServiceFunc func) ;
WSAPI_ErrorCode WSAPI_CallService(WSAPI_CommandPBPtr pb, char *name, void *arg, unsigned long *result) ;
As MacOS X nears, it is expected that some plug-in developers will merely carbonize their plug-ins while others will want to take full advantage of the new operating system. MacOS X supports not only differing thread models (preemptive and cooperative), but also multiple executable formats (CFM and Mach-O). It is not alone in this regard, as other platforms have been known to support more than one executable type.
The purpose of this specification is to outline changes in the WSAPI necessary to allow full exploitation of the underlying operating system (regardless of brand), while at the same time allowing for easy portability for existing plug-ins.
If a plug-in sets the capIsPreemptive flag during the WSAPI_Register command, it is claiming to be preemptive. The plug-in must be completely thread-safe, meaning multiple commands can be dispatched to the plug-in and run concurrently.
If a plug-in fails to set the capIsPreemptive flag, then the server should treat the plug-in as only being safe for cooperative threading. A thread running within a cooperative plug-in should only be preempted by another thread executing code in the same plug-in when the running thread uses the server callbacks. Cooperative plug-in authors should assume that any callback can yield, and that network-related callbacks will almost certainly yield.
As plug-ins become more complex it has become common to access server callbacks "out of context", meaning outside the context of any command received from the server. Typically such plug-ins will create their own threads to perform various maintenance tasks that are too elaborate to be done inside the idle thread. To access server callbacks from these threads, developers have often used NULL parameter blocks, or even constructed fake parameter blocks to access callbacks that require a valid PB.
This specification adds callbacks for creating "out of context" PBs for use in such threads. WSAPI_NewCommandPB() returns WSAPI_I_NoErr and changes the value of the customPB parameter to point to the new PB. If the callback returns an error code then the value of customPB is undefined. WSAPI_DisposeCommandPB() is used to dispose of the PB when it is no longer needed.
Custom PBs are not thread-safe. This means plug-ins that create custom PBs must ensure that any given PB is not used in two or more threads running concurrently. Server implementations should allow all callbacks to be accessed with custom PBs, excepting those that are restricted to specific run roles. A custom PB can be created with WSAPI_CreateCustomPB using NULL as the input PB, however, a good PB should be used. The WSAPI_CreateCustomPB is thread-safe.
It has been well-established that heap management has a direct and significant impact on server performance. An HTTP request that does not stray from the critical path of the server code may require only four or five heap operations, but a request that produces dynamic output may require several hundred such operations. In a preemptive environment each request to the global heap requires synchronisation, which can quickly become very expensive. Clearly, high-performance plug-ins need a more efficient memory model. By associating a memory "pool" with each request, implementations can greatly reduce the occurrence of heap synchronisation.
The WSAPI_ReallocateMemory() callback is added because reallocation is typically much more efficient than the alternative. Some heap managers can "grow" a memory location much more efficiently than allocating a new space and moving the data. If NULL is returned, then the request failed and the old pointer is still valid. If a non-NULL value is returned then the request succeeded, and the old pointer is invalid. Note that in the ideal case, the old and new pointers will be the same, meaning the existing space was simply enlarged.
The WSAPI_AllocatePoolMemory(), WSAPI_ReallocatePoolMemory(), and WSAPI_FreePoolMemory() work almost identically to the existing memory calls. The difference is that they require a valid PB (unlike the existing calls), and memory MUST be freed/reallocated with the same PB as was used to allocate it.
WSAPI has supported the concept of plugin-to-plugin services for a long time. Allowing plug-ins to talk to each other directly to share data and functionality that the server does not directly provide has proven to be a highly useful concept. It is used in the WebSTAR Data Cache, the VDMAPI, and the PIXO standard.
Having mixed threading and binary models introduces new complications to that process. Preemptive plug-ins should not be calling services located in cooperative plug-ins without some kind of additional synchronisation, and Mach-O binaries can not call services inside CFM binaries without some glue code to fix the cross-TOC calling conventions. While it is fairly easy for the server to prevent a plug-in from looking up a service registered by an incompatible plug-in, it is more desirable to allow all these plug-ins to interoperate without restriction. To that end, new service management callbacks are proposed. By passing all service calls through the server this standard opens the possibility of cross-binary-format/thread-style service calls.
The user-defined WSAPI_ServiceFunc function type takes a valid PB and argument as input, and returns an unsigned long. Services are registered with the WSAPI_RegisterService() callback, which takes as its parameters a valid PB, a null-terminated service name, and a pointer to the registered function. Registering a NULL function un-registers a service by the same name registered by the same plug-in. Valid names are composed of alphanumeric characters, underscore "_", and spaces " ". An attempt to register a service under a name already used by a different plug-in will return the WSAPI_E_DuplicateService error.
The piServicesList returns a TAB-delimited list of registered services.
WSAPI_CallService() causes the server to pass the given argument and parameter block to the service registered under the given name. If the service can not be located, the server will return the new error code WSAPI_E_ServiceNotFound. If the server can not reconcile the differing thread/format types of the two plug-ins, the error code WSAPI_E_ServiceNotCompatible will be returned.
Backward compatibility: the use of the piServiceAddress parameter in new plug-ins is deprecated. Server implementations may continue to support it, but if they do so they should not allow a plug-in of one thread/format type to look up a service registered by a plug-in of a different thread/format type.
Server implementations should issue the WSAPI_Register and WSAPI_Init commands before becoming multi-threaded. Plug-in developers should assume that any commands received after WSAPI_Init take place in a multi-threaded environment.
The requirement for support of cooperative plug-ins is very deliberately defined in terms of how concurrent threads are scheduled within a given plug-in, as opposed to specifying the nature of the thread itself. This allows implementors to support cooperatively written plug-ins even on systems that lack the concept of non-preemptive threading. The WebSTAR/5 implementation creates only preemptive threads, but uses semaphores to prevent multiple threads from executing inside a cooperative plug-in. Developers can assume that their plug-in code won't be interrupted by another thread executing within the same plug-in, but should not assume they won't be preempted by other applications or threads operating within other plug-ins.
Implementations that do not wish to support memory pools can simply map the pooled memory callbacks to their normal heap management functions. WebSTAR will initially do this until an appropriate pool manager can be incorporated into the server.