Multiprocess associated operation

This type of custom server consists of a parent process, and a number of child processes in one to one correspondence with the channel processes linked to the custom server.

The parent process is responsible for reading all requests from channel processes. It maintains data which allows it to identify the child process associated with a particular channel process. When a request is received, it indicates to the child process that a request is available, and the child process is responsible for handling it to completion. The child process may work on a simple model like the single process blocking model, or it may require nonblocking operation.

The parent process creates the child process dynamically on receipt of an OpenHostServerLink, and destroys it on receipt of a CloseHostServerLink.

Advantages

The advantages of multiprocess associated operation are that:

Disadvantages

The disadvantages of multiprocess associated operation are that:

An example of a custom server of this type is given in Example code for a multiprocess custom server.

Example code for a multiprocess custom server

This custom server uses an unnamed pipe for the parent process to send requests to the child process. It has a single user function which does not block.

/*-----------------------------------------------------------------------------
 * m_ca.c : Example code for a multiprocess custom server
 *
 * The model for this custom server is that the parent process is responsible
 * for reading all requests from state tables. A child process is associated
 * with each CHP that has an open link to the custom server, and that child
 * process is responsible for processing requests from its associated CHP and
 * returning any response to the state table. The parent process forwards
 * the requests down an unnamed pipe to the child for processing.
 *
 * An unnamed pipe was chosen for simplicity in this example, but other
 * inter-process communication methods may be more appropriate for other
 * custom servers. In a more complex environment where more than one type of
 * asynchronous event may occur, you may need to modify the simple blocking
 * design used here to a nonblocking design.
 *
 *-----------------------------------------------------------------------------
 * Blueworx Voice Response Custom Server
 * 5765-B81 (C) Copyright by IBM Corp. 1997.  All rights reserved
*-----------------------------------------------------------------------------
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <sys/wait.h>
#include "CA_header.h"
#include "m_ca_hdr.h"
#define M_CA_MSG_SIZE sizeof (DT_MSG_INFO_ST)
static const char *program_name;
static int         total_CHPs = 0;    /* maximum possible number of
                   child processes */
static int        *m_child_fd;       /* one pipe for each child process */
static boolean_t   parent_process = TRUE;
/*-----------------------------------------------------------------------------
 * user_func : (CHILD process)
 * This is just a dummy function to perform some action based on a request
 * from the state table
 *-----------------------------------------------------------------------------
 */
long user_func (long input)
{
  return (++ input);
}
/*-----------------------------------------------------------------------------
 * m_child_main: (CHILD process)
 * This is the main loop for each child process. It waits on the pipe for a
 * request passed by the parent. An EOF on the pipe is handled as a request
 * to end the child process, probably because the associated state table has
 * ended. The child process runs from the OpenHostServerLink until the
 * CloseHostServerLink, and handles requests from a single state table only.
 *-----------------------------------------------------------------------------
 */
void m_child_main (int pipe_fd, short link_id)
{
  DT_MSG_INFO_ST DT_msg_info_st;
  ssize_t        size_read;
  long           func_input, func_output;
 printf ("%s: child process called for link_id %.3d\n", program_name, link_id);
  fflush (stdout);
  while (TRUE)
  {
   /*
    * wait for a request to arrive on the pipe from the parent
    *
    * Note the read is guaranteed to be atomic, being smaller than PIPE_BUF
    */
    size_read = read (pipe_fd, &DT_msg_info_st, M_CA_MSG_SIZE);
    if (size_read == M_CA_MSG_SIZE)
    {
      printf ("%s: received request for link_id %.3d, func_id %d\n",
               program_name, DT_msg_info_st.link_id, DT_msg_info_st.func_id);
      fflush (stdout);
    }
    else
    {
      if (size_read >= 0)
      {
        printf ("%s: child process for link_id %.3d closed by parent\n",
                 program_name, link_id);
      }
      else
      {
        if (errno == EINTR) /* interrupted by signal */
          continue;
        else
         printf ("%s: read() failed, errno %d (%s) - closing child process
         for link_id %.3d\n", program_name, errno, strerror (errno), link_id);
      }
      fflush (stdout);
      close (pipe_fd);
      CA_Terminate (0);
    }
    /*
    * This section is based on the equivalent system-generated main
    */
    switch (DT_msg_info_st.func_id)
    {
      case user_func_func_id :
        if (CA_Get_DT_Parameters (&DT_msg_info_st, &func_input) != 0)
        {
          CA_Send_DT_Error (&DT_msg_info_st);
          break;
        }
        func_output = user_func (func_input);
        if (CA_Put_DT_Parameters (&DT_msg_info_st, &func_output) != 0)
          CA_Send_DT_Error (&DT_msg_info_st);
        else
          CA_Send_DT_Msg (&DT_msg_info_st);
        break;
      default :
        CA_Send_DT_Error (&DT_msg_info_st);
        break;
    } /* end switch */
  } /* end while */
} /* m_child_main */
/*-----------------------------------------------------------------------------
 * m_initialize: (PARENT process)
 * The initialization function for the m_ca custom server.
 * Called once only.
 *-----------------------------------------------------------------------------
 */
void m_initialize (int argc, char **argv)
{
  DT_INFO_ST DT_info_st;
  program_name = basename (argv[0]);
  printf ("%s custom server\n5765-B81 (C) Copyright IBM Corp. 1997\n",
   program_name);  fflush (stdout);
  CA_Set_Options (MULTI_PROCESS_CA, NULL);
  if (CA_Set_CA_Version (CA_VERSION_ID) != 0)
  {
    printf ("%s: CA_Set_CA_Version returned %d (%s) - terminatingn",
             program_name, CA_errno, CA_ERRSTRING);
    exit (0);
  }
  if (CA_Init (APPL_NAME) != 0)
  {
    printf ("%s: CA_Init returned %d
(%s) - terminatingn",
             program_name, CA_errno, CA_ERRSTRING);
    exit (0);
  }
 /*
  * Get Blueworx Voice Response system information, including the
    number of CHPs */
  if (CA_Get_DT_Info (&DT_info_st) == -1)
  {
    printf ("%s: CA_Get_DT_Info returned %d (%s) - terminatingn",
             program_name, CA_errno, CA_ERRSTRING);
    CA_Terminate (0);
  }
  total_CHPs = DT_info_st.total_CHPs;
 /*
  * Get memory to hold the information about child processes, and clear it
  */
  m_child_fd = (int *)malloc (total_CHPs * sizeof (int));
  if (m_child_fd == (int *)NULL)
  {
    printf ("%s: malloc() failed, errno %d
(%s) - terminatingn",
             program_name, errno, strerror (errno));
    CA_Terminate (0);
  }
  memset (m_child_fd, 0, total_CHPs * sizeof (int));
 /*
  * Now set the signal handler for SIGPIPE to ignore.
  *
  * SIGPIPE is sent when a program attempts to write down a pipe which
  * no process has open for reading. This might occur if a child process
  * has terminated abnormally. The default action for SIGPIPE would be to end
  * the process writing on the pipe, in this case the custom server parent
  * process.
  */
  if (sigignore (SIGPIPE) == -1)
  {
    printf ("%s: sigignore() failed, errno %d (%s) - terminatingn",
             program_name, errno, strerror (errno));
    CA_Terminate (0);
  }
} /* m_initialize */
/*-----------------------------------------------------------------------------
 * m_close_link: (PARENT process)
 * This function is called on receipt of a CloseHostServerLink.
 * It signals the end of the state table to the child process by closing
 * the pipe, which will cause an EOF at the other end.
 *-----------------------------------------------------------------------------
 */
void m_close_link (short link_id)
{
  if (m_child_fd [link_id] > 0)
  {
    close (m_child_fd [link_id]);
    m_child_fd [link_id] = 0;
    /* Ensure child does not remain as a defunct process */
    pid = wait((void *) 0);
  }
} /* m_close_link */
/*-----------------------------------------------------------------------------
 * m_open_link: (PARENT process)
 * This function is called on receipt of an OpenHostServerLink.
 * It creates a pipe down which the parent will forward requests from the
 * state table to the associated child process, then forks to create a child
 * process.
 *-----------------------------------------------------------------------------
 */
void m_open_link (short link_id)
{
  pid_t pid;
  int   pipe_fd [2];
  int i /* used as a loop counter */
  printf ("%s: OpenHostServerLink request from link_id %.3d\n",
           program_name, link_id);
  fflush (stdout);
  if (m_child_fd [link_id] != 0)
  {
   /*
    * Received an Open without a preceding Close.
    * Perhaps the CHP terminated abnormally, so clean up now.
    */
    m_close_link (link_id);
  }
 /*
  * Open an unnamed pipe for communicating with the child process
  */
  if (pipe (pipe_fd) == -1)
  {
    printf ("%s: pipe() failed, errno %d
(%s) - terminatingn",
             program_name, errno, strerror (errno));
    CA_Terminate (0);
  }
 /*
  * now fork a new child process to handle this CHP
  */
  pid = fork ();
  if (pid == -1)
  {
    printf ("%s: fork() failed, errno %d (%s) - terminatingn",
             program_name, errno, strerror (errno));
    CA_Terminate (0);
  }
  if (pid == 0)
  {
   /*
    * This is the child process
    * The child process will only read from the pipe, so close the write fd
    * Then enter the main loop
    */
    parent_process = FALSE;
   /*
    * Have this child close the copies of write pipes to
    * all its siblings otherwise they will not terminate
    * until we do.
    */
    for (i=0;i<total_CHPs;i++)
    {
     if (m_child_fd [i]>0)
      {
         if (close (m_child_fd [i]) != 0)
          {
         printf ("%s:Request to close pipe to sibling
                  with link_id %.3d failed\n",
                  program_name, m_child_fd [i]);
         fflush (stdout);
          }
      }
    }
    m_child_fd = (int *)NULL;
    close (pipe_fd [1]);
    m_child_main (pipe_fd [0], link_id);
  }
  else
  {
   /*
    * This is the parent process
    * The parent process will only write to the pipe, so close the read fd
    * Save the pipe fd of the child in the CHP array.
    */
    close (pipe_fd [0]);
    m_child_fd [link_id] = pipe_fd [1];
  }
} /* m_open_link */
/*-----------------------------------------------------------------------------
 * m_terminate: (PARENT and CHILD processes)
 * The termination function for the m_ca custom server.
 * In the parent process, cleans up by ending all the child processes,
 * as if CHSL had been received. In the child process, there is nothing to do.
 *-----------------------------------------------------------------------------
 */
void m_terminate (void)
{
  short link_id;
  if (parent_process)
  {
    printf ("%s: terminating parent process\n", program_name);
    fflush (stdout);
   /*
    * first tell all the children to close
    */
    for (link_id = 0; link_id < total_CHPs; link_id++)
    {
      m_close_link (link_id);
    }
   /*
    * free allocated memory
    */
    free (m_child_fd);
  }
  else /* one of the child processes */
  {
    printf ("%s: terminating child process\n", program_name);
    fflush (stdout);
  }
} /* m_terminate */
/*-----------------------------------------------------------------------------
 * main: (PARENT process)
 * The main function for the m_ca custom server.
 * This is based on a system-generated main function, but the responsibility
 * for handling function requests from a state table is transferred to the
 * child process associated with that CHP.
 *
 * The OpenHostServerLink and CloseHostServerLink functions are handled in
 * the parent process. All other requests are sent down the pipe to the
 * associated child process, which is responsible for returning a response to
 * the state table.
 *-----------------------------------------------------------------------------
 */
main (int argc, char *argv[])
{
  DT_MSG_INFO_ST DT_msg_info_st;
  ssize_t        size_written;
  short          link_id;
  m_initialize (argc, argv);
  while (TRUE)
  {
    if (CA_Receive_DT_Msg (&DT_msg_info_st, CA_WAIT) == -1)
    {
      if (CA_errno == CA_SIGNAL_RECEIVED)     /* interrupted by signal */
        continue;
      else
      {
        printf ("%s: CA_Receive_DT_Msg returned %d (%s) - terminatingn",
                 program_name, CA_errno, CA_ERRSTRING);
        CA_Terminate (0);
        break;
      }
    }
    switch (DT_msg_info_st.func_id)
    {
      case m_open_link_func_id :
        if (CA_Get_DT_Parameters (&DT_msg_info_st, &link_id) != 0)
        {
          CA_Send_DT_Error (&DT_msg_info_st);
          break;
        }
        m_open_link (link_id);
        break;
      case m_close_link_func_id :
        if (CA_Get_DT_Parameters (&DT_msg_info_st, &link_id) != 0)
        {
          CA_Send_DT_Error (&DT_msg_info_st);
          break;
        }
        m_close_link (link_id);
        break;
      default :
       /*
        * All user functions other than Open/CloseHostServerLink come here,
        * and are sent down the pipe to the child process associated with
        * the CHP which sent the request.
        *
        * Note the write is guaranteed to be atomic, being smaller than
        PIPE_BUF
        */
        if (m_child_fd [DT_msg_info_st.link_id] == 0)
        {
          printf ("%s: no pipe to child process for link_id %.3d\n",
                   program_name, DT_msg_info_st.link_id);
          fflush (stdout);
          CA_Send_DT_Error (&DT_msg_info_st);
        }
        else
        {
          size_written = write (m_child_fd [DT_msg_info_st.link_id],
        &DT_msg_info_st, M_CA_MSG_SIZE);
          if (size_written != M_CA_MSG_SIZE)
          {
            if (size_written == -1)
            {
              printf ("%s: write() for link_id %.3d failed, errno %d (%s)\n",
        program_name, DT_msg_info_st.link_id, errno, strerror (errno));
            }
            else
            {
              printf ("%s: write() for link_id %.3d failed\n",
                       program_name, DT_msg_info_st.link_id);
              m_close_link (DT_msg_info_st.link_id);
            }
            fflush (stdout);
            CA_Send_DT_Error (&DT_msg_info_st);
          }
        }
        break;
     } /* end switch */
   } /* end while */
 } /* end of main() */