Important: The information in this document is obsolete and should not be used for new development.
Using Providers
This section explains how you obtain and change a provider's mode of operation; it provides a more detailed discussion of asynchronous processing and the use of notifier functions; and it explains how you close a provider.In addition to the functions used to set a provider's mode of operation and to close a provider, general provider functions include the
OTIoctl
function, which you can use to communicate directly with a STREAMS module implementing a networking protocol. For more information, see the description of that function in Providers Reference.Controlling a Provider's Modes of Operation
A provider's mode of operation determines how provider functions execute and determines the behavior of provider functions that send and receive data. You can control a provider's mode of operation by calling general provider functions to specify whether provider functions execute synchronously or asynchronously, whether provider functions can block, and whether they can acknowledge sends. The following three sections provide additional information about how you can obtain a provider's current mode of operation and how you can change it.Which Mode To Use
Use the following guidelines in determining which mode to use:
- For easiest programming,
If you are using threads, use synchronous, blocking mode and call the function
OTUseSynchIdleEvents
.If you do not use threads, use synchronous, nonblocking and poll for events using the function
GetEndpointState
.Using providers in synchronous mode makes for very easy coding; however, if they are also blocking, this could severely affect performance. One way to manage this problem is to call the function
OTUseSyncIdleEvents
just after setting the provider's mode of operation. This function generates events of the type
kOTSyncIdleEvents
and sends them to your notifier while Open Transport is waiting to complete a synchronous call. On receipt of this event, your notifier should call the system functionYieldToAnyThread
; this transfers execution to another thread, thus allowing processing to continue while your synchronous operation waits to complete. You should avoid calling functions in synchronous mode at non-System-Task time.
- For best performance, use asynchronous blocking mode.
Asynchronous processing requires some additional work: you must make sure that memory you have allocated for a function's output parameters is persistent and you must use some sort of mechanism to determine when the function has actually completed. These issues are taken up in the section "Using Notifier Functions to Handle Provider Events".
- Never use asynchronous nonblocking mode.
Specifying How Provider Functions Execute
For each provider, you can control whether provider functions run synchronously or asynchronously. When you open a provider, you set its default mode of execution. For example, when you open an endpoint provider, you can use either the functionOTOpenEndpoint
orOTAsyncOpenEndpoint
. If you open an endpoint provider using theOTAsyncOpenEndpoint
function, Open Transport creates the provider and sets the default execution mode for all the provider's functions to asynchronous.A provider's default mode of execution remains in effect until you change it by calling either the
OTSetSynchronous
function or theOTSetAsynchronous
function. The new mode remains in effect until you change the mode again. A provider's mode of execution affects only that provider. If you use two or more providers, they need not operate in the same mode.You should be aware that mixing synchronous and asynchronous calls can cause critical problems. Take the following sequence as an example:
The problem is that the notifier function, called in step 4, now executes with the provider in synchronous mode: the mode of execution is determined when a function is called. Thus any Open Transport function called in the notifier will execute synchronously. However, functions called from a notifier may not execute synchronously; therefore your system will return an error. To avoid this problem, make sure there are no outstanding asynchronous requests when switching to synchronous mode.
- Set asynchronous mode.
- Call a function.
- Set synchronous mode; call a function.
- The function called in step 2 completes, and the notifier installed for that provider executes at deferred task time.
The return behavior of certain provider functions is controlled not only by a provider's mode of execution but also by the provider's blocking status, described in the following section. Changing a provider's mode of execution does not change its blocking status.
Setting a Provider's Blocking Status
A newly created provider does not block, regardless of which Open Transport function created it. After a provider is created, you can change its blocking status as often as you like. A provider's blocking status affects only that provider.
If a provider is blocking and you call a function synchronously, all processing on the Macintosh is halted until the synchronous function completes. For information on how to handle this situation, see "Specifying How Provider Functions Execute".
- You use the
OTSetBlocking
function to set a provider's mode of operation
to blocking.- You use the
OTSetNonBlocking
function to set a provider's mode of operation to nonblocking.- You use the
OTIsNonBlocking
function to determine whether a
provider blocks.
If a provider is nonblocking, provider functions that cannot complete send or receive operations return an error indicating the reason. The result returned might be
In many of these cases, you should call the function again.
kEAGAINErr
orkEWOULDBLOCKErr
, indicating that the function would have to be queued before it could executekOTNoDataErr
, indicating that data has not yet arrivedkOTFlowErr
, indicating that the provider is flow controlled.KENOMEMErr
, indicating that there is not enough memory
Setting a Provider's Send-Acknowledgment Status
You can control the behavior of provider functions that send data by specifying that the provider not make an internal copy of the data it is sending, but that it relies entirely upon the data being in the buffer you provide. Asking the provider not to make a copy is the same as asking it to acknowledge sends (the Open Transport phrasing). In the current version, you can only specify that endpoint providers acknowledge sends. For more detailed information about this mode of operation, see "Acknowledging Sends".Using Notifier Functions to Handle Provider Events
When provider functions execute asynchronously, you can continue processing without having to wait for a function to complete execution. In some cases, you might need to know when the function has finished executing, either because further processing depends on the results of that operation or because you need to use memory you have allocated for that function. In order to meet this need, the Open Transport architecture defines completion events, which are generated by a provider when an asynchronous function completes execution. To pass the event to your application as well as other information about the function that has completed, the provider calls a notifier function that you have written and installed for that provider.The provider uses the notifier's parameters to pass the following information back to your application:
If you open a provider in asynchronous mode, you install a notifier function by passing a pointer to it in one of the parameters to the function used to open the provider. If you open a provider in synchronous mode, you must install the notifier by calling the
- a context pointer for your use
You define this pointer when you install the notifier function. When the provider calls the notifier, it passes this pointer back to you. It is typically the
ProviderRef
or a data structure that contains theProviderRef
.
- an event code identifying the provider event
- the function result if it's a completion event.
- a pointer to additional information that the function is returning
This parameter is called the
cookie
parameter. For example, when you call a function that assigns an address to an endpoint, you can request a particular address. When the function returns, it passes back the address that is actually assigned to the endpoint. If you call the function asynchronously, this information is referenced by thecookie
parameter.
OTInstallNotifier
function . If you want to change notifiers, you must first remove the old notifier by calling theOTRemoveNotifier
function and then call theOTInstallNotifier
function to install the new notifier.You are responsible for the contents of a notifier function. Typically, such a function tests to see whether the function that just completed has returned an error. If it has not, it uses a
switch
statement to transfer control to different subroutines, depending on the event code passed to the notifier. In the notifier shown Listing 3-1 fatal errors all break out of the switch to the default case. The notifier sample is intended to give you a sense of how such code is structured. In general, the notifier does not need to handle every completion event, just those that you expect to happen and that have meaning for the provider you are opening. You should ignore any events you are not expecting.Listing 3-1 A notifier function
static pascal void Notifier(void* context, OTEventCode event, OTResult result, void* cookie) { EPInfo* epi = (EPInfo*) context; switch (event) { case T_LISTEN: { DoListenAccept(); return; } case T_ACCEPTCOMPLETE: { if (result != kOTNoError) DBAlert1("Notifier: T_ACCEPTCOMPLETE - result %d", result); return; } case T_PASSCON: { if (result != kOTNoError) { DBAlert1("Notifier: T_PASSCON result %d", result); return; } OTAtomicAdd32(1, &gCntrConnections); OTAtomicAdd32(1, &gCntrTotalConnections); OTAtomicAdd32(1, &gCntrIntervalConnects); if ( OTAtomicSetBit(&epi->stateFlags, kPassconBit) != 0 ) { ReadData(epi); } return; } case T_DATA: { if ( OTAtomicSetBit(&epi->stateFlags, kPassconBit) != 0 ) { ReadData(epi); } return; } case T_GODATA: { SendData(epi); return; } case T_DISCONNECT: { DoRcvDisconnect(epi); return; } case T_DISCONNECTCOMPLETE: { if (result != kOTNoError) DBAlert1("Notifier: T_DISCONNECT_COMPLETE result %d", result); return; } case T_MEMORYRELEASED: { OTAtomicAdd32(-1, &epi->outstandingSends); return; } default: { DBAlert1("Notifier: unknown event <%x>", event); return; } } }You can use a notifier function to handle asynchronous events as well as completion events. A provider uses asynchronous events to inform your application that data has arrived or that a connection or disconnection request is pending.The method used is the same as for completion events. You must includecase
statements in the notifier that are pertinent to the asynchronous events you expect to receive.The provider calls your notifier function at deferred task time or at system task time. This means that the routines called from your notifier
The only exception to these rules occurs when you are responding to the event
- might need to be reentrant
- cannot move or purge memory
- cannot depend on the validity of handles to unlocked blocks
- should not perform time-consuming tasks
- should not make synchronous calls to Open Transport
- should not make synchronous Device Manager or File Manager calls
kOTProviderWillClose
. See the event codes enumeration for additional information.Open Transport might call a notification routine reentrantly. Open Transport attempts to queue calls to a notification routine to prevent reentrancy and to keep the processor stack from growing, but this behavior is not guaranteed. You should be prepared and write your notification routine defensively. For additional information, see "MyNotifierCallbackFunction".
If you execute provider functions asynchronously, you must also take special care about the duration of the function's variables. A function that is executed asynchronously returns immediately, and the stack frame of the function that called it might be torn down before you have had a chance to retrieve the information returned in the parameters to the asynchronous function (using the notifier function's
cookie
parameter). If these parameters are local variables in the calling function, the information passed back by the asynchronous function is lost. To avoid this situation, you need to write the function that calls the asynchronous function in such a way that the memory pointed to by its return parameters is not overwritten. For example, you could make these variables global or use the functionOTAllocMem
to allocate them.Transferring a Provider's Ownership
An Open Transport client is any task that calls theInitOpenTransport
function. Open Transport keeps track of the owner of each provider, and when a client dies or quits without closing all of its outstanding providers, Open Transport attempts to close them on behalf of the client. Every shared library, code resource, or program that creates an endpoint, or uses one of the endpoint functions that allocate memory on behalf of the client, is a client of Open Transport. For ASLM shared libraries and applications, Open Transport can clean up after the library or application easily. For CFM shared libraries and code resources, however, the client must callCloseOpenTransport
before terminating (this can be done by makingCloseOpenTransport
the termination procedure for the CFM library).Although it's not a frequent occurrence, there may be times when it is not convenient for you to lose access to a provider. For example, if you are still using a provider created by a shared library when that shared library is unloaded or you are still using a provider reference passed by another application when that application quits, you will find yourself using invalid references unexpectedly.
In cases where you do not want Open Transport to close a given provider, you can define yourself as its new owner with the OTTransferProviderOwnership function . You need to obtain the previous owner's client ID before the client terminates, and then pass it to Open Transport along with the provider reference for the provider. Open Transport allocates a new provider reference and returns the new reference to you. The old provider reference is then invalid and should not be used. Listing 3-2 furnishes an example of transferring a provider's ownership. In this example, an Open Transport client, the ProviderFactory library, creates an endpoint. It then passes the endpoint reference back to another Open Transport client, the TransferProvider application. The application is responsible for transferring the ownership of the endpoint from the library to itself before shutting down the library; it does so using the
GetProviderFromFactory
function.Listing 3-2 Transferring provider ownsership
static OSStatus GetProviderFromFactory(void) { OSStatus err; EndpointRef originalEndpoint; OTClient originalOwner; EndpointRef newEndpoint; TEndpointInfo newEndpointInfo; /* Use the factory library to create an endpoint.*/ err = FactoryCreateEndpoint(&originalEndpoint, &originalOwner); if (err == noErr) { /* Transfer the ownership of endpoint, so that OT knows we now own it */ newEndpoint = OTTransferProviderOwnership(originalEndpoint, originalOwner, &err); if (err == noErr) { /* We can now use newEndpoint as if we created it. */ /* We call OTGetEndpointInfo as an example of an operation */ /* on the endpoint.*/ err = OTGetEndpointInfo(newEndpoint, &newEndpointInfo); if (err == noErr) { printf("Maximum size of endpoint address = %ld.\n", newEndpointInfo.addr); } OTCloseProvider(newEndpoint); } } return err; } void main(void) {/* initialize connection to Open Transport */ OSStatus err; err = InitOpenTransport(); if (err == noErr) { /* initialize the provider factory library. */ err = InitProviderFactory(); if (err == noErr) { /* call GetProviderFromFactory to demonstrate */ /* use of OTTransferProviderOwnership */ err = GetProviderFromFactory(); CloseProviderFactory(); } CloseOpenTransport(); } if (err == noErr) { printf("Success!\n"); } else { printf("Failed with error %ld.\n", err); } }Closing a Provider
There are two instances in which you need to close a provider:
Closing a provider deletes all memory reserved for it in the system heap, deletes its resources, and cancels any provider functions that are currently executing.
- when you are through using the services offered by a provider
You do this by calling the
OTCloseProvider
function and passing the provider reference of the provider you wish to close.
- in response to a
kOTProviderWillClose
event or akOTProviderIsClosed
event.If you get a
kOTProviderIsClosed
event, the service underlying your provider is already gone; closing the provider only frees up memory resources.
If you have opened a provider asynchronously (for example, by calling the
AsyncOpenEndpoint
function), it is not possible to close it before the call has completed. This might happen if the user quits the application before the provider has opened. For this reason, it is safer to open a provider synchronously and then to use theOTSetAsynchronous
function to set the execution mode.The blocking status of a provider governs what happens when the provider is closed. In non-blocking mode, closing the provider flushes all outgoing commands in the stream and immediately closes the provider. In blocking mode, the stream is given up to 15 seconds per module to allow outgoing commands and data to be processed before the stream is closed.
If you are closing a provider in response to a
kOTProviderWillClose
event, note that Open Transport issues this event only at system task time. Thus, you can set the endpoint to synchronous mode (from within the notifier function) and call functions synchronously to do whatever clean-up is necessary before you return from the notifier.
Subtopics
- Controlling a Provider's Modes of Operation
- Which Mode To Use
- Specifying How Provider Functions Execute
- Setting a Provider's Blocking Status
- Setting a Provider's Send-Acknowledgment Status
- Using Notifier Functions to Handle Provider Events
- Transferring a Provider's Ownership
- Closing a Provider