Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Networking With Open Transport / Part 1 - Open Transport Essentials
Chapter 3 - Providers


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:

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 function OTOpenEndpoint or OTAsyncOpenEndpoint. If you open an endpoint provider using the OTAsyncOpenEndpoint 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 the OTSetAsynchronous 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:

  1. Set asynchronous mode.

  2. Call a function.

  3. Set synchronous mode; call a function.

  4. The function called in step 2 completes, and the notifier installed for that provider executes at deferred task time.

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.

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".

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.

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 OTInstallNotifier function . If you want to change notifiers, you must first remove the old notifier by calling the OTRemoveNotifier function and then call the OTInstallNotifier 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 include case 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 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 function OTAllocMem to allocate them.

Transferring a Provider's Ownership

An Open Transport client is any task that calls the InitOpenTransport 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 call CloseOpenTransport before terminating (this can be done by making CloseOpenTransport 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.

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 the OTSetAsynchronous 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

Previous Book Contents Book Index Next

© Apple Computer, Inc.
15 JAN 1998