Orderly Termination of Programs

Orderly Termination of Programs

By Omar Bashir

Overload, 17(89):, February 2009


Ensuring a clean shutdown is important. Omar Bashir presents some techniques.

Servers are processes, typically on networked computers, that encapsulate and manage a collection of related resources and present functionality related to these resources to client applications [ Coulouris01 ] . Servers wait continuously for requests from clients. Upon receiving requests, servers service the requests and then wait for further requests. As many other applications depend upon servers, termination of their execution should be achieved in an orderly manner. This allows the dependencies to be brought to a state to allow an error-free restart. Also the clients in the process of being served need to either be notified or allowed to complete their sessions.

Servers can be terminated in one of the following two ways,

  1. Sending a special application level termination request.
  2. Sending an operating system level signal to the server.

The former is implemented completely at the application level and can be relatively conveniently synchronized with the operation of the server to perform an orderly shutdown. This method is only useful for networked applications.

However, the latter originates from the operating system (albeit triggered by another application) and needs to be handled by a callback mechanism in the destination server. Furthermore, this approach can also be used to implement orderly termination in standalone applications, as this does not require communicating program termination commands via a networked connection.

Most programming languages provide language level constructs to receive signals (including termination signals) from operating systems. Applications can register callbacks to be triggered on the occurrence of these signals. For signals indicating application termination, registered callbacks can initiate an orderly termination of the application. Most such callbacks may follow a pattern. An object-oriented generalization of one of such patterns is discussed in this article. A simple framework that implements this pattern in C++ is described. This implementation is specific to the Linux platform. Application of this framework in single threaded and multithreaded programs is also illustrated.

An orderly termination mechanism

Servers operate in a loop, i.e., wait for requests to arrive. Once the requests arrive, they process these requests and then wait for further requests. The simplest way to manage orderly termination of a server is to set a flag (referred here to as the shutdown flag) when the server is to be terminated. The server should continuously monitor the shutdown flag and if it is set, the server should initiate the termination process.

Stevens differentiates servers as iterative and concurrent [ Stevens99 ] . Iterative servers handle only one request at a time. If another request arrives while the server is processing a previous request, the new request is queued and is processed once the previous request has been processed. Managing orderly termination in iterative servers can be relatively straightforward and a possible means of achieving this is shown in the state diagram in figure 1.

Figure 1

The server, after initialization, waits for client's requests. In case the request is received, the server processes the request. After processing the request it checks the shutdown flag to determine if a shutdown was initiated. If that flag is set, the server performs the termination operations, e.g., closing down files, database connections etc. If the flag is not set then the server waits for the next request. In case no request arrives, the server times out and checks the shutdown flag in case program termination was initiated while the server was waiting for the request. If that flag is not set then the server again waits for a client's request. Alternatively, if this flag is set, the server performs the necessary termination operations.

Concurrent servers handle multiple requests at one time. There are several approaches to this. A traditional mechanism is to call the Unix fork function to create a separate child process for each client or request. Alternatively threads are spawned instead of child processes to service each client or client's request. Orderly termination of concurrent servers may require a slightly more elaborate process but follows a pattern similar to that for iterative servers.

The state transition diagram in figure 2 shows orderly termination of concurrent servers. Upon receiving a request, the server will spawn a thread to service the request. If the shutdown flag has been set, the server will wait for all the threads it spawned earlier to terminate before initiating server termination.

Figure 2

Finally, a multithreaded server can have a number of continuously running threads, which may service incoming requests independently (therefore, operating as a concurrent server) or they may assist in servicing a single request (therefore, operating as a multithreaded iterative server). Figure 3 shows orderly termination of a multithreaded server with continuously running threads.

Figure 3

Once the server receives a signal to terminate operation, it signals all the threads to terminate their operations. Individual threads within the server may be viewed as iterative servers for this purpose. After signaling all the threads to terminate, the server waits for the threads to join. Once all the threads have joined, the server terminates its operation.

Class structures and dynamics

In the simplest case, a termination handler is based on two participants. As shown in the class diagram in figure 4, these are the ShutdownManager class and the list of Subject s that are notified of imminent program termination by the ShutdownManager . A Subject can be the complete application wrapper or individual classes within the application, e.g., database handler, socket wrapper, etc. A Subject should at least implement the Haltable interface, which should present a method ( shutdown() ) to be called by the ShutdownManager object to notify imminent program termination and a method ( isShuttingDown() ) to determine if the Subject is shutting down in response to a call to the shutdown() method.

Figure 4

Programs using this pattern will have to declare an object of the ShutdownManager class and register all instances of implementations of Haltable using the addHaltable() method. Programs will have to register a callback to receive the program termination signal from the operating system. This callback will call the executeShutdown() method of the ShutdownManager instance, which will iterate through the list of registered Haltable instances and call their shutdown() methods.

Figure 5

Implementation of Termination Handler as shown in figure 4 may be sufficient for single threaded programs. However, for multithreaded programs, Termination Handler needs to be extended as shown in figure 5. Here, the Subject needs to implement the Runnable interface, which extends the Haltable interface. Subject s that need to be executed in separate threads are not directly registered with the ShutdownManager . Rather they need to be decorated by objects of the ThreadOwner class (Decorator pattern, [ Gamma95 ] . ThreadOwner also implements the Haltable interface, therefore allowing objects of the ThreadOwner class to be registered with the ShutdownManager . Objects of the ThreadOwner class execute implementations of Runnable in separate threads. Furthermore, they can allow orderly shutdown in a thread-safe manner. When called from executeShutdown() , the shutdown() method of the ThreadOwner class locks a mutex and then calls the shutdown() method of the Subject allowing the thread in which the Subject is executing to terminate appropriately. Similarly, the isShuttingDown() method of the ThreadOwner class locks the mutex and then calls the isShuttingDown() method of the Subject . As isShuttingDown() method is also called by the Haltable implementations internally to determine when to terminate their operations, Runnable implementations require a reference to their respective ThreadOwner instances so that they can access and invoke the decorated (and thread safe) isShuttingDown() method provided by the ThreadOwner . The setThreadOwner() method of a Runnable implementation is used to establish this association.

Figure 6

Once the executeShutdown() method of the ShutdownManager instance is called, it first iterates through the list of registered Haltable instances and calls their respective shutdown() methods. Then it again iterates through this list and for every Haltable , which is also a ThreadOwner , it calls the join() method of the ThreadOwner to wait for the respective thread to join (Figure 6). Therefore, when executeShutdown() returns, all registered Haltable instances have been notified of termination and if they have been executing in separate threads, those threads should have joined thus allowing the server to terminate in an orderly manner.

Implementation in C++

Listing 1 shows the C++ implementation of the Haltable interface.

    #ifndef HALTABLE_H_
    #define HALTABLE_H_
    namespace haltable{
      class Haltable
      {
        public:
          virtual void shutdown(void)  = 0;
          virtual bool isShuttingDown(void) = 0;
          virtual ~Haltable(){}
      };
    }
    #endif /*HALTABLE_H_*/
Listing 1

As mentioned earlier, each Haltable implementation has to implement the shutdown() method that specifies the operations to be performed for that Haltable implementation upon shutdown. Also isShuttingDown() needs to be implemented to allow the calling method to determine if shutdown() had already been called and the Haltable implementation is in the process of terminating its operation. isShuttingDown() will typically be called inside the main functional loop of the Haltable implementation so that the loop can be exited in response to a shutdown notification.

Listing 2 shows the Runnable interface. This C++ implementation of the Runnable interface is actually an abstract class. It holds a pointer to the ThreadOwner object that will execute a concrete subclass of Runnable in a separate thread. As mentioned earlier, this association with the ThreadOwner object is required to allow execution of thread safe implementations of shutdown() and isShuttingdown() methods provided by ThreadOwner when invoked from within the Runnable implementations. This association is established by calling the setThreadOwner() method and passing it the pointer to the ThreadOwner object which executes this Runnable implementation in a separate thread. Finally, the functionality of a Runnable implementation to be executed in its separate thread is implemented in the execute() method.

    #ifndef RUNNABLE_H_
    #define RUNNABLE_H_
    #include "Haltable.h"
    #include <cstdio>
    namespace haltable{
      class ThreadOwner;
      class Runnable: public virtual Haltable{
         protected:
           ThreadOwner* threadOwner;
         public:
          Runnable(void):threadOwner(NULL){}
          virtual void execute(void) = 0;
          virtual void setThreadOwner(
             ThreadOwner* threadHandler){
            threadOwner = threadHandler;
          }
          virtual ~Runnable(){}
       };
    };
    #endif /*RUNNABLE_H_*/
Listing 2

Listing 3 shows the ThreadOwner class which is an implementation of the Haltable interface.

    #ifndef THREADOWNER_
    #define THREADOWNER_
    #include "Runnable.h"
    #include <pthread.h>
    #include <stdexcept>
    namespace haltable{
    void* executeInThread(void* runnableObj);
    enum ThreadStatus {
      THREAD_NOT_CREATED,
      THREAD_CREATED,
      ERROR_CREATING_THREAD
    };
    class ThreadOwner:public Haltable{
      private:
        std::string name;
        pthread_t threadId;
        Runnable* runnable;
        pthread_mutex_t shutdownMutex;
        ThreadStatus currentStatus;
      public:
        ThreadOwner(const std::string& threadName,
           Runnable* runnableObj):name(threadName),
           runnable(runnableObj),
           currentStatus(THREAD_NOT_CREATED){
          pthread_mutex_init(&shutdownMutex, NULL);
          runnable->setThreadOwner(this);
        }
        const std::string& getName(void){
        return name;
        }
        ThreadStatus start(void){
          if (currentStatus == THREAD_NOT_CREATED){
            if (pthread_create(&threadId, NULL,
               executeInThread, runnable) == 0){
              currentStatus = THREAD_CREATED;
            } else {
              currentStatus = ERROR_CREATING_THREAD;
            }
          }
          return currentStatus;
        }
        void join(void){
          if (currentStatus != THREAD_CREATED){
            std::domain_error exp(
               "Thread has or could not be created.");
            throw exp;
          } else {
            pthread_join(threadId, NULL);
          }
        }
        virtual void shutdown(void) {
          pthread_mutex_lock(&shutdownMutex);
          runnable->shutdown();
          pthread_mutex_unlock(&shutdownMutex);
        }
        virtual bool isShuttingDown(void) {
          bool reply = false;
          pthread_mutex_lock(&shutdownMutex);
          reply = runnable->isShuttingDown();
          pthread_mutex_unlock(&shutdownMutex);
          return reply;
        }
        virtual ~ThreadOwner(){
          pthread_mutex_destroy(&shutdownMutex);
        }
      };
    };
    #endif /*THREADOWNER_*/
Listing 3

A ThreadOwner instance is used to invoke the execute() method of an instance of a Runnable implementation in a separate thread. Thus, for multithreaded applications, ThreadOwner is used as a Subject for the ShutdownManager instance. ThreadOwner uses a mutex, which is locked when the shutdown() and isShuttingDown() methods of the Runnable instance are called from the shutdown() and isShuttingDown() implementations of ThreadOwner thus avoiding any race condition. Constructor of ThreadOwner also calls the setThreadOwner() of the Runnable instance passed to the constructor as a parameter and passes its own pointer to the Runnable instance. This allows the instance of Runnable implementation to call the decorated shutdown() and isShuttingDown() methods provided by the ThreadOwner rather than using its own undecorated methods. start() method of ThreadOwner invokes the executeInThread() function in a new thread. The argument of this function is type-casted as a pointer to Runnable and then its execute() method is invoked to execute the functionality that the Runnable instance provides. Listing 4 shows the implmentation of executeInThread() function.

    #include "ThreadOwner.h"

    void* haltable::executeInThread(void* runnableObj){
      Runnable* runnable = (Runnable*) runnableObj;
      runnable->execute();
      pthread_exit(NULL);
      return NULL;
    }
Listing 4

Listing 5 shows the ShutdownManager implementation. ShutdownManager implements a Singleton pattern [ Gamma95 ] as only one instance of this class should be responsible for managing Haltables and ThreadOwners within a program. Therefore, the constructor is private. Pointer to an instance of this class is obtained by calling a static initialise() method. This method increments the refCount static variable of the class and creates an instance of this class if one has not already been created. Pointer to this instance is assigned to the instance static variable. The pointer to the instance of the class is then returned. Once the instance is no longer required, dispose() method of the object is called. This method decrements the refCount variable and once the value of the refCount variable is zero, the instance of this class being pointed to by the instance class variable is deleted. As both initialise() and dispose() methods access static class members, they lock a mutex ( initMutex ) to ensure thread safety. Finally, the terminate() static method is used to destroy initMutex which is statically initialized. The call to this method should be the last statement in a program.

    #ifndef SHUTDOWNMANAGER_H_
    #define SHUTDOWNMANAGER_H_
    #define SHUTDOWN_MANAGER_DEBUG

    #include <list>
    #include "Haltable.h"
    #include "ThreadOwner.h"
    #include <csignal>
    #include <pthread.h>
    #include <unistd.h>
    #include <stdexcept>
    #include <iostream>

    namespace haltable{
    void handler(int sig);

    class ShutdownManager{
      private:
        std::list<Haltable*> haltables;
        int pipeDescriptors[2];
        static ShutdownManager* instance;
        static int refCount;
        static pthread_mutex_t initMutex;
        ShutdownManager(void): haltables(){
          struct sigaction termAction;
          sigemptyset(&termAction.sa_mask);
          termAction.sa_handler = handler;
          termAction.sa_flags = 0;
          pipe(pipeDescriptors);
          sigaction(SIGTERM, &termAction, NULL);
        }

      public:
        static ShutdownManager* initialise(void){
          pthread_mutex_lock(&initMutex);
          refCount++;
          if (NULL == instance){
            instance = new ShutdownManager();
          }
          pthread_mutex_unlock(&initMutex);
          return instance;
        }
        void dispose(void){
          pthread_mutex_lock(&initMutex);
          refCount--;
          if ((NULL != instance) && (refCount == 0)){
            delete instance;
            instance = NULL;
          }
          pthread_mutex_unlock(&initMutex);
        }
        static void terminate(void){
          pthread_mutex_destroy(&initMutex);
        }
        void addHaltable(Haltable* haltable){
          haltables.push_back(haltable);
        }
        void executeShutdown(void){
          for (std::list<Haltable*>::iterator itr =
             haltables.begin();
            itr != haltables.end(); itr++){
              (*itr)->shutdown();
          }
          for (std::list<Haltable*>::iterator itr =
             haltables.begin();
            itr != haltables.end(); itr++){
              ThreadOwner* threadOwner =
                 dynamic_cast<ThreadOwner*>(*itr);
             if (threadOwner != NULL){
              try{
                threadOwner->join();
              } catch(const std::domain_error& exp){
                std::cout << exp.what() << std::endl;
              }
             }
          }
          char continueChar = 'x';
          write(pipeDescriptors[1], &continueChar,
             sizeof(continueChar));
        }
       void waitForExecuteShutdown(void){
          char continueChar;
          while (read(pipeDescriptors[0],
             &continueChar,
             sizeof(continueChar)) != 1){}
          close(pipeDescriptors[0]);
          close(pipeDescriptors[1]);
        }
    };
    };

    #endif /*SHUTDOWNMANAGER_H_*/
Listing 5

The constructor of this class specifies the handler() function as the signal handler for the SIGTERM signal, the signal sent to a process to notify its termination. The constructor also creates a pipe [ Stevens99b ] to communicate that all the Haltable s have been notified of shutdown and all the ThreadOwner s have joined. The handler() function is invoked once the SIGTERM signal is received by the process. handler() blocks the SIGTERM signal and then invokes the executeShutdown() method of the ShutdownManager 's instance. executeShutdown() invokes the shutdown() methods of all registered Haltable s and then for all Haltable s that are also ThreadOwner s, it invokes their join() methods to wait for them to terminate before proceeding. executeShutdown() then writes a character to the pipe created in the constructor . The main() function of the program should, at the end, call the waitForExecuteShutdown() method of the ShutdownManager 's instance. waitForExecuteShutdown() blocks to read a character from the pipe created in the constructor of ShutdownManager . In a multithreaded application, this allows the main() function to wait for the executeShutdown() method to end before ending the main() function ensuring that all threads have terminated normally.

Listing 6 shows the implementation of the handler() function and also the initialisation of the static data members of ShutdownManager . handler() obtains the pointer to the instance of ShutdownManager by call its initialise() static method. Once it has completed its operation, it releases the instance by calling the dispose() method on the object.

    #include "ShutdownManager.h"
    haltable::ShutdownManager* haltable::ShutdownManager::instance = NULL;
    int haltable::ShutdownManager::refCount = 0;
    pthread_mutex_t haltable::ShutdownManager::initMutex = PTHREAD_MUTEX_INITIALIZER;
    void haltable::handler(int sig){
      haltable::ShutdownManager* shutdownManager =
         haltable::ShutdownManager::initialise();
      struct sigaction termAction;
      sigemptyset(&termAction.sa_mask);
      termAction.sa_handler = SIG_IGN;
      termAction.sa_flags = 0;
      sigaction(SIGTERM, &termAction, NULL);
      shutdownManager->executeShutdown();
      shutdownManager->dispose();
    }
Listing 6

Example - a single threaded application

Listing 7 shows an implementation of a Haltable called the SingleThreadedTimeLogger . Its execute() method opens a specified file and logs system time in that file after every second as long as the isShuttingDown() method returns false . Once the isShuttingDown() method returns true , the execute() method exits the loop and closes the file before ending. The shutdown() method simply sets the shutdownFlag whereas the isShuttingDown() method returns the value of that flag.

    #ifndef SINGLETHREADEDTIMELOGGER_H_
    #define SINGLETHREADEDTIMELOGGER_H_
    #include "Haltable.h"
    #include <fstream>
    #include <string>
    #include <ctime>
    #include <cstdlib>
    #include <unistd.h>
    #include <iostream>

    class SingleThreadedTimeLogger:
       virtual public haltable::Haltable{
      private:
        std::ofstream outFile;
        bool shutdownFlag;
        int sleepTime;
        std::string message;
      protected:
        std::string outFileName;
        bool openFile(void){
          outFile.open(outFileName.c_str());
          return outFile.good();
        }
        void closeFile(void){
          outFile.close();
        }
        void writeTimeToFile(){
          char buffer[128];
          time_t currentTime = time(NULL);
          ctime_r(&currentTime, buffer);
          outFile << message << " :: " << buffer;
          sleep(sleepTime);
        }
      public:
        SingleThreadedTimeLogger(
           const std::string& fileName,
           const std::string& msg):outFile(),
           shutdownFlag(false),
           sleepTime(1),
           message(msg),
           outFileName(fileName){}
        virtual ~SingleThreadedTimeLogger(){
          if (outFile.is_open()){
            outFile.close();
          }
        }
        virtual void shutdown(void){
          shutdownFlag = true;
        }
        virtual bool isShuttingDown(void){
          return shutdownFlag;
        }
        void execute(void){
          if (openFile()){
            while (!isShuttingDown()){
              writeTimeToFile();
            }
            closeFile();
          } else {
            std::cout << "Error opening "
               << outFileName << std::endl;
          }
        }
    };
    #endif /*SINGLETHREADEDTIMELOGGER_H_*/
Listing 7

Listing 8 shows the program that uses an object of SingleThreadedTimeLogger class to log time into the specified file. The main() function of the program instantiates timeLogger object of the SingleThreadedTimeLogger class. The main() function also obtains the reference of ShutdownManager class by calling its initialise() static method and assigns it to shutdownManager variable. timeLogger is added to shutdownManager object's list of Haltable s by passing its pointer to the addHaltable() method. After timeLogger has been added to shutdownHandler 's list of Haltable s, its execute() method is called to start the logging process.

To perform an orderly termination of this application, the user may determine the process ID using Linux's ps command and then kill the application using the kill <process ID> command. As a result, a SIGTERM signal is sent to this application. Upon receipt, the handler() function in ShutdownManager.h is executed to initiate an orderly termination of the program Pentax Q Review .

The main() function releases the instance of the ShutdownManager class by calling the dispose method on the object pointed to by shutdownManager pointer. Finally, before returning, the terminate() static method of the ShutdownManager class is called (see Listing 8).

    #ifndef THREADOWNER_
    #include "ShutdownManager.h"
    #include "SingleThreadedTimeLogger.h"
    #include <fstream>
    #include <string>
    #include <ctime>
    #include <unistd.h>
    #include <iostream>

    int main(void){
      haltable::ShutdownManager* shutdownManager =
         haltable::ShutdownManager::initialise();
      SingleThreadedTimeLogger timeLogger(
         "time_log.txt", "SingleThreadedTimeLogger");
      shutdownManager->addHaltable(&timeLogger);
      timeLogger.execute();
      std::cout << "Exiting application" << std::endl;
      shutdownManager->waitForExecuteShutdown();
      shutdownManager->dispose();
      haltable::ShutdownManager::terminate();
      return 0;
    }
Listing 8

Example - a multi-threaded application

Listing 9 shows the ThreadableTimeLogger , an extension of the SingleThreadedTimeLogger (Listing 7) and the Runnable (Listing 2) classes. Instances of this class can be executed in separate threads using instances of the ThreadOwner class. Implementation of the execute() method is similar to that of the SingleThreadedTimeLogger except that it uses the associated ThreadOwner instance to determine, by calling the ThreadOwner 's isShuttingDown() method, if its shutdown() method has been called. This is because the ThreadOwner instance calls ThreadableTimeLogger 's shutdown() and isShuttingDown() methods after locking a mutex to avoid race conditions. As described earlier, the association between a ThreadableTimeLogger instance and a ThreadOwner instance is achieved via the Runnable abstract class's setThreadOwner() method.

    #ifndef THREADABLETIMELOGGER_H_
    #define THREADABLETIMELOGGER_H_

    #include "Runnable.h"
    #include "SingleThreadedTimeLogger.h"
    #include <pthread.h>
    #include <iostream>

    class ThreadableTimeLogger:
       public SingleThreadedTimeLogger,
       public haltable::Runnable{
      public:
        ThreadableTimeLogger(
           const std::string& fileName,
           const std::string& msg):
           SingleThreadedTimeLogger(fileName, msg),
           Runnable(){}
        virtual ~ThreadableTimeLogger(){}

        void execute(void){
          if (openFile()){
            while (!threadOwner->isShuttingDown()){
              writeTimeToFile();
            }
            closeFile();
          } else {
            std::cout << "Error opening " <<
               outFileName << std::endl;
          }
        }

        virtual void shutdown(void){
          std::cout << "Shutdown called in
             ThreadableTimeLogger" << std::endl;
          SingleThreadedTimeLogger::shutdown();
        }

        virtual bool isShuttingDown(void){
          std::cout << "isShuttingDown called in
             ThreadableTimeLogger" << std::endl;
          return SingleThreadedTimeLogger::
             isShuttingDown();
        }
    };

    #endif /*THREADABLETIMELOGGER_H_*/
Listing 9

Listing 10 shows the program that uses three instances of ThreadableTimeLogger to log time in three different files concurrently. The main() function of this example creates three objects of the ThreadableTimeLogger class and three objects of the ThreadOwner class. Each ThreadableTimeLogger instance is associated with a ThreadOwner instance. ThreadOwner instances are then added to the list of Haltable s in the instance of the ShutdownManager class. start() is then called on each of these objects to start the respective threads for each of ThreadableTimeLogger instances to invoke their execute() methods in. main() then calls the waitForExecuteShudown() method of the ShutdownManager 's instance to block on the pipe internal to the ShutdownManager 's instance waiting for the notification by the executeShutdown() method to signal that all Haltable s have been notified of termination and all ThreadOwner s have joined. This application can also be signaled to terminate by using Linux's kill command. As in the previous example, handle() will call executeShutdown() method of the ShutdownManager 's instance. However, as all the Haltable s in this case are also ThreadOwner s, executeShutdown() will wait for all the respective threads to join before returning.

    #include <fstream>
    #include "ShutdownManager.h"
    #include "ThreadableTimeLogger.h"
    #include <ctime>
    #include <string>

    int main(void){
      haltable::ShutdownManager* shutdownManager =
         haltable::ShutdownManager::initialise();
      ThreadableTimeLogger timeLogger0(
         "time_log_0.txt", "InsideThreadA");
      ThreadableTimeLogger timeLogger1(
         "time_log_1.txt", "InsideThreadB");
      ThreadableTimeLogger timeLogger2(
         "time_log_2.txt", "InsideThreadC");

      haltable::ThreadOwner threadA(
         "ThreadA", &timeLogger0);
      haltable::ThreadOwner threadB(
         "ThreadB", &timeLogger1);
      haltable::ThreadOwner threadC(
         "ThreadC", &timeLogger2);

      shutdownManager->addHaltable(&threadA);
      shutdownManager->addHaltable(&threadB);
      shutdownManager->addHaltable(&threadC);

      if (threadA.start() !=
         haltable::THREAD_CREATED){
        std::cout << "Error starting thread A" <<
           std::endl;
      }
      if (threadB.start() !=
         haltable::THREAD_CREATED){
        std::cout << "Error starting thread B" <<
           std::endl;
      }
      if (threadC.start() !=
         haltable::THREAD_CREATED){
        std::cout << "Error starting thread C" <<
           std::endl;
      }
        shutdownManager->waitForExecuteShutdown();
      std::cout << "Terminating application" <<
         std::endl;
      shutdownManager->dispose();
      haltable::ShutdownManager::terminate();
      return (0);
    }
Listing 10

Concluding remarks

Ensuring orderly termination of applications, particularly servers, can end up being complicated. Various resources being used by these applications need to be brought to consistent states for subsequent error-free restart and various clients need to either be notified of the shutdown or their requests completed before the shutdown. Large applications may contain several objects of many different classes that typically are wrappers over system resources and need to be notified of an impending shutdown. This article has described a pattern that can be used in a framework to allow necessary operations to be performed by respective objects once the application has been notified of its termination. An implementation of a framework based on this pattern and two examples of its use are also described. This framework is written in C++ for Linux.

Acknowledgements

I am grateful to Ric Parkin and the reviewers for their valuable feedback and encouragement.

References

[Coulouris01] G. Coulouris, J. Dollimore, T. Kindberg, Distributed Systems, Concepts and Design, Pearson Education, 2001.

[Gamma95] E. Gamma, R. Helm, R. Johnson, J. Vlissides, Design Patterns, Elements of Reusable Object Oriented Software, 1995.

[Stevens99] W. R. Stevens, Unix Network Programming (Volume 1), Pearson Education 1999.

[Stevens99b] W. R. Stevens, Unix Network Programming (Volume 2), Pearson Education 1999.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.