Ru-Brd |
MotivationMany networked applications perform activities periodically or must be notified when specified time periods have elapsed. For example, Web servers require watchdog timers that release resources if clients don't send an HTTP GET request shortly after they connect. Native OS timer capabilities vary, but many platforms share the following problems:
One way to avoid these problems is to manage timers in the normal course of event handling, as follows :
It's hard to develop this type of timer facility portably across OS platforms, however, due to the wide range of capabilities and restrictions. Moreover, this capability is often redeveloped for many projects due to tight coupling between the timer queue mechanism and the synchronous event demultiplexing mechanism. To alleviate the need for application developers to rewrite efficient, scalable, and portable time-driven dispatchers in an ad hoc manner, the ACE Reactor framework defines a family of reusable timer queue classes. Class CapabilitiesThe ACE timer queue classes allow applications to register time-driven event handlers derived from ACE_Event_Handler . These classes provide the following capabilities:
The interfaces and relationships of all the ACE timer queue classes are shown in Figure 3.5 (page 64). The key methods in these classes are outlined in the following table: Figure 3.5. The ACE Timer Queue Classes
The schedule() method of an ACE timer queue must be passed two parameters:
This method can optionally be passed the following parameters:
When a timer queue dispatches an event handler's handle_timeout() method, it passes as parameters the current time and the void pointer ACT passed to the schedule() method when the event handler was scheduled originally. The return value of schedule() uniquely identifies each timer event that's scheduled with a timer queue. Applications can pass the unique timer identifier to the cancel() method to remove a particular event handler before it expires. Applications can also pass the address of the handler to cancel() to remove all timers associated with a particular event handler. If a non- NULL pointer to a void pointer is passed to cancel() , it's assigned the ACT passed by the application when the timer was scheduled originally, which makes it possible to allocate an ACT dynamically without leaking memory. The ACE timer queue classes combine design patterns, hook methods, and template arguments to provide the following timer queue implementations :
The ACE Reactor framework allows developers to use any of these timer queue implementations to achieve the functionality needed by their applications, without burdening them with a "one size fits all" implementation. Since the methods in the ACE_Timer_Queue base class are virtual, applications can also integrate their own implementations of other timer queue mechanisms.
ExampleAlthough the Logging_Acceptor and Logging_Event_Handler event handlers in Section 3.3 implement the logging server functionality correctly, they may consume system resources unnecessarily. For example, clients can connect to a server and then not send log records for long periods of time. In the example below, we illustrate how to apply the ACE timer queue mechanisms to reclaim resources from those event handlers whose clients log records infrequently. Our design is based on the Evictor pattern [HV99], which describes how and when to release resources, such as memory and I/O handles, in order to optimize system resource management. We use the Evictor pattern in conjunction with the ACE Reactor framework's timer queue mechanisms to check periodically when each registered event handler has received its last client log record. If the time the last log record was received exceeds a designated threshold (one hour ), the following actions occur:
Clients must detect these closed connections and reestablish them when they need to send more log records, as shown in the Example portions of Sections 6.2 and 7.4. This example uses ACE_Timer_Queue::gettimeofday() to obtain all time-of-day values. This method allows us to change the program's time source at run time as described in Sidebar 14 (page 65). For example, if changes to the system's time-of-day clock should neither delay nor hasten client evictions, the following code can be added to main() before any timer- related work is done: ACE_Reactor *r = ACE_Reactor::instance (); ACE_High_Res_Timer::global_scale_factor (); r->timer_queue ()-> gettimeofday (&ACE_High_Res_Timer::gettimeofday_hr); The ACE_High_Res_Timer::global_scale_factor() call performs any initialization needed to obtain accurate timer values before the program starts doing real work. To implement the Evictor pattern, we extend Logging_Acceptor and Logging_Event_Handler in Section 3.3 to create Logging_Acceptor_Ex and Logging_Event_Handler_Ex . We then register a timer for every instance of Logging_Event_Handler_Ex . Since the default ACE timer queue ( ACE_Timer_Heap ) is highly scalable and efficient, its scheduling, cancellation, and dispatching overhead is low, even if there are thousands of timers and instances of Logging_Event_Handler_Ex . We start by creating a new header file called Logging_Acceptor_Ex.h that contains the new Logging_Acceptor_Ex class. The changes to this class are minor. We simply override and modify its handle_input() method to create a Logging_Event_Handler_Ex rather than a Logging_Event_Handler , as shown below: #include "ace/INET_Addr.h" #include "ace/Reactor.h" #include "Logging_Acceptor.h" #include "Logging_Event_Handler_Ex.h" class Logging_Acceptor_Ex : public Logging_Acceptor { public: typedef ACE_INET_Addr PEER_ADDR; // Simple constructor to pass <ACE_Reactor> to base class. Logging_Acceptor_Ex (ACE_Reactor *r = ACE_Reactor::instance ()) : Logging_Acceptor (r) {} int handle_input (ACE_HANDLE) { Logging_Event_Handler_Ex *peer_handler = 0; ACE_NEW_RETURN (peer_handler, Logging_Event_Handler_Ex (reactor ()), -1); // ... same as Logging_Acceptor::handle_input() } }; In Chapter 7, we'll illustrate how the ACE Acceptor-Connector framework uses C++ features (such as templates, inheritance, and dynamic binding) and design patterns (such as Template Method and Strategy [GoF]) to add new behavior to an event handler without copying or modifying existing code. The Logging_Event_Handler_Ex class involves more substantial additions, so we create both a Logging_Event_Handler_Ex.h file and a Logging_Event_Handler_Ex.cpp file. In Logging_Event_Handler_Ex.h we extend Logging_Event_Handler to create the following Logging_Event_Handler_Ex class: class Logging_Event_Handler_Ex : public Logging_Event_Handler { private: // Time when a client last sent a log record. ACE_Time_Value time_of_last_log_record_; // Maximum time to wait for a client log record. const ACE_Time_Value max_client_timeout_; We implement the Evictor pattern by adding an ACE_Time_Value that keeps track of the time when a client last sent a log record. The methods in the public interface of Logging_Event_Handler_Ex are shown below: public: typedef Logging_Event_Handler PARENT; // 3600 seconds == one hour. enum { MAX_CLIENT_TIMEOUT = 3600 }; Logging_Event_Handler_Ex (ACE_Reactor *reactor, const ACE_Time_Value &max_client_timeout = ACE_Time_Value (MAX_CLIENT_TIMEOUT)) : Logging_Event_Handler (reactor), time_of_last_log_record (0), max_client_timeout_ (max_client_timeout) {} virtual Logging_Event_Handler_Ex () { /* no-op */ } virtual int open (); // Activate the event handler. // Called by a reactor when logging events arrive. virtual int handle_input (ACE_HANDLE); // Called when a timeout expires to check if the client has // been idle for an excessive amount of time. virtual int handle_timeout (const ACE_Time_Value &tv, const void *act); // Called when this object is destroyed, e.g., when it's // removed from a reactor. virtual int handle_close (ACE_HANDLE = ACE_INVALID_HANDLE, ACE_Reactor_Mask = 0); }; The Logging_Event_Handler_Ex::handle_input() method notes the time when a log record is received from the connected client. We get the time from the timer queue's time source to ensure that all comparisons are consistent. After recording the time, this method forwards to its parent's handle_input() method to process the log record, as shown below: int Logging_Event_Handler_Ex::handle_input (ACE_HANDLE h) { time_of_last_log_record_ = reactor ()->timer_queue ()->gettimeofday (); return PARENT::handle_input (h); } The open() method is shown next: 1 int Logging_Event_Handler_Ex::open () { 2 int result = PARENT::open (); 3 if (result != -1) { 4 ACE_Time_Value reschedule (max_client_timeout_.sec () / 4); 5 result = reactor ()->schedule_timer 6 (this, 0, 7 max_client_timeout_, // Initial timeout. 8 reschedule); // Subsequent timeouts. 9 } 10 return result; 11 } Line 2 Forward to the parent's open() method (page 59). Lines 4 “8 Call ACE_Reactor::schedule_timer() (page 76) to schedule this event handler to be dispatched by the reactor periodically to check whether its client sent it a log record recently. We schedule the initial timer to expire in max_client_timeout_ seconds (which defaults to one hour) and also request that it then expire periodically every max_client_timeout_ / 4 seconds (i.e., check every 15 minutes). When a timer expires, the reactor uses its timer queue mechanism to dispatch the following handle_timeout() hook method automatically: int Logging_Event_Handler_Ex::handle_timeout (const ACE_Time_Value &now, const void *) { if (now - time_of_last_log_record_ >= max_client_timeout_) reactor ()->remove_handler (this, ACE_Event_Handler::READ_MASK); return 0; } When an ACE timer queue dispatches handle_timeout() , it sets the now parameter to the absolute time (from ACE_Timer_Queue::gettimeofday() ) when the queue expires the timer. Logging_Event_Handler_Ex::handle_timeout() checks if the time elapsed between now and the time when this event handler received its last log record is greater than the designated max_client_timeout_ threshold. If so, it calls ACE_Reactor::remove_handler() , which triggers the reactor to call the following hook method to remove the event handler from the reactor: int Logging_Event_Handler_Ex::handle_close (ACE_HANDLE, ACE_Reactor_Mask) { reactor ()->cancel_timer (this); return PARENT::handle_close (); } This method cancels the handler's timer and calls its parent's handle_close() method (page 60), which closes the log file and socket to the client, and then deletes itself. |
Ru-Brd |