5.4 The ACE_Service_Config Class

Ru-Brd

Motivation

Before a service can execute, it must be configured into an application's address space. One way to configure services into a networked application is to statically link the functionality provided by its various classes and functions into separate OS processes, and then manually instantiate or initialize them at run time. We used this approach in the logging server examples in Chapters 3 and 4 and throughout C++NPv1, where the logging server program runs in a process that handles log records from client applications. Although our use of the ACE Reactor framework in earlier chapters improved the networked logging server's modularity and portability, the following drawbacks arose from statically configuring the Reactor_Logging_Server class with its main() program:

  • Service configuration decisions are made prematurely in the development cycle, which is undesirable if developers don't know the best way to collocate or distribute services in advance. Moreover, the "best" configuration may change as the computing context changes. For example, an application may write log records to a local file when it's running on a disconnected laptop computer. When the laptop is connected to a LAN, however, it may forward log records to a centralized logging server. Forcing networked applications to commit prematurely to a particular service configuration impedes their flexibility and can reduce their performance and functionality. It can also force costly redesign and reimplementation later in a project's life cycle.

  • Modifying a service may affect other services adversely if the implementation of a service is coupled tightly with its initial configuration. To enhance reuse, for example, a logging server may initially reside in the same program as other services, such as a name service. If the other services change, however, for example if the name service lookup algorithm changes, all existing code in the server would require modification, recompilation, and static relinking. Moreover, terminating a running process to change some of its service code would also terminate the collocated logging service. This disruption in service may not be acceptable for highly available systems, such as telecommunication switches or customer care call centers [SS94].

  • System performance may scale poorly since associating a separate process with each service ties up OS resources, such as I/O handles, virtual memory, and process table slots. This design is particularly wasteful if services are often idle. Moreover, processes can be inefficient for many short-lived communication tasks , such as asking a time service for the current time or resolving a host address request via the Domain Name Service (DNS).

To address the drawbacks of purely static configurations, the ACE Service Configurator framework defines the ACE_Service_Config class.

Class Capabilities

ACE_Service_Config implements the Facade pattern [GoF] to integrate other classes in the ACE Service Configurator framework and coordinate the activities necessary to manage the services in an application. This class provides the following capabilities:

  • It interprets a scripting language that allows applications or administrators to provide the ACE Service Configurator framework with commands, called directives , to locate and initialize a service's implementation at run time, as well as to suspend, resume, reinitialize , and/or shut down a component after it's been initialized . Directives can be specified to ACE_Service_Config in either of two ways:

    1. Using configuration files (named svc.conf by default) that contain one or more directives

    2. Programmatically, by passing individual directives as strings

  • It supports the management of services located in the application (the so-called static services ), as well as those that must be linked dynamically (the so-called dynamic services ) from separate shared libraries (DLLs).

  • It allows service reconfiguration at run time using the following mechanisms:

    1. On POSIX platforms, ACE_Service_Config can be integrated with the ACE Reactor framework to reprocess its configuration files upon receipt of a SIGHUP signal or any other user -specified signal, such as SIGINT .

    2. By passing the "reconfigure" command via ACE_Service_Manager , as described in Sidebar 31 (page 132).

    3. An application can request its ACE_Service_Config to reprocess its configuration files at any time. For example, a Windows directory change notification event can be used to help a program learn when its configuration file changes. This change event can then trigger reprocessing of the configuration.

    4. An application can also specify individual directives for its ACE_Service_Config to process at any time via the process_directive() method.

Figure 5.6 The ACE_Service_Config Class
  ACE_Service_Config  + ACE_Service_Config (ignore_static_svcs : int = 1,                        repository_size : size_t = MAX_SERVICES,                        signum : int = SIGHUP)  +  open (argc : int, argv : ACE_TCHAR *[],   logger_key : const ACE_TCHAR * = ACE_DEFAULT_LOGGER_KEY,   ignore_static_svcs : int = 1,   ignore_default_svc_conf : int = 0,   ignore_debug_flag : int = 0) : int  +  close () : int  +  process_directives () : int  +  process_directive (directive : ACE_TCHAR[]) : int  +  reconfigure () : int  +  suspend (name : const ACE_TCHAR []) : int  +  resume (name : const ACE_TCHAR []) : int  

The interface for ACE_Service_Config is shown in Figure 5.6. This class has a rich interface since it exports all the features in the ACE Service Configurator framework. We therefore group the description of its methods into the three categories described below.

1. Service Configurator life cycle management methods. The following methods initialize and shut down the ACE_Service_Config :

ACE Class

Description

ACE_Service_Config() open()

These methods create and initialize the ACE_Service_Config .

close()

This method shuts down and finalizes all the configured services and deletes the resources allocated when the ACE_Service_Config was initialized.

There's only one instance of ACE_Service_Config 's state in a process. This class is a variant of the Monostate pattern [CB97], which ensures a unique state for its instances by declaring all data members to be static. Moreover, the ACE_Service_Config methods are also declared as static . The ACE_Service_Config constructor, however, is the only way to set the maximum size of the ACE_Service_Repository . It's also the only programmatic way to change the signal number that can be registered with the reactor to trigger reconfigurations. Instantiating an instance of ACE_Service_Config therefore simply sets these parameters for the underlying monostate object and does not create a separate configuration object. Thus, the ACE_Service_Config destructor is a no-op.

The open() method is the common way of initializing the ACE_Service_Config . It parses arguments passed in the argc and argv parameters, skipping the first parameter ( argv[0] ) since that's the name of the program. The options recognized by ACE_Service_Config are outlined in the following table:

Option

Description

'-b'

Turn the application process into a daemon (see Sidebar 5 on page 32).

'-d'

Display diagnostic information as directives are processed .

'-f'

Supply a file containing directives other than the default svc.conf file. This argument can be repeated to supply multiple configuration files.

'-n'

Don't process static directives, which eliminates the need to initialize the ACE_Service_Repository statically.

'-s'

Designate the signal to be used to cause the ACE_Service_Config to reprocess its configuration file. By default, SIGHUP is used.

'-S'

Supply a directive to the ACE_Service_Config directly. This argument can be repeated to process multiple directives.

'-y'

Process static directives, which requires the static initialization of the ACE_Service_Repository .

2. Service configuration methods. After parsing all its argc / argv arguments, the ACE_Service_Config::open() method calls one or both of the following methods to configure the application:

Method

Description

process_directives()

Process a sequence of directives that are stored in the designated script file(s). This method allows multiple directives to be stored persistently and processed iteratively in batch mode. Each service configuration directive in each configuration file is executed in the order they are specified.

process_directive()

Process a single directive passed as a string.This method allows directives to be created dynamically and processed interactively, such as via a GUI or a network connection.

The following table summarizes the service configuration directives that can be processed by these two ACE_Service_Config methods:

Directive

Description

dynamic

Dynamically link a service and initialize it by calling its init() hook method.

static

Call the init() hook method to initialize a service that was linked statically.

remove

Remove a service completely, that is, call its fini() hook method and unlink it from the application process when it's no longer used.

suspend

Call a service's suspend() hook method to pause it without removing it.

resume

Call a service's resume() hook method to continue processing a service that was suspended earlier.

stream

Initialize an ordered list of hierarchically related modules.

We describe the syntax and semantics for the tokens in each of these directives below.

  • Dynamically link and initialize a service: dynamic svc-name svc-type DLL-name:factory_func() [ "argc/argv options" ]

    The dynamic directive instructs the ACE Service Configurator framework to dynamically link and initialize a service object. The svc-name is the name assigned to the service. The svc-type designates the type of the service, which can be a Service_Object * , Module * , or Stream * . DLL-name is the name of the dynamic link library that contains the factory_func() symbol. This symbol is the entry point for an extern "C" function that the ACE_Service_Config interpreter invokes to create an instance of a service. If the svc-type is Service_Object * then factory_func() must return a pointer to an object derived from ACE_Service_Object . The factory_func() symbol should have a leading underscore character since linkers add that character to externally visible symbols.

    DLL-name can be either a full pathname or a filename without a suffix. If it's a full pathname, the ACE_DLL::open() method described in Sidebar 33 is used to dynamically link the designated file into the application process. If it's a filename, however, ACE_DLL::open() uses ACE::ldfind() (also described in Sidebar 33) to locate the DLL and dynamically link it into the address space of the process via ACE_DLL::open() . The dynamic directive can be used portably across operating systems since ACE encapsulates these platform details.

    The argc/argv options are an optional list of parameters that can be supplied to initialize a service object via its init() hook method. The ACE Service Configurator framework uses the ACE_ARGV class described in Sidebar 35 (page 148) to separate the string into arguments and substitute the values of environment variables that are included in the string.

  • Initialize a statically linked service: static svc-name [ "argc/argv options" ]

    Although ACE_Service_Config is commonly used to configure services dynamically, it can also be used to configure services statically via the static directive. The svc-name and optional argc/argv options are the same as those in the dynamic directive. The syntax is simpler, however, since the service object must already be linked into the executable program image statically. Thus, there's no need to either locate and link a DLL or call a factory function to create a service object. Static configuration trades flexibility for increased security, which may be useful for certain types of servers that must contain only trusted, statically linked services. Recall from Sidebar 31 (page 132) that static service loading is disabled by default. Static service loading must therefore be enabled explicitly to use them, or else the static directive will have no effect.

    Sidebar 33: The ACE_DLL Class

    Applications that link and unlink DLLs explicitly can encounter the following problems:

    - A nonuniform programming interface that's even less portable than the Socket API described in Chapter 3 of C++NPv1

    - Unsafe types that invite errors and misuse because the native OS DLL APIs return weakly typed handles that are passed to DLL functions, such as those used to locate symbols and unlink the DLL

    - Potential resource leaks, since it's possible to forget to release DLL handles

    To address these problems, ACE defines the ACE_DLL wrapper facade class to encapsulate explicit linking/ unlinking functionality. This class eliminates the need for applications to use error-prone , weakly typed handles and also ensures that resources are released properly by its destructor. In addition, it uses the ACE::ldfind() method to locate DLLs via the following algorithms:

    • DLL filename expansion ACE::ldfind() determines the name of the DLL by adding the appropriate prefix and suffix. For example, it adds the lib prefix and .so suffix for Solaris and the .dll suffix for Windows.

    • DLL search path ACE::ldfind() will also search for the designated DLL using the platform's DLL search path environment variable. For example, it searches for DLLs using LD _ LIBRARY _ PATH on many UNIX systems and PATH on Windows.

    The key methods in the ACE_DLL class are outlined in the following table.

    ACE Class

    Description

    ACE_DLL() open()

    Opens and dynamically links a designated DLL

    ACE_DLL() close()

    Closes and optionally unlinks the DLL

    symbol()

    Returns a pointer to a function or object in the DLL

    error()

    Returns a string explaining which failure occurred

    The interface of ACE_DLL is shown in the figure below.

      ACE_DLL  - handle_ : ACE_SHLIB_HANDLE  + open (name : const ACE_TCHAR *,          mode : int = ACE_DEFAULT_SHLIB_MODE,          close_on_destruct : int = 1) : int  + close () : int  + symbol (name : const ACE_TCHAR *) : void *  + error (void) : ACE_TCHAR * 
  • Remove a service completely: remove svc-name

    The remove directive causes the ACE_Service_Config interpreter to query the ACE_Service_Repository for the named service. If this service is located, the interpreter invokes its fini() hook method, which performs the activities needed to clean up resources when the service shuts down. If a service destruction function pointer is associated with the service object, it's called to destroy the service object itself (the ACE _ FACTORY _ DEFINE macro defines this function automatically). Finally, if the service was linked dynamically from a DLL, it's unlinked via the ACE_DLL::close() method. Since a DLL can be linked multiple times in a process, ACE_DLL::close() ensures that the DLL is only unlinked when it's no longer in use.

  • Suspend a service without removing it: suspend svc-name

    The suspend directive causes the ACE_Service_Config interpreter to query the ACE_Service_Repository for the designated svc-name service. If this service is located, its suspend() hook method is invoked. A service can override this method to implement the appropriate actions needed to suspend its processing.

  • Resume a previously suspended service: resume svc-name

    The resume directive causes the ACE_Service_Config interpreter to query the ACE_Service_Repository for the designated svc-name service. If this service is located, its resume() hook method is invoked. A service can override this method to implement the appropriate actions needed to resume its processing, which typically reverse the effects of the suspend() method.

  • Initialize an ordered list of hierarchically related modules: stream svc-name '{' module-list '}'

    The stream directive causes the ACE_Service_Config interpreter to initialize an ordered list of hierarchically related modules. Each module consists of a pair of services that are interconnected and communicate by passing ACE_Message_Block objects. The implementation of the stream directive uses the ACE Streams framework described in Chapter 9.

The complete Backus/Naur Format (BNF) syntax for svc.conf files parsed by the ACE_Service_Config is shown in Figure 5.7. Sidebar 34 (page 146) describes how to specify svc.conf files using the optional XML syntax.

3. Utility methods. ACE_Service_Config defines the following utility methods:

Method

Description

reconfigure()

Reprocess the current configuration file(s).

suspend()

Suspend a service, identified by name.

resume()

Resume a suspended service, identified by name.

Figure 5.7 BNF for the ACE_Service_Config Scripting Language
 <svc-conf-entries> ::= <svc-conf-entries> <svc-conf-entry>  NULL  <svc-conf-entry>   ::= <dynamic>  <static>  <suspend>                           <resume>  <remove>  <stream>  <dynamic> ::= dynamic <svc-location> <parameters-opt>  <static> ::= static <svc-name> <parameters-opt>  <suspend> ::= suspend <svc-name>  <resume> ::= resume <svc-name>  <remove> ::= remove <svc-name>  <stream> ::= stream <streamdef> '{' <module-list> '}'  <streamdef> ::= <svc-name>  dynamic  static  <module-list> ::= <module-list> <module>  NULL  <module> ::= <dynamic>  <static>  <suspend>                <resume>  <remove>  <svc-location> ::= <svc-name> <svc-type> <svc-factory> <status>  <svc-type> ::= Service_Object '*'  Module '*'  Stream '*'  NULL  <svc-factory> ::= PATHNAME ':' FUNCTION '(' ')'  <svc-name> ::= STRING  <status> ::= active  inactive  NULL  <parameters-opt> ::= '"' STRING '"'  NULL 

The reconfigure() method can be used to force the ACE_Service_Config interpreter to reprocess the service configuration files. This capability is useful if your application monitors the service configuration files for changes. When changes are noticed, they can be processed using the reconfigure() method. This method is the recommended way to make run-time changes on Windows since there's no equivalent to the common UNIX practice of sending a signal, such as SIGHUP , to a process.

The suspend() and resume() methods enable the suspension and resumption of a service if its name is known. It's a shortcut to the methods defined in the ACE_Service_Repository singleton.

Example

This example shows how to apply the ACE Service Configurator framework to create a server whose initial configuration behaves as follows :

  • It statically configures an instance of Service_Reporter .

  • It dynamically links and configures the Reactor_Logging_Server_Adapter template from the Example portion of Section 5.2 into the server's address space.

We then show how to dynamically reconfigure the server to support a different implementation of a reactive logging service.

Sidebar 34: Using XML to Configure Services

The ACE_Service_Config class can be configured to interpret an XML-based scripting language. The Document Type Definition (DTD) for this language is shown below:

 <!ELEMENT ACE_Svc_Conf (dynamicstaticsuspendresume                          removestreamstreamdef)*>  <!ELEMENT streamdef ((dynamicstatic),module)>  <!ATTLIST streamdef id IDREF #REQUIRED>  <!ELEMENT module (dynamicstaticsuspendresumeremove)+>  <!ELEMENT stream (module)>  <!ATTLIST stream id IDREF #REQUIRED>  <!ELEMENT dynamic (initializer)>  <!ATTLIST dynamic id ID #REQUIRED                    status (activeinactive) "active"                    type (moduleservice_objectstream)                    #REQUIRED>  <!ELEMENT initializer EMPTY>  <!ATTLIST initializer init CDATA #REQUIRED                        path CDATA #IMPLIED                        params CDATA #IMPLIED>  <!ELEMENT static EMPTY>  <!ATTLIST static id ID #REQUIRED                   params CDATA #IMPLIED>  <!ELEMENT suspend EMPTY>  <!ATTLIST suspend id IDREF #REQUIRED>  <!ELEMENT resume EMPTY>  <!ATTLIST resume id IDREF #REQUIRED>  <!ELEMENT remove EMPTY>  <!ATTLIST remove id IDREF #REQUIRED> 

The syntax of this XML-based configuration language is different from the one in Figure 5.5, but its semantics are the same. Although it's more verbose to compose, the ACE XML-based configuration file format is more flexible. For example, users can plug in customized XML event handlers to extend the behavior of the ACE Service Configurator framework without modifying the underlying ACE implementation.

The XML configuration file format is relatively new (it was introduced in ACE 5.3). It's therefore not yet the default used by the ACE Service Configurator framework. Users can choose the XML-based Service Configurator by compiling ACE with the ACE _ HAS _ XML _ SVC _ CONF macro enabled. ACE provides the svcconf-convert.pl perl script to translate original format files into the new XML format. The script is located in the $ACE_ROOT/bin/ directory.

Initial server configuration. We start by writing the following generic main() program in Configurable_Logging_Server.cpp . This program configures the Service_Reporter and Reactor_Logging_Server_Adapter services into an application process and then runs the reactor's event loop.

 1 #include "ace/OS.h"   2 #include "ace/Service_Config.h"   3 #include "ace/Reactor.h"   4   5 int ACE_TMAIN (int argc, ACE_TCHAR *argv[]) {   6   ACE_STATIC_SVC_REGISTER (Reporter);   7   8   ACE_Service_Config::open   9     (argc, argv, ACE_DEFAULT_LOGGER_KEY, 0);  10  11   ACE_Reactor::instance ()->run_reactor_event_loop ();  12   return 0;  13 } 

Lines 13 There are no service-specific header files (or code) in the main() program. It's therefore completely generic, and can be reused for many programs that are configured using the ACE Service Configurator and Reactor frameworks.

Line 5 Replace the main() entry point name with the ACE _ TMAIN macro. This macro uses the alternate wmain() entry point on Windows when Unicode is enabled, and the usual main() in all other situations.

Line 6 Register the static Reporter service with the ACE Service Configurator framework. Although the framework now knows of the service, it is not activated unless a service configuration directive causes it to be.

Lines 89 Call ACE_Service_Config::open() to configure the application. All decisions about which service(s) to load, and all of the service parameters, are located in the service configuration file, which is external to the binary application program. We then run the reactor's event loop to handle I/O events from clients .

Since we know that our program will activate the static Service_Reporter service, we added the fourth argument (and by necessity, the third) to ACE_Service_Config::open() to explicitly enable static service loading. If we instead decided to leave this decision to the user or administrator, that argument would not be supplied, and the user would choose to enable or disable static services by supplying the -y option on the command line.

When ACE_Service_Config::open() is called, it uses the ACE_Service_Config::process_directives() method to interpret the svc.conf file below:

 1 static Service_Reporter "-p $SERVICE_REPORTER_PORT"  2  3 dynamic Server_Logging_Daemon Service_Object *  4 SLD:_make_Server_Logging_Daemon()  5   "$SERVER_LOGGING_DAEMON_PORT" 

Line 1 The Service_Reporter code, registration information, and factory function were all statically linked into the executable program. This directive therefore simply causes the Service Configurator framework to activate the service by calling Service_Reporter::init() (page 132). The argc / argv arguments passed to init() are the string "-p" and an expansion of the SERVICE_REPORTER_PORT environment variable. The Service Configurator framework expands this environment variable automatically using the ACE_ARGV class described in Sidebar 35. ACE_ARGV recognizes SERVICE_REPORTER_PORT as an environment variable by its leading $ character and substitutes its associated value. The ACE _ STATIC _ SVC _ REQUIRE macro used in Service_Reporter.cpp (page 135) ensures the Service_Reporter is registered with ACE_Service_Repository before the ACE_Service_Config::open() method is called.

Sidebar 35: The ACE_ARGV Class

The ACE_ARGV class is a useful utility class that can

  1. Transform a string into an argc / argv -style vector of strings

  2. Incrementally assemble a set of strings into an argc / argv vector

  3. Transform an argc / argv -style vector into a string

During the transformation, the class can substitute environment variable values for each $ -delimited environment variable name encountered . ACE_ARGV provides an easy and efficient mechanism to create arbitrary command-line arguments. Consider its use whenever command-line processing is required, especially when environment variable substitution is desirable. ACE uses ACE_ARGV extensively, particularly in its Service Configurator framework.

Lines 35 This code configures the server logging daemon via the following steps:

  1. Dynamically link the SLD DLL into the address space of the process.

  2. Use the ACE_DLL class described in Sidebar 33 (page 143) to extract the _make_Server_Logging_Daemon() factory function from the SLD DLL symbol table.

  3. The factory function is called to allocate a Server_Logging_Daemon object.

  4. The Service Configurator framework calls the service object's Server_Logging_Daemon::init() hook method, passing as its argc / argv arguments an expansion of the SERVER_LOGGING_DAEMON_PORT environment variable that designates the port number where the server logging daemon listens for client connection requests .

  5. If init() succeeds, the Server_Logging_Daemon pointer is stored in the ACE_Service_Repository under the name "Server_Logging_Daemon" .

Sidebar 36 illustrates an XML version of the svc.conf file shown above.

Sidebar 36: An XML svc.conf File Example

The XML representation of the svc.conf file shown on page 147 is shown below:

 1 <ACE_Svc_Conf>   2   <static id='Service_Reporter'   3           params='-p $SERVICE_REPORTER_PORT'/>   4   5   <dynamic id='Server_Logging_Daemon'   6            type='service_object'>   7     <initializer path='SLD'   8                  init='_make_Server_Logging_Daemon'   9                  params='$SERVER_LOGGING_DAEMON_PORT'/>  10   </dynamic>  11 </ACE_Svc_Conf> 

The XML svc.conf file is more verbose than the original format since it specifies field names explicitly. However, the XML format allows svc.conf files to express expanded capabilities, since new sections and fields can be added without affecting existing syntax. There's also no threat to backwards compatibility, as might occur if fields were added to the original format or the field order changed.

The SLD_ DLL is generated from the following SLD.cpp file:

 #include "Reactor_Logging_Server_Adapter.h"  #include "Logging_Acceptor.h"  #include "SLD_export.h"  typedef Reactor_Logging_Server_Adapter<Logging_Acceptor>          Server_Logging_Daemon;  ACE_FACTORY_DEFINE (SLD, Server_Logging_Daemon) 

The SLD.cpp file contains the Server_Logging_Daemon type definition that instantiates the Reactor_Logging_Server_Adapter template with the Logging_Acceptor class (page 54). The ACE _ FACTORY _ DEFINE macro generates the _make_Server_Logging_Daemon() factory function in the DLL containing the service code. If code outside the service DLL needs to refer to the factory function, it can use the ACE _ FACTORY _ DECLARE macro to declare the _make_Server_Logging_Daemon() factory function with the proper import declaration.

ACE's import/export helper macros are described in Sidebar 37 (page 150). These macros help to ensure that a DLL's externally visible symbols are exported properly from the DLL on all supported platforms, as well as allowing the DLL's users to import them properly. Applying the export macros within the ACE service macros allow the ACE Service Configurator framework to look up the factory function's entry point symbol when activating a service.

Sidebar 37: The ACE DLL Import/Export Macros

Windows has specific rules for explicitly importing and exporting symbols in DLLs. Developers with a UNIX background may not have encountered these rules in the past, but they are important for managing symbol usage in DLLs on Windows. ACE makes it easy to conform to these rules by supplying a script that generates the necessary import/export declarations and a set of guidelines for using them successfully. To ease porting, the following procedure can be used on all platforms that ACE runs on:

  1. Select a concise mnemonic for each DLL to be built.

  2. Run the $ACE_ROOT/bin/generate_export_file.pl Perl script, specifying the DLL's mnemonic on the command line. The script will generate a platform-independent header file and write it to the standard output. Redirect the output to a file named <mnemonic>_export.h

  3. #include the generated file in each DLL source file that declares a globally visible class or symbol.

  4. To use in a class declaration, insert the keyword <mnemonic>_Export between class and the class name.

  5. When compiling the source code for the DLL, define the macro <mnemonic>_BUILD_DLL .

Following this procedure results in the following behavior on Windows:

  • Symbols decorated using the above guidelines will be declared using __declspec(dllexport) when built in their DLL

  • When referenced from components outside the DLL, the symbols will be declared __declspec( dllimport ) .

If you choose a separate mnemonic for each DLL and use them consistently, it will be straightforward to build and use DLLs across all OS platforms.

The UML sequence diagram in Figure 5.8 illustrates the steps involved in configuring the server logging daemon based on the svc.conf file shown above. At program start time, the object generated by the ACE _ STATIC _ SVC _ REQUIRE macro registers the Service_Reporter information created using the ACE _ STATIC _ SVC _ DEFINE macro into ACE_Service_Config . When the ACE_Service_Config::open() method is called it uses the specified factory function to instantiate a Service_Reporter object, but doesn't activate it. The open() method then calls process_directives() , which interprets the directives in the svc.conf file. The first directive activates the static Service_Reporter service. The second directive triggers the following actions:

Figure 5.8. UML Sequence Diagram for Configuring the Logging Server

  1. The SLD DLL is linked dynamically.

  2. The _make_Server_Logging_Daemon factory function is called to create an instance of Reactor_Logging_Server_Adapter .

  3. The new service object's init() method is called to activate the service.

When all configuration activities are done, the main() program calls ACE_Reactor::run_reactor_event_loop() . At that point, the services are running, just like the objects that were configured statically in previous examples.

Reconfiguring the server. The ACE Service Configurator framework can reconfigure a server at run time in response to external events, such as signals or commands. At this point, the framework rereads its svc.conf file(s) and performs the designated directives, such as inserting or removing service objects into or from a server, and suspending or resuming existing service objects. We now illustrate how to use these features to reconfigure our server logging daemon dynamically.

The initial configuration of the logging server has the following limitations:

  • It uses the Logging_Acceptor implementation from Section 3.3 (page 54), which doesn't time out logging handlers that remain idle for long periods of time.

  • There is no way to shut down the run_reactor_event_loop() method called on the ACE_Reactor singleton.

We can add these capabilities without affecting existing code or the Service_Reporter service in the process by defining a new svc.conf file and instructing the server to reconfigure itself by sending it a signal, such as SIGHUP or SIGINT .

 1 remove Server_Logging_Daemon  2  3 dynamic Server_Logging_Daemon Service_Object *  4 SLDex:_make_Server_Logging_Daemon_Ex()  5   "$SERVER_LOGGING_DAEMON_PORT"  6  7 dynamic Server_Shutdown Service_Object *  8 SLDex:_make_Server_Shutdown() 

This svc.conf file assumes the server process is currently running with the Server_Logging_Daemon already configured. The ACE Service Configurator framework provides configuration mechanisms and assumes the policies that determine when and what to reconfigure are handled by an administrator or another application.

Line 1 Remove the existing server logging daemon from the ACE service repository and unlink it from the application's address space.

Lines 35 Dynamically configure a different instantiation of the Reactor_Logging_Server_Adapter template into the address space of the server logging daemon. In particular, the _make_Server_Logging_Daemon_Ex() factory function is created by the ACE _ FACTORY _ DEFINE macro shown below in the SLDex.cpp file, which is used to generate the SLDex DLL.

 typedef Reactor_Logging_Server_Adapter<Logging_Acceptor_Ex>          Server_Logging_Daemon_Ex;  ACE_FACTORY_DEFINE (SLDEX, Server_Logging_Daemon_Ex) 

This macro instantiates the Reactor_Logging_Server_Adapter template with the Logging_Acceptor_Ex (page 67).

Lines 78 Dynamically configure a Server_Shutdown service object that uses the controller() function (page 98) and Quit_Handler class (page 98) to wait for an administrator to shut down the server via a command on its standard input. The Server_Shutdown class shown below inherits from ACE_Service_Object so that we can manage its life cycle via the ACE Service Configurator framework.

 class Server_Shutdown : public ACE_Service_Object {  public:  Server_Shutdown::init() spawns a thread to run the controller() function:    virtual int init (int, ACE_TCHAR *[]) {      reactor_ = ACE_Reactor::instance ();      return ACE_Thread_Manager::instance ()->spawn               (controller, reactor_, THR_DETACHED);    } 

We pass the THR _ DETACHED flag to spawn so that the controller's thread identifier and other resources are reclaimed by the OS automatically after the thread terminates.

The Server_Shutdown::fini() method notifies the reactor to shut down:

 virtual int fini () {      Quit_Handler *quit_handler = 0;      ACE_NEW_RETURN (quit_handler,                      Quit_Handler (reactor_), -1);      return reactor_->notify (quit_handler);    }    // ... Other method omitted ...  private:    ACE_Reactor *reactor_;  }; 

We use ACE _ FACTORY _ DEFINE to generate the _make_Server_Shutdown() factory function needed by the ACE Service Configurator framework.

 ACE_FACTORY_DEFINE (SLDEX, Server_Shutdown) 

The UML sequence diagram in Figure 5.9 illustrates the steps involved in reconfiguring the server logging daemon based on the svc.conf file shown above.

Figure 5.9. UML Sequence Diagram for Reconfiguring the Logging Server

The dynamic reconfiguration mechanism in the ACE Service Configurator framework enables developers to modify server functionality or fine-tune performance without extensive redevelopment and reinstallation effort. For example, debugging a faulty implementation of the logging service simply involves the dynamic reconfiguration of a functionally equivalent service that contains additional instrumentation to help identify the erroneous behavior. This reconfiguration process may be performed without modifying, recompiling, relinking, or restarting the currently executing server logging daemon. In particular, this reconfiguration doesn't affect the Service_Reporter that was configured statically.

Ru-Brd


C++ Network Programming
C++ Network Programming, Volume I: Mastering Complexity with ACE and Patterns
ISBN: 0201604647
EAN: 2147483647
Year: 2002
Pages: 65

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net