4.2 Terminating an NLM

An NLM is not properly finished until it terminates successfully. Most NLMs ordered to termination by entry of the UNLOAD command from the console. The following guidelines provide the principles for bringing an NLM to a clean, complete conclusion.

4.2.1 Clean Up All Resources Allocated Anywhere in an NLM

Not only must an NLM free all resources it has allocated, but each thread created by an NLM must also free any resources it has allocated.

Some NLM developers attempt to write an all-purpose cleanup procedure. However, such procedures are difficult to write successfully because they must free resources they did not allocate. Instead, write the NLM in such a way that every thread frees each resource it allocates when the resource is no longer needed. This method is much more effective, and it ensures that the complete NLM will not leave unfreed resources at termination.

4.2.2 Implement a Signal Handler (SIGTERM)

The advantage of using a SIGTERM handler instead of calling AtUnload or atexit is cleanup—when NetWare executes the handler for these functions, all of the NLM threads have been summarily terminated whether or not they have freed resources they allocated.

  1. Using the signal function write a SIGTERM handler. Do not allow the handler to return until your main thread and all other NLM threads have freed allocated resources and terminated.

  2. As one method of SIGTERM handler implementation, create two global integer variables:

    • NLM_exiting, initially set to FALSE (0)

    • NLM_threadCnt, initially set to zero (0)

  3. For the first statement in the NLM main function, increment NLM_threadCnt.

  4. For the last statement in the NLM main function, decrement NLM_threadCnt.

  5. In the course of running, ensure that all loops within the NLM monitor the NLM_exiting variable. If NLM_exiting is ever set to TRUE, have all threads free any allocated resources and self terminate.

The following code illustrates one signal handler implementation:

  void  NLM_SignalHandler (int sig) 
      { 
        switch (sig) 
        { 
        case SIGTERM: 
           NLM_exiting = TRUE; 
           while (NLM_threadCnt != 0) 
              ThreadSwitchWithDelay() 
           break; 
        } 
     return; 
     }
  

4.2.3 Provide CLIB Context for the SIGTERM Handler if Needed

The SIGTERM handler is executed by an OS thread-the Console prompt thread. (Operating system threads cannot take advantage of most functions offered in the NetWare SDK) While the SIGTERM handler has control over this operating system thread, it accepts and executes commands from the server console screen. That fact makes two things very important to understand:

  • The console command prompt will not be available until the signal handler releases it.

  • Your SIGTERM signal handler must not destroy the thread executing it because that would destroy the command prompt. Therefore, do not call exit from your SIGTERM handler.

Under some circumstances, your SIGTERM handler might need to call NetWare SDK functions that require full CLIB context. It is possible to borrow a CLIB context, effectively converting the operating system thread to a CLIB thread. The following instructions explain how to do this:

  1. Select an active thread in your NLM that already has CLIB context. The NLM main function is generally suitable for this purpose.

  2. Call GetThreadGroupID and store the ID of the main function into a global variable such as NLM_mainThreadGroupID.

  3. Call GetThreadGroupID again and store the existing operating system context of the SIGTERM handler thread.

  4. Call SetThreadGroupID and assign the borrowed CLIB context stored in NLM_mainThreadGroupID to the SIGTERM handler operating system thread.

  5. When operations requiring CLIB context are completed, call SetThreadGroupID again to set the SIGTERM handler thread back to its original context.

4.2.4 Allow for Blocked or Suspended Code at UNLOAD

  1. Recognize a possible deadlock if an UNLOAD command is issued while a function is blocked.

    In the code below, assume that the body of main includes a function such as getch that blocks (or suspends) thread execution until a character is received from the keyboard. The console operator will likely attempt an UNLOAD routine at some time when the NLM is waiting for keyboard input. In this condition, the SIGTERM handler waits for main to decrement the NLM_threadCnt value to zero before proceeding. However, main does not decrement the value until it receives a character. As a result, the system console screen appears to be hung and the NLM does not unload as requested.

  2. If a function is blocked when an UNLOAD command is issued, call a function that can allow processing to continue.

    The SIGTERM handler is responsible for waking up any blocked or suspended threads so that they can become aware of the NLM_exiting value. (In turn, each thread is responsible for checking the NLM_exiting value as often as appropriate.) The SIGTERM handler can help wake up a thread blocked on the getch function by calling the ungetch function and stuffing a character into the keyboard buffer. This character is then read out of the keyboard buffer by the blocked getch and execution can proceed. Other blocking functions to watch out for include gets, t_snd, NWSList, NWSMenu, SuspendThread, and delay.

  int NLM_mainThreadGroupID;  
  int NLM_threadCnt = 0;  
  int NLM_exiting = FALSE; 
   
  void NLM_SignalHandler(int sig) 
     { 
     int handlerThreadGroupID; 
     switch(sig) 
        { case SIGTERM: 
           NLM_exiting = TRUE; 
           handlerThreadGroupID = GetThreadGroupID(); 
           SetThreadGroupID(NLM_mainThreadGroupID); 
   
           /* NLM SDK functions may be called here */ 
   
           while(NLM_threadCnt != 0) ThreadSwitchWithDelay(); 
           SetThreadGroupID(handlerThreadGroupID); 
           break; 
        } 
     return; 
     } 
  void main(void)  
     { 
     ++NLM_threadCnt; 
   
     NLM_mainThreadGroupID = GetThreadGroupID(); 
     signal(SIGTERM, NLM_SignalHandler); 
   
     /* Body of main continues here... */ 
   
     -–NLM_threadCnt; 
     return; 
     }
  

4.2.5 Allow for Child Threads and Call-backs

The main function, all child threads, and any call-back routines should use the NLM_threadCnt variable to keep make sure resources are cleaned up for NLM termination.

  1. As shown in the above sample, write main to increment the NLM_threadCnt as its first action and to decrement NLM_threadCnt as its last.

  2. If your NLM calls BeginThread or any similar function, ensure that the spawned thread increments and decrements the NLM_threadCnt in the same way that main does.

  3. If your NLM sets up call-back routines, ensure that each of those routines increments and decrements the NLM_threadCnt as does main.

    Call-back routines might include functions such as NWAddFSMonitorHook, NWRegisterNSPExtension, and RegisterForEvent.

4.2.6 Allow for Normal NLM Termination

When it is appropriate, allow your NLM to terminate normally. The steps below can safeguard against premature termination:

  1. Ensure that your NLM does not terminate until the NLM_threadCnt is decremented to 0.

  2. Ensure that your main thread never terminates until the NLM_threadCnt has a value of 1, indicating that only main is still running.

Implementing these safeguards allows any thread in your application to shut down the NLM by setting NLM_exiting to TRUE, but it also forces main to stay alive until all other NLM threads have terminated. The following code sample illustrates this:

  void main(void) 
     { 
     ++NLM_threadCnt; 
   
     NLM_mainThreadGroupID = GetThreadGroupID(); 
     signal(SIGTERM, NLM_SignalHandler); 
   
     /* Body of main continues here... */ 
   
     while(NLM_threadCnt != 1) 
        ThreadSwitchWihDelay(); 
   
     -–NLM_threadCnt;
     return;
     }
  

4.2.7 Protect Against CTRL-C

Users can break out of your NLM using CTRL-C. You can avoid this in either of two ways:

  • Disable CTRL-C functionality by calling SetCtrlCharCheckMode.

  • Register a SIGINT signal handler that causes your NLM to ignore CTRL-C, as illustrated in the code example below.

    Notice that the SIGINT signal handler must be reregistered each time a CTRL-C event occurs.

  int NLM_mainThreadGroupID; 
  int NLM_threadCnt = 0; 
  int NLM_exiting = FALSE; 
   
  void NLM_SignalHandler(int sig) 
     { int handlerThreadGroupID; 
     switch(sig) 
        { case SIGTERM: 
           NLM_exiting = TRUE; 
           handlerThreadGroupID = GetThreadGroupID(); 
           SetThreadGroupID(NLM_mainThreadGroupID); 
   
           /* NLM SDK functions may be called here */ 
   
           while(NLM_threadCnt != 0) 
              ThreadSwitchWithDelay(); 
   
            SetThreadGroupID(handlerThreadGroupID); 
            break; 
   
        case SIGINT: 
           signal(SIGINT, NLM_SignalHandler); 
           break; 
        } 
     return; 
     } 
   
  void main(void) 
     { 
     ++NLM_threadCnt; 
   
     NLM_mainThreadGroupID = GetThreadGroupID(); 
     signal(SIGTERM, NLM_SignalHandler); 
     signal(SIGINT, NLM_SignalHandler); 
   
     /* Body of main continues here... */ 
   
     -–NLM_threadCnt; 
     return; 
     }