Reactor Siemens
Reactor Siemens
CLIENT
DATABASE
PRINTER
1 Intent
CONNECTION
REQUEST
LOGGING
SERVER
SOCKET HANDLES
NETWORK
CONSOLE
CLIENT
LOGGING
RECORDS
LOGGING
RECORDS
CLIENT
SERVER
2 Also Known As
Dispatcher, Notifier
3 Example
To illustrate the Reactor pattern, consider the event-driven
server for a distributed logging service shown in Figure 1.
Client applications use the logging service to record information about their status in a distributed environment. This status information commonly includes error notifications, debugging traces, and performance reports. Logging records
are sent to a central logging server, which can write the
records to various output devices, such as a console, a printer,
a file, or a network management database.
The logging server shown in Figure 1 handles logging
records and connection requests sent by clients. Logging
records and connection requests can arrive concurrently on
multiple handles. A handle identifies network communication resources managed within an OS.
The logging server communicates with clients using a
connection-oriented protocol, such as TCP [1]. Clients that
want to log data must first send a connection request to the
LOGGING SERVER
SERVER
THREAD1
1: accept ()
3: create()
Logging
Acceptor
THREAD2
THREAD3
5: recv()
6: write()
Logging
Handler
Logging
Handler
2: connect()
CLIENT
A
6 Solution
4: send()
NETWORK
CLIENT
B
4 Context
7 Structure
5 Problem
Server applications in a distributed system must handle multiple clients that send them service requests. Before invoking a specific service, however, the server application must
demultiplex and dispatch each incoming request to its corresponding service provider. Developing an effective server
mechanisms for demultiplexing and dispatching client requests requires the resolution of the following forces:
Availability: The server must be available to handle incoming requests even if it is waiting for other requests to arrive. In particular, a server must not block indefinitely handling any single source of events at the exclusion of other
event sources since this may significantly delay the responseness to other clients.
8 Dynamics
Handles can have operations invoked on them synchronously without blocking the application process.
Initiation Dispatcher
Event Handler
After all Event Handlers are registered, an application calls handle events to start the Initiation
Dispatchers event loop.
At this point, the
Initiation Dispatcher combines the Handle
from each registered Event Handler and uses the
Synchronous Event Demultiplexer to wait
for events to occur on these Handles. For instance, the TCP protocol layer uses the select synchronous event demultiplexing operation to wait for
client logging record events to arrive on connected
socket Handles.
Implements the hook method, as well as the methods to process these events in an application-specific
manner. Applications register Concrete Event
Handlers with the Initiation Dispatcher to
process certain types of events. When these events arrive, the Initiation Dispatcher calls back the
hook method of the appropriate Concrete Event
Handler.
Initiation Dispatcher
handle_events()
register_handler(h)
remove_handler(h)
uses
Handle
select (handlers);
foreach h in handlers loop
h.handle_event(type)
end loop
handlers
1
N
owns
Event Handler
handle_event(type)
get_handle()
notifies
Synchronous Event
Demultiplexer
select()
Concrete
Event
Handler
The following interaction diagram illustrates the collaboration between application code and participants in the Reactor pattern:
INITIALIZATION
MODE
main
program
INITIALIZE
REGISTER HANDLER
EVENT HANDLING
MODE
EXTRACT HANDLE
RUN EVENT LOOP
callback :
Concrete
Event_Handler
Initiation
Dispatcher Handles
Initiation_Dispatcher()
register_handler(callback, event_type)
get_handle()
handle_events()
select()
handle_event(event_type)
NETWORK
CLIENT
A
CLIENT
B
LOGGING SERVER
SERVER
1: register
handler()
NETWORK
CLIENT
1: send()
LOGGING SERVER
Logging
Handler
2: handle
for A
event()
3: recv()
4: write()
5: return
Logging
Initiation
Handler
Dispatcher
for B
4: connect()
Logging
Acceptor 6: accept()
7: create()
5: handle
event()
Initiation
Dispatcher
2: handle_events()
3: select()
Logging
Handler
8: register
handler()
9 Implementation
3. The Initiation Dispatcher invokes the synchronous event demultiplexing select (3) operation
to wait for connection requests or logging data to arrive;
or more events occur. This is commonly implemented using an OS event demultiplexing system call like select.
The select call indicates which Handle(s) are ready to
perform I/O operations without blocking the OS process in
which the application-specific service handlers reside. In
general, the Synchronous Event Demultiplexer
is based upon existing OS mechanisms, rather than developed by implementers of the Reactor pattern.
{
public:
// Register an Event_Handler of a particular
// Event_Type (e.g., READ_EVENT, ACCEPT_EVENT,
// etc.).
int register_handler (Event_Handler *eh,
Event_Type et);
// Remove an Event_Handler of a particular
// Event_Type.
int remove_handler (Event_Handler *eh,
Event_Type et);
};
class Event_Handler
{
public:
// Hook methods that are called back by
// the Initiation_Dispatcher to handle
// particular types of events.
virtual int handle_accept (void) = 0;
virtual int handle_input (void) = 0;
virtual int handle_output (void) = 0;
virtual int handle_timeout (void) = 0;
virtual int handle_close (void) = 0;
class Event_Handler
// = TITLE
//
Abstract base class that serves as the
//
target of the Initiation_Dispatcher.
{
public:
// Hook method that is called back by the
// Initiation_Dispatcher to handle events.
virtual int handle_event (Event_Type et) = 0;
// Hook method that returns the underlying
// I/O Handle.
virtual Handle get_handle (void) const = 0;
};
particular events. The developers must determine what processing to perform when the corresponding hook method is
invoked by the initiation dispatcher.
The following code implements the Concrete Event
Handlers for the logging server described in Section 3.
These handlers provide passive connection establishment
(Logging Acceptor) and data reception (Logging
Handler).
used to transfer data reliably between the client and the logging server.
The SOCK Acceptor and SOCK Stream classes used
to implement the logging server are part of the C++ socket
wrapper library provided by ACE [9]. These socket wrappers
encapsulate the SOCK Stream semantics of the socket interface within a portable and type-secure object-oriented interface. In the Internet domain, SOCK Stream sockets are
implemented using TCP.
The constructor for the Logging Acceptor registers
itself with the Initiation Dispatcher Singleton [5]
for ACCEPT events, as follows:
Logging_Acceptor::Logging_Acceptor
(const INET_Addr &addr)
: acceptor_ (addr)
{
// Register acceptor with the Initiation
// Dispatcher, which "double dispatches"
// the Logging_Acceptor::get_handle() method
// to obtain the HANDLE.
Initiation_Dispatcher::instance ()->
register_handler (this, ACCEPT_EVENT);
}
SOCK_Stream new_connection;
// Accept the connection.
acceptor_.accept (new_connection);
private:
// Socket factory that accepts client
// connections.
SOCK_Acceptor acceptor_;
};
The logging server main function: This function implements a single-threaded, concurrent logging server that waits
in the Initiation Dispatchers handle events
event loop.
As requests arrive from clients, the
Initiation Dispatcher invokes the appropriate
Concrete Event Handler hook methods, which accept connections and receive and process logging records.
The main entry point into the logging server is defined as
follows:
/* NOTREACHED */
return 0;
}
Logging
Server
INITIALIZE
REGISTER HANDLER
FOR ACCEPTS
EXTRACT HANDLE
la :
Logging
Acceptor
lh :
Initiation
Logging
Dispatcher Handles
Handler
Initiation_Dispatcher()
register_handler(la, ACCEPT_EVENT)
get_handle()
handle_events()
select()
handle_event(ACCEPT_EVENT)
sock = acceptor_.accept()
lh = new Logging_Acceptor (sock);
register_handler(lh, READ_EVENT)
get_handle()
handle_event(READ_EVENT)
Separation of concerns: The Reactor pattern decouples application-independent demultiplexing and dispatching mechanisms from application-specific hook method
functionality. The application-independent mechanisms become reusable components that know how to demultiplex
events and dispatch the appropriate hook methods defined
by Event Handlers. In contrast, the application-specific
functionality in a hook method knows how to perform a particular type of service.
Once the Initiation Dispatcher object is initialized, it becomes the primary focus of the control flow within
the logging server. All subsequent activity is triggered by
hook methods on the Logging Acceptor and Logging
Handler objects registered with, and controlled by, the
Initiation Dispatcher.
When a connection request arrives on the network
connection, the Initiation Dispatcher calls back
the Logging Acceptor, which accepts the network
connection and creates a Logging Handler. This
Logging Handler then registers with the Initiation
Dispatcher for READ events. Thus, when a client sends
a logging record, the Initiation Dispatcher calls
back to the clients Logging Handler to process the incoming record from that client connection in the logging
servers single thread of control.
10 Known Uses
The Reactor pattern has been used in many object-oriented
frameworks, including the following:
Provides coarse-grained concurrency control: The Reactor pattern serializes the invocation of event handlers at
the level of event demultiplexing and dispatching within
a process or thread. Serialization at the Initiation
Dispatcher level often eliminates the need for more complicated synchronization or locking within an application
process.
CORBA ORBs: The ORB Core layer in many singlethreaded implementations of CORBA [12] (such as VisiBroker, Orbix, and TAO [13]) use the Reactor pattern to demultiplex and dispatch ORB requests to servants.
Ericsson EOS Call Center Management System: This
system uses the Reactor pattern to manage events routed by
Event Servers [14] between PBXs and supervisors in a Call
Center Management system.
11.2 Liabilities
The Reactor pattern has the following liabilities:
Restricted applicability: The Reactor pattern can only be
applied efficiently if the OS supports Handles. It is possible to emulate the semantics of the Reactor pattern using
multiple threads within the Initiation Dispatcher,
e.g. one thread for each Handle. Whenever there are events
available on a handle, its associated thread will read the event
and place it on a queue that is processed sequentially by the
initiation dispatcher. However, this design is typically very
inefficient since it serializes all Event Handlers, thereby
increasing synchronization and context switching overhead
without enhancing parallelism.
Project Spectrum: The high-speed medical image transfer subsystem of project Spectrum [15] uses the Reactor pattern in a medical imaging system.
11 Consequences
11.1 Benefits
The Reactor pattern offers the following benefits:
Non-preemptive: In a single-threaded application process, Event Handlers are not preempted while they are
executing. This implies that an Event Handler should
not perform blocking I/O on an individual Handle since
this will block the entire process and impede the responsiveness for clients connected to other Handles. Therefore, for long-duration operations, such as transferring multimegabyte medical images [15], the Active Object pattern
[17] may be more effective. An Active Object uses multithreading or multi-processing to complete its tasks in parallel
with the Initiation Dispatchers main event-loop.
References
[1] W. R. Stevens, UNIX Network Programming, First Edition.
Englewood Cliffs, NJ: Prentice Hall, 1990.
[2] D. C. Schmidt, ACE: an Object-Oriented Framework for
Developing Distributed Applications, in Proceedings of the
6th USENIX C++ Technical Conference, (Cambridge, Massachusetts), USENIX Association, April 1994.
Hard to debug: Applications written with the Reactor pattern can be hard to debug since the inverted flow of control oscillates between the framework infrastructure and the
method callbacks on application-specific handlers. This increases the difficulty of single-stepping through the runtime behavior of a framework within a debugger since application developers may not understand or have access to the
framework code. This is similar to the problems encountered
trying to debug a compiler lexical analyzer and parser written with LEX and YACC. In these applications, debugging
is straightforward when the thread of control is within the
user-defined action routines. Once the thread of control returns to the generated Deterministic Finite Automata (DFA)
skeleton, however, it is hard to follow the program logic.
[3] W. Pree, Design Patterns for Object-Oriented Software Development. Reading, MA: Addison-Wesley, 1994.
[4] D. C. Schmidt, An OO Encapsulation of Lightweight OS
Concurrency Mechanisms in the ACE Toolkit, Tech. Rep.
WUCS-95-31, Washington University, St. Louis, September
1995.
[5] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
[6] D. C. Schmidt and P. Stephenson, Experiences Using Design
Patterns to Evolve System Software Across Diverse OS Platforms, in Proceedings of the 9th European Conference on
Object-Oriented Programming, (Aarhus, Denmark), ACM,
August 1995.
12 See Also
[7] S. Berczuk, A Pattern for Separating Assembly and Processing, in Pattern Languages of Program Design (J. O. Coplien
and D. C. Schmidt, eds.), Reading, MA: Addison-Wesley,
1995.
10
[16] H. Custer, Inside Windows NT. Redmond, Washington: Microsoft Press, 1993.
[17] R. G. Lavender and D. C. Schmidt, Active Object: an Object
Behavioral Pattern for Concurrent Programming, in Proceedings of the 2nd Annual Conference on the Pattern Languages
of Programs, (Monticello, Illinois), pp. 17, September 1995.
[18] T. Harrison, I. Pyarali, D. C. Schmidt, and T. Jordan, Proactor An Object Behavioral Pattern for Dispatching Asynchronous Event Handlers, in The 4th Pattern Languages of
Programming Conference (Washington University technical
report #WUCS-97-34), September 1997.
11