diff --git a/TAO/bin/tao_orb_tests.lst b/TAO/bin/tao_orb_tests.lst index cc91442dd809a..ef5528073cbf4 100644 --- a/TAO/bin/tao_orb_tests.lst +++ b/TAO/bin/tao_orb_tests.lst @@ -431,6 +431,7 @@ TAO/tests/TransportCurrent/Framework/run_test.pl -static: !DISABLE_TRANSPORT_CUR TAO/tests/TransportCurrent/IIOP/run_test.pl -dynamic: !DISABLE_TRANSPORT_CURRENT !STATIC !CORBA_E_COMPACT !CORBA_E_MICRO !DISABLE_INTERCEPTORS !MINIMUM TAO/tests/TransportCurrent/IIOP/run_test.pl -static: !DISABLE_TRANSPORT_CURRENT STATIC !CORBA_E_COMPACT !CORBA_E_MICRO !DISABLE_INTERCEPTORS !MINIMUM TAO/tests/Transport_Cache_Manager/run_test.pl +TAO/tests/Transport_Idle_Timeout/run_test.pl TAO/tests/UNKNOWN_Exception/run_test.pl: TAO/tests/Native_Exceptions/run_test.pl: TAO/tests/Servant_To_Reference_Test/run_test.pl: !MINIMUM !CORBA_E_COMPACT !CORBA_E_MICRO !ST diff --git a/TAO/docs/Options.html b/TAO/docs/Options.html index 55d091eac2122..6c4db94b3a050 100644 --- a/TAO/docs/Options.html +++ b/TAO/docs/Options.html @@ -1388,6 +1388,12 @@

1.1. Resource_Factory

to your config.h file. + + -ORBTransportIdleTimeout number + Number of seconds after which idle connections are closed and purged from + the transport cache. By default idle connections are kept until they are + explicitly closed or purged because the transport cache is almost full. +

diff --git a/TAO/tao/Cache_Entries_T.inl b/TAO/tao/Cache_Entries_T.inl index 411c7d0e0ffd3..46831d312b256 100644 --- a/TAO/tao/Cache_Entries_T.inl +++ b/TAO/tao/Cache_Entries_T.inl @@ -8,7 +8,7 @@ namespace TAO { template ACE_INLINE Cache_IntId_T::Cache_IntId_T () - : transport_ (0), + : transport_ (nullptr), recycle_state_ (ENTRY_UNKNOWN), is_connected_ (false) { @@ -16,7 +16,7 @@ namespace TAO template ACE_INLINE Cache_IntId_T::Cache_IntId_T (const Cache_IntId_T &rhs) - : transport_ (0), + : transport_ (nullptr), recycle_state_ (ENTRY_UNKNOWN), is_connected_ (false) { @@ -75,7 +75,7 @@ namespace TAO { // Yield ownership of the TAO_Transport object. transport_type *val = this->transport_; - this->transport_ = 0; + this->transport_ = nullptr; return val; } @@ -112,7 +112,6 @@ namespace TAO is_delete_ (false), index_ (0) { - } template ACE_INLINE diff --git a/TAO/tao/Resource_Factory.h b/TAO/tao/Resource_Factory.h index 7e06b646e7df8..44df9ae369173 100644 --- a/TAO/tao/Resource_Factory.h +++ b/TAO/tao/Resource_Factory.h @@ -254,6 +254,8 @@ class TAO_Export TAO_Resource_Factory : public ACE_Service_Object /// replies during shutdown. virtual bool drop_replies_during_shutdown () const = 0; + virtual int transport_idle_timeout () const = 0; + protected: /** * Loads the default protocols. This method is used so that the diff --git a/TAO/tao/Transport.cpp b/TAO/tao/Transport.cpp index 49683ddaef425..2251b51177be9 100644 --- a/TAO/tao/Transport.cpp +++ b/TAO/tao/Transport.cpp @@ -127,8 +127,8 @@ TAO_Transport::TAO_Transport (CORBA::ULong tag, , tail_ (nullptr) , incoming_message_queue_ (orb_core) , current_deadline_ (ACE_Time_Value::zero) - , flush_timer_id_ (-1) , transport_timer_ (this) + , transport_idle_timer_ (this) , handler_lock_ (orb_core->resource_factory ()->create_cached_connection_lock ()) , id_ ((size_t) this) , purging_order_ (0) @@ -183,10 +183,12 @@ TAO_Transport::~TAO_Transport () { if (TAO_debug_level > 9) { - TAOLIB_DEBUG ((LM_DEBUG, ACE_TEXT ("TAO (%P|%t) - Transport[%d]::~Transport\n"), + TAOLIB_DEBUG ((LM_DEBUG, ACE_TEXT ("TAO (%P|%t) - Transport[%d]::~Transport, start\n"), this->id_)); } + this->cancel_idle_timer (); + delete this->messaging_object_; delete this->ws_; @@ -217,6 +219,11 @@ TAO_Transport::~TAO_Transport () #if TAO_HAS_TRANSPORT_CURRENT == 1 delete this->stats_; #endif /* TAO_HAS_TRANSPORT_CURRENT == 1 */ + if (TAO_debug_level > 9) + { + TAOLIB_DEBUG ((LM_DEBUG, ACE_TEXT ("TAO (%P|%t) - Transport[%d]::~Transport, end\n"), + this->id_)); + } } void @@ -324,6 +331,16 @@ TAO_Transport::register_if_necessary () void TAO_Transport::close_connection () { + if (TAO_debug_level > 4) + { + TAOLIB_DEBUG ((LM_DEBUG, + ACE_TEXT ("TAO (%P|%t) - Transport[%d]::close_connection\n"), + this->id ())); + } + + // Cancel any pending timer + this->cancel_idle_timer (); + this->connection_handler_i ()->close_connection (); } @@ -375,8 +392,7 @@ TAO_Transport::register_handler () this->ws_->is_registered (true); // Register the handler with the reactor - return r->register_handler (this->event_handler_i (), - ACE_Event_Handler::READ_MASK); + return r->register_handler (this->event_handler_i (), ACE_Event_Handler::READ_MASK); } int @@ -547,12 +563,24 @@ TAO_Transport::make_idle () this->id ())); } - return this->transport_cache_manager ().make_idle (this->cache_map_entry_); + int const result = this->transport_cache_manager ().make_idle (this->cache_map_entry_); + if (result == 0) + { + this->schedule_idle_timer (); + } + return result; } int TAO_Transport::update_transport () { + if (TAO_debug_level > 3) + { + TAOLIB_DEBUG ((LM_DEBUG, + ACE_TEXT ("TAO (%P|%t) - Transport[%d]::update_transport\n"), + this->id ())); + } + return this->transport_cache_manager ().update_entry (this->cache_map_entry_); } @@ -852,7 +880,7 @@ TAO_Transport::schedule_output_i () ACE_Event_Handler * const eh = this->event_handler_i (); ACE_Reactor * const reactor = eh->reactor (); - if (reactor == nullptr) + if (!reactor) { if (TAO_debug_level > 1) { @@ -940,23 +968,63 @@ TAO_Transport::handle_timeout (const ACE_Time_Value & /* current_time */, // pending. this->reset_flush_timer (); - TAO_Flushing_Strategy *flushing_strategy = - this->orb_core ()->flushing_strategy (); + TAO_Flushing_Strategy *flushing_strategy = this->orb_core ()->flushing_strategy (); int const result = flushing_strategy->schedule_output (this); if (result == TAO_Flushing_Strategy::MUST_FLUSH) { typedef ACE_Reverse_Lock TAO_REVERSE_LOCK; TAO_REVERSE_LOCK reverse (*this->handler_lock_); ACE_GUARD_RETURN (TAO_REVERSE_LOCK, ace_mon, reverse, -1); - if (flushing_strategy->flush_transport (this, nullptr) == -1) { - return -1; - } + if (flushing_strategy->flush_transport (this, nullptr) == -1) + { + return -1; + } } } return 0; } +int +TAO_Transport::handle_idle_timeout (const ACE_Time_Value & /* current_time */, const void */*act*/) +{ + if (TAO_debug_level > 6) + { + TAOLIB_DEBUG ((LM_DEBUG, + ACE_TEXT ("TAO (%P|%t) - Transport[%d]::handle_idle_timeout, ") + ACE_TEXT ("idle timer expired, closing transport\n"), + this->id ())); + } + + // Timer has expired, so setting the idle timer id back to -1 + this->idle_timer_id_ = -1; + + if (this->transport_cache_manager ().purge_entry_when_purgable (this->cache_map_entry_) == -1) + { + if (TAO_debug_level > 6) + TAOLIB_DEBUG ((LM_DEBUG, + ACE_TEXT ("TAO (%P|%t) - Transport[%d]::handle_idle_timeout, ") + ACE_TEXT ("idle_timeout, transport is not purgable, don't close it, reschedule it\n"), + this->id ())); + + this->schedule_idle_timer (); + } + else + { + if (TAO_debug_level > 6) + TAOLIB_DEBUG ((LM_DEBUG, + ACE_TEXT ("TAO (%P|%t) - Transport[%d]::handle_idle_timeout, ") + ACE_TEXT ("idle_timeout, transport purged due to idle timeout\n"), + this->id ())); + + // Close the underlying socket. + // close_connection() is safe to call from the reactor thread. + (void) this->close_connection (); + } + + return 0; +} + TAO_Transport::Drain_Result TAO_Transport::drain_queue (TAO::Transport::Drain_Constraints const & dc) { @@ -2729,8 +2797,7 @@ TAO_Transport::pre_close () // of the is_connected_ flag, so that during cache lookups the cache // manager doesn't need to be burdened by the lock in is_connected(). this->is_connected_ = false; - this->transport_cache_manager ().mark_connected (this->cache_map_entry_, - false); + this->transport_cache_manager ().mark_connected (this->cache_map_entry_, false); this->purge_entry (); { ACE_MT (ACE_GUARD (ACE_Lock, guard, *this->handler_lock_)); @@ -2804,13 +2871,10 @@ TAO_Transport::post_open (size_t id) ACE_TEXT (", cache_map_entry_ is [%@]\n"), this->id_, this->cache_map_entry_)); } - this->transport_cache_manager ().mark_connected (this->cache_map_entry_, - true); + this->transport_cache_manager ().mark_connected (this->cache_map_entry_, true); // update transport cache to make this entry available - this->transport_cache_manager ().set_entry_state ( - this->cache_map_entry_, - TAO::ENTRY_IDLE_AND_PURGABLE); + this->transport_cache_manager ().set_entry_state (this->cache_map_entry_, TAO::ENTRY_IDLE_AND_PURGABLE); return true; } @@ -2874,4 +2938,47 @@ TAO_Transport::connection_closed_on_read () const return connection_closed_on_read_; } +void +TAO_Transport::schedule_idle_timer () +{ + int const timeout_sec = this->orb_core_->resource_factory ()->transport_idle_timeout (); + if (timeout_sec > 0) + { + ACE_Reactor *reactor = this->orb_core_->reactor (); + const ACE_Time_Value tv (static_cast (timeout_sec)); + this->idle_timer_id_= reactor->schedule_timer (std::addressof(this->transport_idle_timer_), nullptr, tv); + + if (TAO_debug_level > 6) + { + TAOLIB_DEBUG ((LM_DEBUG, + ACE_TEXT ("TAO (%P|%t) - Transport[%d]::schedule_idle_timer, ") + ACE_TEXT ("schedule idle timer with id [%d] ") + ACE_TEXT ("in the reactor.\n"), + this->id (), this->idle_timer_id_)); + } + } +} + +void +TAO_Transport::cancel_idle_timer () +{ + if (this->idle_timer_id_ != -1) + { + ACE_Reactor *reactor = this->orb_core ()->reactor (); + if (reactor) + { + if (TAO_debug_level > 6) + { + TAOLIB_DEBUG ((LM_DEBUG, + ACE_TEXT ("TAO (%P|%t) - Transport[%d]::cancel_idle_timer, ") + ACE_TEXT ("cancel idle timer with id [%d] ") + ACE_TEXT ("from the reactor.\n"), + this->id (), this->idle_timer_id_)); + } + reactor->cancel_timer (this->idle_timer_id_); + this->idle_timer_id_ = -1; + } + } +} + TAO_END_VERSIONED_NAMESPACE_DECL diff --git a/TAO/tao/Transport.h b/TAO/tao/Transport.h index 4268a2767464f..bc32a6ef3fd8a 100644 --- a/TAO/tao/Transport.h +++ b/TAO/tao/Transport.h @@ -1,6 +1,3 @@ -// -*- C++ -*- - -//============================================================================= /** * @file Transport.h * @@ -9,7 +6,6 @@ * * @author Fred Kuhns */ -//============================================================================= #ifndef TAO_TRANSPORT_H #define TAO_TRANSPORT_H @@ -23,6 +19,7 @@ #endif /* ACE_LACKS_PRAGMA_ONCE */ #include "tao/Transport_Timer.h" +#include "tao/Transport_Idle_Timer.h" #include "tao/Incoming_Message_Queue.h" #include "tao/Incoming_Message_Stack.h" #include "tao/Message_Semantics.h" @@ -853,6 +850,9 @@ class TAO_Export TAO_Transport */ int handle_timeout (const ACE_Time_Value ¤t_time, const void* act); + /// Timeout called when the idle timer expired for this transport + int handle_idle_timeout (const ACE_Time_Value ¤t_time, const void* act); + /// Accessor to recv_buffer_size_ size_t recv_buffer_size () const; @@ -896,6 +896,9 @@ class TAO_Export TAO_Transport /// Transport statistics TAO::Transport::Stats* stats () const; + /// Helper method to cancel the timer when the transport is not idle anymore + void cancel_idle_timer (); + private: /// Helper method that returns the Transport Cache Manager. TAO::Transport_Cache_Manager &transport_cache_manager (); @@ -1058,10 +1061,8 @@ class TAO_Export TAO_Transport */ bool using_blocking_io_for_asynch_messages() const; - /* - * Specialization hook to add concrete private methods from - * TAO's protocol implementation onto the base Transport class - */ + /// Helper method to schedule a timer when the transport is made idle + void schedule_idle_timer (); protected: /// IOP protocol tag. @@ -1120,10 +1121,16 @@ class TAO_Export TAO_Transport ACE_Time_Value current_deadline_; /// The timer ID - long flush_timer_id_; + long flush_timer_id_ { -1 }; + + /// The idle timer ID + long idle_timer_id_ { -1 }; /// The adapter used to receive timeout callbacks from the Reactor - TAO_Transport_Timer transport_timer_; + TAO::Transport_Timer transport_timer_; + + /// The adapter used to receive idle timeout callbacks from the Reactor + TAO::Transport_Idle_Timer transport_idle_timer_; /// Lock that insures that activities that *might* use handler-related /// resources (such as a connection handler) get serialized. diff --git a/TAO/tao/Transport_Cache_Manager_T.cpp b/TAO/tao/Transport_Cache_Manager_T.cpp index 156e24adcf6ba..571b344634018 100644 --- a/TAO/tao/Transport_Cache_Manager_T.cpp +++ b/TAO/tao/Transport_Cache_Manager_T.cpp @@ -227,9 +227,9 @@ namespace TAO transport_type *&transport, size_t &busy_count) { - if (prop == 0) + if (prop == nullptr) { - transport = 0; + transport = nullptr; return CACHE_FOUND_NONE; } @@ -299,6 +299,9 @@ namespace TAO found = CACHE_FOUND_AVAILABLE; found_entry = entry; entry->item ().recycle_state (ENTRY_BUSY); + // We found a transport we can use, so cancel its idle timer + // with the lock held + entry->item().transport ()->cancel_idle_timer (); if (TAO_debug_level > 6) { @@ -437,7 +440,9 @@ namespace TAO // Do not mark the entry as closed if we don't have a // blockable handler added if (retval) - (*iter).int_id_.recycle_state (ENTRY_CLOSED); + { + (*iter).int_id_.recycle_state (ENTRY_CLOSED); + } } return true; @@ -508,8 +513,7 @@ namespace TAO template bool - Transport_Cache_Manager_T:: - is_entry_connecting_i (const HASH_MAP_ENTRY &entry) + Transport_Cache_Manager_T::is_entry_connecting_i (const HASH_MAP_ENTRY &entry) { Cache_Entries_State entry_state = entry.int_id_.recycle_state (); bool result = (entry_state == ENTRY_CONNECTING); @@ -537,8 +541,7 @@ namespace TAO #if !defined (ACE_LACKS_QSORT) template int - Transport_Cache_Manager_T:: - cpscmp(const void* a, const void* b) + Transport_Cache_Manager_T::cpscmp(const void* a, const void* b) { const HASH_MAP_ENTRY** left = (const HASH_MAP_ENTRY**) const_cast (a); const HASH_MAP_ENTRY** right = (const HASH_MAP_ENTRY**) const_cast (b); @@ -565,7 +568,7 @@ namespace TAO { ACE_MT (ACE_GUARD_RETURN (ACE_Lock, ace_mon, *this->cache_lock_, 0)); - DESCRIPTOR_SET sorted_set = 0; + DESCRIPTOR_SET sorted_set = nullptr; int const sorted_size = this->fill_set_i (sorted_set); // Only call close_entries () if sorted_set != 0. It takes @@ -594,8 +597,7 @@ namespace TAO { if (this->is_entry_purgable_i (*sorted_set[i])) { - transport_type* transport = - sorted_set[i]->int_id_.transport (); + transport_type* transport = sorted_set[i]->int_id_.transport (); sorted_set[i]->int_id_.recycle_state (ENTRY_BUSY); transport->add_reference (); @@ -628,13 +630,13 @@ namespace TAO } delete [] sorted_set; - sorted_set = 0; + sorted_set = nullptr; // END FORMER close_entries } } // Now, without the lock held, lets go through and close all the transports. - if (! transports_to_be_closed.is_empty ()) + if (!transports_to_be_closed.is_empty ()) { typename transport_set_type::iterator it (transports_to_be_closed); while (! it.done ()) diff --git a/TAO/tao/Transport_Cache_Manager_T.h b/TAO/tao/Transport_Cache_Manager_T.h index cfd2ba1662d69..fd4e6a8047e14 100644 --- a/TAO/tao/Transport_Cache_Manager_T.h +++ b/TAO/tao/Transport_Cache_Manager_T.h @@ -127,6 +127,9 @@ namespace TAO /// Purge the entry from the Cache Map int purge_entry (HASH_MAP_ENTRY *& entry); + /// Purge the entry from the Cache Map only when the entry is purgable + int purge_entry_when_purgable (HASH_MAP_ENTRY *& entry); + /// Mark the entry as connected. void mark_connected (HASH_MAP_ENTRY *& entry, bool state); diff --git a/TAO/tao/Transport_Cache_Manager_T.inl b/TAO/tao/Transport_Cache_Manager_T.inl index d992671e8a820..3845092e11fe4 100644 --- a/TAO/tao/Transport_Cache_Manager_T.inl +++ b/TAO/tao/Transport_Cache_Manager_T.inl @@ -60,6 +60,42 @@ namespace TAO return retval; } + template + ACE_INLINE int + Transport_Cache_Manager_T::purge_entry_when_purgable (HASH_MAP_ENTRY *&entry) + { + int retval = 0; + + if (entry != 0) + { + HASH_MAP_ENTRY* cached_entry = 0; + ACE_MT (ACE_GUARD_RETURN (ACE_Lock, guard, *this->cache_lock_, -1)); + if (entry != 0) // in case someone beat us to it (entry is reference to transport member) + { + // Only purge the entry when it is purgable + if (this->is_entry_purgable_i (*entry)) + { + // Store the entry in a temporary and zero out the reference. + // If there is only one reference count for the transport, we will end up causing + // it's destruction. And the transport can not be holding a cache map entry if + // that happens. + cached_entry = entry; + entry = 0; + + // now it's save to really purge the entry + retval = this->purge_entry_i (cached_entry); + } + else + { + // Entry is not purgable at this moment + retval = -1; + } + } + } + + return retval; + } + template ACE_INLINE void Transport_Cache_Manager_T::mark_connected (HASH_MAP_ENTRY *&entry, bool state) diff --git a/TAO/tao/Transport_Connector.cpp b/TAO/tao/Transport_Connector.cpp index 9d920a75c559d..56cd323040045 100644 --- a/TAO/tao/Transport_Connector.cpp +++ b/TAO/tao/Transport_Connector.cpp @@ -328,7 +328,6 @@ TAO_Connector::parallel_connect (TAO::Profile_Transport_Resolver *r, // connection establishment. Maybe a custom wait strategy is needed // at this point to register several potential transports so that // when one succeeds the rest are cancelled or closed. - unsigned int endpoint_count = 0; for (TAO_Endpoint *ep = root_ep->next_filtered (this->orb_core(),nullptr); ep != nullptr; @@ -705,8 +704,7 @@ TAO_Connector::connect (TAO::Profile_Transport_Resolver *r, } else // not making new connection { - (void) this->wait_for_transport (r, base_transport, - timeout, true); + (void) this->wait_for_transport (r, base_transport, timeout, true); base_transport->remove_reference (); } } diff --git a/TAO/tao/Transport_Idle_Timer.cpp b/TAO/tao/Transport_Idle_Timer.cpp new file mode 100644 index 0000000000000..0fafd882b45f6 --- /dev/null +++ b/TAO/tao/Transport_Idle_Timer.cpp @@ -0,0 +1,31 @@ +#include "tao/Transport_Idle_Timer.h" +#include "tao/Transport.h" +#include "tao/ORB_Core.h" +#include "tao/debug.h" +#include "ace/Reactor.h" + +TAO_BEGIN_VERSIONED_NAMESPACE_DECL + +namespace TAO +{ + Transport_Idle_Timer::Transport_Idle_Timer (TAO_Transport *transport) + : transport_ (transport) + { + } + + int + Transport_Idle_Timer::handle_timeout (const ACE_Time_Value ¤t_time, const void* act) + { + // Hold a reference to the transport to prevent its destruction, because that would + // also destruct this idle timer object + this->transport_->add_reference (); + + int const retval = this->transport_->handle_idle_timeout (current_time, act); + + this->transport_->remove_reference (); + + return retval; + } +} + +TAO_END_VERSIONED_NAMESPACE_DECL diff --git a/TAO/tao/Transport_Idle_Timer.h b/TAO/tao/Transport_Idle_Timer.h new file mode 100644 index 0000000000000..097286616ef58 --- /dev/null +++ b/TAO/tao/Transport_Idle_Timer.h @@ -0,0 +1,63 @@ +/** + * @file Transport_Idle_Timer.h + * + * Reactor timer that fires when a transport has been idle for the + * configured transport_idl_timeout period and triggers auto-close. + * + * @author Johnny Willemsen + */ + +#ifndef TAO_TRANSPORT_IDLE_TIMER_H +#define TAO_TRANSPORT_IDLE_TIMER_H + +#include /**/ "ace/pre.h" +#include "ace/Event_Handler.h" + +#if !defined (ACE_LACKS_PRAGMA_ONCE) +# pragma once +#endif /* ACE_LACKS_PRAGMA_ONCE */ + +#include /**/ "tao/Versioned_Namespace.h" + +TAO_BEGIN_VERSIONED_NAMESPACE_DECL + +class TAO_Transport; + +namespace TAO +{ + /** + * @class Transport_Idle_Timer + * + * @brief One-shot reactor timer that closes an idle transport. + * + * Created by TAO_Transport::schedule_idle_timer() when a transport + * enters the ENTRY_IDLE_AND_PURGABLE state. Cancelled if the + * transport is reacquired for a new request before the timer fires. + */ + class Transport_Idle_Timer : public ACE_Event_Handler + { + public: + explicit Transport_Idle_Timer (TAO_Transport *transport); + ~Transport_Idle_Timer () override = default; + + /// Reactor callback — close the transport if still idle. + int handle_timeout (const ACE_Time_Value ¤t_time, + const void *act = nullptr) override; + + Transport_Idle_Timer () = delete; + Transport_Idle_Timer (const Transport_Idle_Timer &) = delete; + Transport_Idle_Timer &operator= (const Transport_Idle_Timer &) = delete; + Transport_Idle_Timer (Transport_Idle_Timer &&) = delete; + Transport_Idle_Timer &operator= (Transport_Idle_Timer &&) = delete; + + private: + /// Transport this idle timer works on + TAO_Transport *transport_; + }; +} + +TAO_END_VERSIONED_NAMESPACE_DECL + +#include /**/ "ace/post.h" + +#endif /* TAO_TRANSPORT_IDLE_TIMER_H */ diff --git a/TAO/tao/Transport_Timer.cpp b/TAO/tao/Transport_Timer.cpp index 081692f209b40..e58b11fc269c8 100644 --- a/TAO/tao/Transport_Timer.cpp +++ b/TAO/tao/Transport_Timer.cpp @@ -1,19 +1,20 @@ -// -*- C++ -*- #include "tao/Transport_Timer.h" #include "tao/Transport.h" TAO_BEGIN_VERSIONED_NAMESPACE_DECL -TAO_Transport_Timer::TAO_Transport_Timer (TAO_Transport *transport) - : transport_ (transport) +namespace TAO { -} + Transport_Timer::Transport_Timer (TAO_Transport *transport) + : transport_ (transport) + { + } -int -TAO_Transport_Timer::handle_timeout (const ACE_Time_Value ¤t_time, - const void *act) -{ - return this->transport_->handle_timeout (current_time, act); + int + Transport_Timer::handle_timeout (const ACE_Time_Value ¤t_time, const void *act) + { + return this->transport_->handle_timeout (current_time, act); + } } TAO_END_VERSIONED_NAMESPACE_DECL diff --git a/TAO/tao/Transport_Timer.h b/TAO/tao/Transport_Timer.h index 89babfd0de6c7..0ca68a8ef3c76 100644 --- a/TAO/tao/Transport_Timer.h +++ b/TAO/tao/Transport_Timer.h @@ -1,12 +1,8 @@ -// -*- C++ -*- - -//============================================================================= /** * @file Transport_Timer.h * * @author Carlos O'Ryan */ -//============================================================================= #ifndef TAO_TRANSPORT_TIMER_H #define TAO_TRANSPORT_TIMER_H @@ -18,37 +14,39 @@ # pragma once #endif /* ACE_LACKS_PRAGMA_ONCE */ -#include /**/ "tao/TAO_Export.h" #include /**/ "tao/Versioned_Namespace.h" TAO_BEGIN_VERSIONED_NAMESPACE_DECL class TAO_Transport; -/** - * @class TAO_Transport_Timer - * - * @brief Allows TAO_Transport instances to receive timeout - * notifications from the Reactor. In other words, implements - * the Adapter Role, of the Adapter Pattern, where the Adaptee - * is a TAO_Transport and the client is the Reactor. - */ -class TAO_Transport_Timer : public ACE_Event_Handler +namespace TAO { -public: - /// Constructor /** - * @param transport The adaptee - */ - explicit TAO_Transport_Timer (TAO_Transport *transport); - - /// Receive timeout events from the Reactor and forward them to the - /// TAO_Transport - int handle_timeout (const ACE_Time_Value ¤t_time, const void *act) override; -private: - /// The Adaptee - TAO_Transport *transport_; -}; + * @class Transport_Timer + * + * @brief Allows TAO_Transport instances to receive timeout + * notifications from the Reactor. In other words, implements + * the Adapter Role, of the Adapter Pattern, where the Adaptee + * is a TAO_Transport and the client is the Reactor. + */ + class Transport_Timer : public ACE_Event_Handler + { + public: + /// Constructor + /** + * @param transport The adaptee + */ + explicit Transport_Timer (TAO_Transport *transport); + + /// Receive timeout events from the Reactor and forward them to the + /// TAO_Transport + int handle_timeout (const ACE_Time_Value ¤t_time, const void *act) override; + private: + /// The Adaptee + TAO_Transport *transport_; + }; +} TAO_END_VERSIONED_NAMESPACE_DECL diff --git a/TAO/tao/default_resource.cpp b/TAO/tao/default_resource.cpp index 7cecdeb7be8bf..17c4b1a2d0d76 100644 --- a/TAO/tao/default_resource.cpp +++ b/TAO/tao/default_resource.cpp @@ -322,7 +322,6 @@ TAO_Default_Resource_Factory::init (int argc, ACE_TCHAR *argv[]) else this->report_option_value_error (ACE_TEXT("-ORBConnectionCacheMax"), argv[curarg]); } - else if (ACE_OS::strcasecmp (argv[curarg], ACE_TEXT("-ORBConnectionCachePurgePercentage")) == 0) { @@ -484,6 +483,14 @@ TAO_Default_Resource_Factory::init (int argc, ACE_TCHAR *argv[]) ACE_TEXT ("Zero copy writes unsupported on this platform\n"))); #endif /* TAO_HAS_SENDFILE==1 */ } + else if (0 == ACE_OS::strcasecmp (argv[curarg], ACE_TEXT("-ORBTransportIdleTimeout"))) + { + ++curarg; + if (curarg < argc) + this->transport_idle_timeout_ = ACE_OS::atoi (argv[curarg]); + else + this->report_option_value_error (ACE_TEXT("-ORBTransportIdleTimeout"), argv[curarg]); + } else if (ACE_OS::strncmp (argv[curarg], ACE_TEXT ("-ORB"), 4) == 0) @@ -1217,6 +1224,11 @@ TAO_Default_Resource_Factory::drop_replies_during_shutdown () const return this->drop_replies_; } +int TAO_Default_Resource_Factory::transport_idle_timeout () const +{ + return this->transport_idle_timeout_; +} + // **************************************************************** ACE_STATIC_SVC_DEFINE (TAO_Default_Resource_Factory, diff --git a/TAO/tao/default_resource.h b/TAO/tao/default_resource.h index 4f20cb03d58c5..5e4ece6025c92 100644 --- a/TAO/tao/default_resource.h +++ b/TAO/tao/default_resource.h @@ -184,6 +184,7 @@ class TAO_Export TAO_Default_Resource_Factory CORBA::ULong max_message_size) const; virtual void disable_factory (); virtual bool drop_replies_during_shutdown () const; + virtual int transport_idle_timeout () const; //@} protected: @@ -314,6 +315,10 @@ class TAO_Export TAO_Default_Resource_Factory /// Flag to indicate whether replies should be dropped during ORB /// shutdown. bool drop_replies_; + + /// Amount of seconds after which an idle transport will be closed + /// 0 means no closing of idle connections (default) + int transport_idle_timeout_ { 0 }; }; ACE_STATIC_SVC_DECLARE_EXPORT (TAO, TAO_Default_Resource_Factory) diff --git a/TAO/tao/tao.mpc b/TAO/tao/tao.mpc index 53f1b6a23f903..c845d714decac 100644 --- a/TAO/tao/tao.mpc +++ b/TAO/tao/tao.mpc @@ -307,6 +307,7 @@ project(TAO) : acelib, install, tao_output, taodefaults, pidl, extra_core, taoid Transport_Acceptor.cpp Transport_Connector.cpp Transport_Descriptor_Interface.cpp + Transport_Idle_Timer.cpp Transport_Mux_Strategy.cpp Transport_Queueing_Strategies.cpp Transport_Selection_Guard.cpp @@ -633,6 +634,7 @@ project(TAO) : acelib, install, tao_output, taodefaults, pidl, extra_core, taoid Transport_Cache_Manager_T.h Transport_Connector.h Transport_Descriptor_Interface.h + Transport_Idle_Timer.h Transport.h Transport_Mux_Strategy.h Transport_Queueing_Strategies.h diff --git a/TAO/tests/Transport_Idle_Timeout/Echo_i.cpp b/TAO/tests/Transport_Idle_Timeout/Echo_i.cpp new file mode 100644 index 0000000000000..075db6a42b542 --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/Echo_i.cpp @@ -0,0 +1,91 @@ +#include "Echo_i.h" +#include "tao/debug.h" +#include "ace/Log_Msg.h" +#include "ace/OS_NS_string.h" +#include "ace/OS_NS_sys_time.h" +#include "tao/ORB_Core.h" +#include "tao/Transport_Cache_Manager_T.h" +#include "tao/Thread_Lane_Resources.h" + +/// Sleep for @a seconds, spinning on reactor events so that the server's +/// reactor thread (which fires the idle timer) is not blocked. +/// We cannot use ACE_OS::sleep() alone because in a single-process test +/// harness the reactor runs in the same thread. For a two-process test +/// the sleep is fine; for safety we drain reactor events anyway. +void +sleep_with_reactor (CORBA::ORB_ptr orb, int seconds) +{ + ACE_Time_Value deadline = + ACE_OS::gettimeofday () + ACE_Time_Value (seconds); + + while (ACE_OS::gettimeofday () < deadline) + { + ACE_Time_Value tv (0, 50000); // 50 ms slices + orb->perform_work (tv); + } +} + +size_t +cache_size(CORBA::ORB_ptr orb) +{ + TAO_ORB_Core *core = orb->orb_core (); + return core->lane_resources ().transport_cache ().current_size (); +} + +bool +check (const char *label, size_t got, size_t expected) +{ + if (got == expected) + { + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" [PASS] %C : cache_size = %B (expected %B)\n"), + label, got, expected)); + return true; + } + ACE_ERROR ((LM_ERROR, + ACE_TEXT (" [FAIL] %C : cache_size = %B (expected %B)\n"), + label, got, expected)); + return false; +} + +Echo_i::Echo_i (CORBA::ORB_ptr orb) + : orb_ (CORBA::ORB::_duplicate (orb)) +{ +} + +bool +Echo_i::ping (::CORBA::Long sleep_time, ::CORBA::Long cache_size_expected, ::Test::Echo_ptr server, ::CORBA::Long sleep_time_server, ::CORBA::Long cache_size_expected2) +{ + if (TAO_debug_level > 0) + ACE_DEBUG ((LM_DEBUG, + ACE_TEXT ("(%P|%t) Echo_i::ping, sleep time (%d), cache size expected (%d)\n"), sleep_time, cache_size_expected)); + + bool ok = true; + sleep_with_reactor (this->orb_, sleep_time); + + if (!CORBA::is_nil(server)) + { + if (TAO_debug_level > 0) + ACE_DEBUG ((LM_DEBUG, + ACE_TEXT ("(%P|%t) Echo_i::ping, pinging second server\n"))); + ok &= server->ping (sleep_time_server, 1, Test::Echo::_nil(), 0, 1); + if (sleep_time_server > 0) + { + sleep_with_reactor (this->orb_, sleep_time_server); + } + ok &= check ("ping_second", cache_size(this->orb_), cache_size_expected2); + } + else + { + ok &= check ("ping", cache_size(this->orb_), cache_size_expected); + } + return ok; +} + +void +Echo_i::shutdown () +{ + if (TAO_debug_level > 0) + ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Echo_i::shutdown ()\n"))); + orb_->shutdown (false); +} diff --git a/TAO/tests/Transport_Idle_Timeout/Echo_i.h b/TAO/tests/Transport_Idle_Timeout/Echo_i.h new file mode 100644 index 0000000000000..197d8c61757d4 --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/Echo_i.h @@ -0,0 +1,30 @@ +#ifndef IDLE_TRANSPORT_TIMEOUT_ECHO_I_H +#define IDLE_TRANSPORT_TIMEOUT_ECHO_I_H + +#include "testS.h" +#include "tao/ORB_Core.h" +#include "tao/Transport_Cache_Manager_T.h" + +/** + * @class Echo_i + * + * @brief Servant implementation for Test::Echo. + * + * Provides ping/cache_size/shutdown operations used by the + * Idle_Transport_Timeout regression test. + */ +class Echo_i : public virtual POA_Test::Echo +{ +public: + explicit Echo_i (CORBA::ORB_ptr orb); + + // Test::Echo operations + bool ping (::CORBA::Long sleep_time, ::CORBA::Long cache_size_expected, ::Test::Echo_ptr server, ::CORBA::Long sleep_time_server, ::CORBA::Long cache_size_expected2) override; + + void shutdown () override; + +private: + CORBA::ORB_var orb_; +}; + +#endif /* IDLE_TRANSPORT_TIMEOUT_ECHO_I_H */ diff --git a/TAO/tests/Transport_Idle_Timeout/Idle_Transport_Timeout.mpc b/TAO/tests/Transport_Idle_Timeout/Idle_Transport_Timeout.mpc new file mode 100644 index 0000000000000..9ea88aa41362d --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/Idle_Transport_Timeout.mpc @@ -0,0 +1,44 @@ +project(*idl): taoidldefaults { + IDL_Files { + test.idl + } + custom_only = 1 +} + +project(*server): taoserver { + after += *idl + Source_Files { + server.cpp + Echo_i.cpp + testS.cpp + testC.cpp + } + Header_Files { + Echo_i.h + testS.h + testC.h + } +} + +project(*client): taoclient { + after += *idl + Source_Files { + client.cpp + testC.cpp + } + Header_Files { + testC.h + } +} + +project(*client_multiple): taoclient { + exename = client_multiple + after += *idl + Source_Files { + client_multiple.cpp + testC.cpp + } + Header_Files { + testC.h + } +} diff --git a/TAO/tests/Transport_Idle_Timeout/client.cpp b/TAO/tests/Transport_Idle_Timeout/client.cpp new file mode 100644 index 0000000000000..68faafca6c7d5 --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/client.cpp @@ -0,0 +1,312 @@ +// Regression-test client for the TAO idle-transport-timeout feature. +// +// Test scenarios (executed in order): +// +// TC-1 Basic idle close +// Make one ping; verify server-side cache_size == 1; sleep past +// the timeout; verify cache_size == 0 (transport closed by timer). +// +// TC-2 Reconnect after idle close +// After TC-1, ping again; verify cache_size == 1 again (new +// transport created transparently). Confirms TRANSIENT is not +// raised by the reconnect path. +// +// TC-3 Timer cancellation on reuse +// Ping rapidly N times in a tight loop; the transport is +// continuously reacquired so the idle timer is cancelled and +// rescheduled each cycle. After the loop, sleep just under the +// timeout and verify cache_size == 1 (connection still alive). +// Then sleep past the timeout and verify cache_size == 0. +// +// TC-4 Disabled timeout (opt-out) +// A second ORB is initialised with -ORBTransportIdleTimeout 0. +// After sleeping well past the default 60 s timeout (we use a +// short test timeout of 1 s so the second ORB uses 0 = disabled), +// cache_size still reflects the connection is open. +// NOTE: This scenario requires a separate server run with +// -ORBTransportIdleTimeout 0 in its svc.conf; run_test.pl +// handles this. Within this binary TC-4 is a command-line flag. +// +// Usage: +// client -k -t [-n ] [-d] +// +// -t The idle timeout configured on the *server* (default: 3). +// The client sleeps for (timeout + 2) seconds to give the +// reactor sufficient time to fire the timer. +// -n Number of rapid-fire pings in TC-3 (default: 10). +// -d Run TC-4 (disabled-timeout) scenario instead of TC-1..3. + +#include "testC.h" +#include "ace/Get_Opt.h" +#include "ace/OS_NS_unistd.h" +#include "ace/Log_Msg.h" +#include "ace/OS_NS_sys_time.h" +#include "tao/ORB_Core.h" +#include "tao/Transport_Cache_Manager_T.h" +#include "tao/Thread_Lane_Resources.h" + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/// Sleep for @a seconds, spinning on reactor events so that the server's +/// reactor thread (which fires the idle timer) is not blocked. +/// We cannot use ACE_OS::sleep() alone because in a single-process test +/// harness the reactor runs in the same thread. For a two-process test +/// the sleep is fine; for safety we drain reactor events anyway. +void +sleep_with_reactor (CORBA::ORB_ptr orb, int seconds) +{ + ACE_Time_Value deadline = + ACE_OS::gettimeofday () + ACE_Time_Value (seconds); + + while (ACE_OS::gettimeofday () < deadline) + { + ACE_Time_Value tv (0, 50000); // 50 ms slices + orb->perform_work (tv); + } +} + +/// Verify an expected value and print PASS/FAIL. Returns false on failure. +bool +check (const char *label, size_t got, size_t expected) +{ + if (got == expected) + { + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" [PASS] %C : cache_size = %B (expected %B)\n"), + label, got, expected)); + return true; + } + ACE_ERROR ((LM_ERROR, + ACE_TEXT (" [FAIL] %C : cache_size = %B (expected %B)\n"), + label, got, expected)); + return false; +} + +/// Retrieve the current size of the cache in the client +size_t +cache_size(CORBA::ORB_ptr orb) +{ + TAO_ORB_Core *core = orb->orb_core (); + return core->lane_resources ().transport_cache ().current_size (); +} + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +static const char *ior = nullptr; +static int timeout_sec = 3; // must match server svc.conf value +static int loop_count = 10; +static bool disabled_tc = false; + +static int +parse_args (int argc, ACE_TCHAR *argv[]) +{ + ACE_Get_Opt get_opts (argc, argv, ACE_TEXT ("k:l:t:n:d")); + int c; + while ((c = get_opts ()) != -1) + switch (c) + { + case 'k': ior = get_opts.opt_arg (); break; + case 't': timeout_sec = ACE_OS::atoi (get_opts.opt_arg ()); break; + case 'n': loop_count = ACE_OS::atoi (get_opts.opt_arg ()); break; + case 'd': disabled_tc = true; break; + default: + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("Usage: client -k [-t ] [-n ] [-d]\n")), + -1); + } + if (ior == nullptr) + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("client: -k is required\n")), -1); + return 0; +} + +// --------------------------------------------------------------------------- +// TC-1 : Basic idle close +// --------------------------------------------------------------------------- +// Steps: +// 1. Ping once -> transport created, server cache_size must be 1. +// 2. Sleep (timeout + 2s) -> idle timer fires on the server. +// 3. cache_size must be 0 -> transport closed by timer. +// --------------------------------------------------------------------------- +static bool +tc1_basic_idle_close (CORBA::ORB_ptr orb, Test::Echo_ptr echo) +{ + ACE_DEBUG ((LM_INFO, ACE_TEXT ("\n=== TC-1: Basic idle close ===\n"))); + bool ok = true; + + // --- Step 1: establish a transport --- + ok &= echo->ping (0, 1, Test::Echo::_nil (), 0, 0); + + ok &= check ("TC-1 after ping (expect 1)", cache_size(orb), 1); + + // --- Step 2: idle sleep --- + int const sleep_sec = timeout_sec + 2; + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" sleeping %d s for idle timer to fire...\n"), + sleep_sec)); + sleep_with_reactor (orb, sleep_sec); + + // --- Step 3: cache must be empty now --- + ok &= check ("TC-1 after idle timeout (expect 0)", cache_size(orb), 0); + + return ok; +} + +// --------------------------------------------------------------------------- +// TC-2 : Reconnect after idle close +// --------------------------------------------------------------------------- +// Steps: +// 1. Ping (reconnects transparently after TC-1 closed the transport). +// 2. cache_size must be 1 again. +// --------------------------------------------------------------------------- +static bool +tc2_reconnect (CORBA::ORB_ptr orb, Test::Echo_ptr echo) +{ + ACE_DEBUG ((LM_INFO, ACE_TEXT ("\n=== TC-2: Reconnect after idle close ===\n"))); + bool ok = true; + + // A new ping must succeed without TRANSIENT even though TC-1 caused the + // server to close the connection. TAO's reconnect logic handles this. + ok &= echo->ping (0, 1, Test::Echo::_nil (), 0, 0); + + ok &= check ("TC-2 after reconnect ping (expect 1)", cache_size(orb), 1); + + return ok; +} + +// --------------------------------------------------------------------------- +// TC-3 : Timer cancellation on continuous reuse +// --------------------------------------------------------------------------- +// Steps: +// 1. Send N pings in rapid succession. Each one reacquires the transport +// (BUSY), cancels the idle timer, then releases back to idle and +// reschedules the timer. No close should occur mid-loop. +// 2. Sleep (timeout - 1)s — still within the window, cache must be 1. +// 3. Sleep another 4s — now past the timeout, cache must be 0. +// --------------------------------------------------------------------------- +static bool +tc3_timer_cancel_on_reuse (CORBA::ORB_ptr orb, Test::Echo_ptr echo) +{ + ACE_DEBUG ((LM_INFO, + ACE_TEXT ("\n=== TC-3: Timer cancellation on reuse (%d pings) ===\n"), + loop_count)); + bool ok = true; + + // Rapid-fire loop — transport reused each time + for (int i = 0; i < loop_count; ++i) + { + ok &= echo->ping (0, 1, Test::Echo::_nil (), 0, 0); + } + + // Immediately after the loop the transport returned to idle and the + // timer was (re)started. Cache must show 1 entry. + ok &= check ("TC-3 immediately after loop (expect 1)", cache_size(orb), 1); + + // Sleep to just before the expected timeout + int const pre_sleep = (timeout_sec > 1) ? timeout_sec - 1 : 1; + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" sleeping %d s (pre-timeout)...\n"), pre_sleep)); + sleep_with_reactor (orb, pre_sleep); + + ok &= check ("TC-3 before timeout (expect 1)", cache_size(orb), 1); + + // Sleep past the remainder of the timeout + int constexpr post_sleep = 4; + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" sleeping %d s (post-timeout)...\n"), post_sleep)); + sleep_with_reactor (orb, post_sleep); + + ok &= check ("TC-3 after timeout (expect 0)", cache_size(orb), 0); + + return ok; +} + +// --------------------------------------------------------------------------- +// TC-4 : Disabled timeout (opt-out) +// --------------------------------------------------------------------------- +// The server is started with -ORBTransportIdleTimeout 0 for this scenario. +// After sleeping well past the default timeout, the transport must still +// be present (i.e. not closed). +// --------------------------------------------------------------------------- +static bool +tc4_disabled_timeout (CORBA::ORB_ptr orb, Test::Echo_ptr echo) +{ + ACE_DEBUG ((LM_INFO, ACE_TEXT ("\n=== TC-4: Disabled timeout ===\n"))); + bool ok = true; + + ok &= echo->ping (0, 1, Test::Echo::_nil (), 0, 0); + + ok &= check ("TC-4 after ping (expect 1)", cache_size(orb), 1); + + // With timeout disabled the connection must never be closed. + // We use a short wall-clock sleep equal to (timeout_sec + 2) — when + // run_test.pl invokes TC-4 the server timeout is 0, so the timer never + // fires regardless. + const int sleep_sec = timeout_sec + 2; + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" sleeping %d s (timeout should NOT fire)...\n"), + sleep_sec)); + sleep_with_reactor (orb, sleep_sec); + + ok &= check ("TC-4 after sleep with timeout=0 (expect 1)", cache_size(orb), 1); + + return ok; +} + +// --------------------------------------------------------------------------- +// main +// --------------------------------------------------------------------------- + +int +ACE_TMAIN (int argc, ACE_TCHAR *argv[]) +{ + try + { + CORBA::ORB_var orb = CORBA::ORB_init (argc, argv); + + if (parse_args (argc, argv) != 0) + return 1; + + CORBA::Object_var obj = orb->string_to_object (ior); + Test::Echo_var echo = Test::Echo::_narrow (obj.in ()); + + if (CORBA::is_nil (echo.in ())) + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("Narrow to Test::Echo failed\n")), 1); + + bool all_pass = true; + + if (disabled_tc) + { + // TC-4 only — server was started without idle timeout + all_pass &= tc4_disabled_timeout (orb.in (), echo.in ()); + } + else + { + // TC-1, TC-2, TC-3 in sequence + all_pass &= tc1_basic_idle_close (orb.in (), echo.in ()); + all_pass &= tc2_reconnect (orb.in (), echo.in ()); + all_pass &= tc3_timer_cancel_on_reuse (orb.in (), echo.in ()); + } + + // Shut down the server + ACE_DEBUG ((LM_INFO, ACE_TEXT ("Shutting down echo\n"))); + echo->shutdown (); + + orb->destroy (); + + ACE_DEBUG ((LM_INFO, + ACE_TEXT ("\n=== Overall result: %C ===\n"), + all_pass ? "PASS" : "FAIL")); + return all_pass ? 0 : 1; + } + catch (const CORBA::Exception &ex) + { + ex._tao_print_exception (ACE_TEXT ("client exception")); + return 1; + } +} diff --git a/TAO/tests/Transport_Idle_Timeout/client_multiple.cpp b/TAO/tests/Transport_Idle_Timeout/client_multiple.cpp new file mode 100644 index 0000000000000..a3bf41fcd8a01 --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/client_multiple.cpp @@ -0,0 +1,335 @@ +// Regression-test client for the TAO idle-transport-timeout feature. +// +// Test scenarios (executed in order): +// +// TC-1 Basic idle close +// Make one ping; verify server-side cache_size == 1; sleep past +// the timeout; verify cache_size == 0 (transport closed by timer). +// +// TC-2 Reconnect after idle close +// After TC-1, ping again; verify cache_size == 1 again (new +// transport created transparently). Confirms TRANSIENT is not +// raised by the reconnect path. +// +// TC-3 Timer cancellation on reuse +// Ping rapidly N times in a tight loop; the transport is +// continuously reacquired so the idle timer is cancelled and +// rescheduled each cycle. After the loop, sleep just under the +// timeout and verify cache_size == 1 (connection still alive). +// Then sleep past the timeout and verify cache_size == 0. +// +// TC-4 Disabled timeout (opt-out) +// A second ORB is initialised with -ORBTransportIdleTimeout 0. +// After sleeping well past the default 60 s timeout (we use a +// short test timeout of 1 s so the second ORB uses 0 = disabled), +// cache_size still reflects the connection is open. +// NOTE: This scenario requires a separate server run with +// -ORBTransportIdleTimeout 0 in its svc.conf; run_test.pl +// handles this. Within this binary TC-4 is a command-line flag. +// +// Usage: +// client -k -t [-n ] [-d] +// +// -t The idle timeout configured on the *server* (default: 3). +// The client sleeps for (timeout + 2) seconds to give the +// reactor sufficient time to fire the timer. +// -n Number of rapid-fire pings in TC-3 (default: 10). +// -d Run TC-4 (disabled-timeout) scenario instead of TC-1..3. + +#include "testC.h" +#include "ace/Get_Opt.h" +#include "ace/OS_NS_unistd.h" +#include "ace/Log_Msg.h" +#include "ace/OS_NS_sys_time.h" +#include "tao/ORB_Core.h" +#include "tao/Transport_Cache_Manager_T.h" +#include "tao/Thread_Lane_Resources.h" + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/// Sleep for @a seconds, spinning on reactor events so that the server's +/// reactor thread (which fires the idle timer) is not blocked. +/// We cannot use ACE_OS::sleep() alone because in a single-process test +/// harness the reactor runs in the same thread. For a two-process test +/// the sleep is fine; for safety we drain reactor events anyway. +void +sleep_with_reactor (CORBA::ORB_ptr orb, int seconds) +{ + ACE_Time_Value deadline = + ACE_OS::gettimeofday () + ACE_Time_Value (seconds); + + while (ACE_OS::gettimeofday () < deadline) + { + ACE_Time_Value tv (0, 50000); // 50 ms slices + orb->perform_work (tv); + } +} + +/// Verify an expected value and print PASS/FAIL. Returns false on failure. +bool +check (const char *label, size_t got, size_t expected) +{ + if (got == expected) + { + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" [PASS] %C : cache_size = %B (expected %B)\n"), + label, got, expected)); + return true; + } + ACE_ERROR ((LM_ERROR, + ACE_TEXT (" [FAIL] %C : cache_size = %B (expected %B)\n"), + label, got, expected)); + return false; +} + +/// Retrieve the current size of the cache in the client +size_t +cache_size(CORBA::ORB_ptr orb) +{ + TAO_ORB_Core *core = orb->orb_core (); + return core->lane_resources ().transport_cache ().current_size (); +} + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +static const char *ior = nullptr; +static const char *ior2 = nullptr; +static int timeout_sec = 3; // must match server svc.conf value +static int loop_count = 10; +static bool disabled_tc = false; + +static int +parse_args (int argc, ACE_TCHAR *argv[]) +{ + ACE_Get_Opt get_opts (argc, argv, ACE_TEXT ("k:l:t:n:d")); + int c; + while ((c = get_opts ()) != -1) + switch (c) + { + case 'k': ior = get_opts.opt_arg (); break; + case 'l': ior2 = get_opts.opt_arg (); break; + case 't': timeout_sec = ACE_OS::atoi (get_opts.opt_arg ()); break; + case 'n': loop_count = ACE_OS::atoi (get_opts.opt_arg ()); break; + case 'd': disabled_tc = true; break; + default: + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("Usage: client -k [-l ] [-t ] [-n ] [-d]\n")), + -1); + } + if (ior == nullptr) + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("client: -k is required\n")), -1); + return 0; +} + +// --------------------------------------------------------------------------- +// TC-1 : Basic idle close +// --------------------------------------------------------------------------- +// Steps: +// 1. Ping once -> transport created, server cache_size must be 1. +// 2. Sleep (timeout + 2s) -> idle timer fires on the server. +// 3. cache_size must be 0 -> transport closed by timer. +// --------------------------------------------------------------------------- +static bool +tc1_basic_idle_close (CORBA::ORB_ptr orb, Test::Echo_ptr echo, Test::Echo_ptr echo2) +{ + ACE_DEBUG ((LM_INFO, ACE_TEXT ("\n=== TC-1: Basic idle close ===\n"))); + bool ok = true; + + // --- Step 1: establish a transport --- + ok &= echo->ping (0, 2, echo2, 0, 2); + + ok &= check ("TC-1 after ping (expect 1)", cache_size(orb), 1); + + // --- Step 2: idle sleep --- + int const sleep_sec = timeout_sec + 2; + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" sleeping %d s for idle timer to fire...\n"), + sleep_sec)); + sleep_with_reactor (orb, sleep_sec); + + // --- Step 3: cache must be empty now --- + ok &= check ("TC-1 after idle timeout (expect 0)", cache_size(orb), 0); + + ACE_DEBUG ((LM_INFO, ACE_TEXT ("\n=== TC-1: Call again ping ===\n"))); + + // Make another call, but let wait sleep_sec after the ping to the second server, + // which means the call takes longer as the idle time. + // at that moment the cache should be 1, this means the call takes longer as the idle time, + ok &= echo->ping (0, 2, echo2, sleep_sec, 1); + + ok &= check ("TC-1 after ping longer as timeout (expect 1)", cache_size(orb), 1); + + return ok; +} + +// --------------------------------------------------------------------------- +// TC-2 : Reconnect after idle close +// --------------------------------------------------------------------------- +// Steps: +// 1. Ping (reconnects transparently after TC-1 closed the transport). +// 2. cache_size must be 1 again. +// --------------------------------------------------------------------------- +static bool +tc2_reconnect (CORBA::ORB_ptr orb, Test::Echo_ptr echo, Test::Echo_ptr echo2) +{ + ACE_DEBUG ((LM_INFO, ACE_TEXT ("\n=== TC-2: Reconnect after idle close ===\n"))); + bool ok = true; + + // A new ping must succeed without TRANSIENT even though TC-1 caused the + // server to close the connection. TAO's reconnect logic handles this. + ok &= echo->ping (0, 2, echo2, 0, 2); + + ok &= check ("TC-2 after reconnect ping (expect 1)", cache_size(orb), 1); + + return ok; +} + +// --------------------------------------------------------------------------- +// TC-3 : Timer cancellation on continuous reuse +// --------------------------------------------------------------------------- +// Steps: +// 1. Send N pings in rapid succession. Each one reacquires the transport +// (BUSY), cancels the idle timer, then releases back to idle and +// reschedules the timer. No close should occur mid-loop. +// 2. Sleep (timeout - 1)s — still within the window, cache must be 1. +// 3. Sleep another 4s — now past the timeout, cache must be 0. +// --------------------------------------------------------------------------- +static bool +tc3_timer_cancel_on_reuse (CORBA::ORB_ptr orb, Test::Echo_ptr echo, Test::Echo_ptr echo2) +{ + ACE_DEBUG ((LM_INFO, + ACE_TEXT ("\n=== TC-3: Timer cancellation on reuse (%d pings) ===\n"), + loop_count)); + bool ok = true; + + // Rapid-fire loop — transport reused each time + for (int i = 0; i < loop_count; ++i) + { + ok &= echo->ping (0, 2, echo2, 0, 2); + } + + // Immediately after the loop the transport returned to idle and the + // timer was (re)started. Cache must show 1 entry. + ok &= check ("TC-3 immediately after loop (expect 1)", cache_size(orb), 1); + + // Sleep to just before the expected timeout + int const pre_sleep = (timeout_sec > 1) ? timeout_sec - 1 : 1; + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" sleeping %d s (pre-timeout)...\n"), pre_sleep)); + sleep_with_reactor (orb, pre_sleep); + + ok &= check ("TC-3 before timeout (expect 1)", cache_size(orb), 1); + + // Sleep past the remainder of the timeout + int constexpr post_sleep = 4; + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" sleeping %d s (post-timeout)...\n"), post_sleep)); + sleep_with_reactor (orb, post_sleep); + + ok &= check ("TC-3 after timeout (expect 0)", cache_size(orb), 0); + + return ok; +} + +// --------------------------------------------------------------------------- +// TC-4 : Disabled timeout (opt-out) +// --------------------------------------------------------------------------- +// The server is started with -ORBTransportIdleTimeout 0 for this scenario. +// After sleeping well past the default timeout, the transport must still +// be present (i.e. not closed). +// --------------------------------------------------------------------------- +static bool +tc4_disabled_timeout (CORBA::ORB_ptr orb, Test::Echo_ptr echo, Test::Echo_ptr echo2) +{ + ACE_DEBUG ((LM_INFO, ACE_TEXT ("\n=== TC-4: Disabled timeout ===\n"))); + bool ok = true; + + ok &= echo->ping (0, 2, echo2, 0, 1); + + ok &= check ("TC-4 after ping (expect 1)", cache_size(orb), 1); + + // With timeout disabled the connection must never be closed. + // We use a short wall-clock sleep equal to (timeout_sec + 2) — when + // run_test.pl invokes TC-4 the server timeout is 0, so the timer never + // fires regardless. + const int sleep_sec = timeout_sec + 2; + ACE_DEBUG ((LM_INFO, + ACE_TEXT (" sleeping %d s (timeout should NOT fire)...\n"), + sleep_sec)); + sleep_with_reactor (orb, sleep_sec); + + ok &= check ("TC-4 after sleep with timeout=0 (expect 1)", cache_size(orb), 1); + + return ok; +} + +// --------------------------------------------------------------------------- +// main +// --------------------------------------------------------------------------- + +int +ACE_TMAIN (int argc, ACE_TCHAR *argv[]) +{ + try + { + CORBA::ORB_var orb = CORBA::ORB_init (argc, argv); + + if (parse_args (argc, argv) != 0) + return 1; + + CORBA::Object_var obj = orb->string_to_object (ior); + CORBA::Object_var obj2; + if (ior2) + { + ACE_DEBUG ((LM_INFO, ACE_TEXT ("Client received echo2\n"))); + obj2 = orb->string_to_object (ior2); + } + Test::Echo_var echo = Test::Echo::_narrow (obj.in ()); + Test::Echo_var echo2 = Test::Echo::_narrow (obj2.in ());; + + if (CORBA::is_nil (echo.in ())) + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("Narrow to Test::Echo failed\n")), 1); + + bool all_pass = true; + + if (disabled_tc) + { + // TC-4 only — server was started without idle timeout + all_pass &= tc4_disabled_timeout (orb.in (), echo.in (), echo2.in ()); + } + else + { + // TC-1, TC-2, TC-3 in sequence + all_pass &= tc1_basic_idle_close (orb.in (), echo.in (), echo2.in ()); + all_pass &= tc2_reconnect (orb.in (), echo.in (), echo2.in ()); + all_pass &= tc3_timer_cancel_on_reuse (orb.in (), echo.in (), echo2.in ()); + } + + // Shut down the server + ACE_DEBUG ((LM_INFO, ACE_TEXT ("Shutting down echo\n"))); + echo->shutdown (); + if (!CORBA::is_nil (echo2.in ())) + { + ACE_DEBUG ((LM_INFO, ACE_TEXT ("Shutting down echo2\n"))); + echo2->shutdown (); + } + + orb->destroy (); + + ACE_DEBUG ((LM_INFO, + ACE_TEXT ("\n=== Overall result: %C ===\n"), + all_pass ? "PASS" : "FAIL")); + return all_pass ? 0 : 1; + } + catch (const CORBA::Exception &ex) + { + ex._tao_print_exception (ACE_TEXT ("client exception")); + return 1; + } +} diff --git a/TAO/tests/Transport_Idle_Timeout/run_test.pl b/TAO/tests/Transport_Idle_Timeout/run_test.pl new file mode 100755 index 0000000000000..becfb2f4db06e --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/run_test.pl @@ -0,0 +1,252 @@ +#!/usr/bin/perl + +# Test driver for the idle-transport-timeout regression test. +# Follows the standard TAO test-script conventions used throughout +# TAO/tests/ (PerlACE helpers, two-process server/client, IOR handshake). +# +# Scenarios driven: +# 1. TC-1, TC-2, TC-3 — timeout enabled (3 s), normal close/reconnect/reuse +# 2. TC-4 — timeout disabled (0), connection must persist + +use strict; +use lib "$ENV{ACE_ROOT}/bin"; +use PerlACE::TestTarget; + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +my $ior_file1 = "test1.ior"; +my $ior_file2 = "test2.ior"; +my $timeout_sec = 3; # must match svc.conf value +my $status = 0; +my $debug_level = '0'; +my $cdebug_level = '0'; +foreach my $i (@ARGV) { + if ($i eq '-debug') { + $debug_level = '10'; + } + if ($i eq '-cdebug') { + $cdebug_level = '10'; + } +} + +sub run_scenario { + my ($label, $svc_conf, $extra_client_args) = @_; + + print "\n### $label ###\n"; + + my $server = PerlACE::TestTarget::create_target (1) || die "Cannot create server target"; + my $client = PerlACE::TestTarget::create_target (2) || die "Cannot create client target"; + + my $server_ior = $server->LocalFile ($ior_file1); + my $client_ior = $client->LocalFile ($ior_file1); + + $server->DeleteFile ($ior_file1); + $client->DeleteFile ($ior_file1); + + my $SV = $server->CreateProcess ( + "server", + "-ORBdebuglevel $debug_level -ORBSvcConf $svc_conf -o $server_ior" + ); + + my $CL = $client->CreateProcess ( + "client", + "-ORBdebuglevel $cdebug_level -ORBSvcConf $svc_conf -k file://$client_ior -t $timeout_sec $extra_client_args" + ); + + # Start server + my $server_status = $SV->Spawn (); + if ($server_status != 0) { + print STDERR "ERROR: server Spawn returned $server_status\n"; + return 1; + } + + # Wait for IOR file to appear (up to 30 s) + if ($server->WaitForFileTimed ($ior_file1, + $server->ProcessStartWaitInterval ()) == -1) { + print STDERR "ERROR: IOR file '$server_ior' not created\n"; + $SV->Kill (); $SV->TimedWait (1); + return 1; + } + + # Transfer IOR file to client (no-op on single-host runs) + if ($server->GetFile ($ior_file1) == -1) { + print STDERR "ERROR: server GetFile '$ior_file1' failed\n"; + $SV->Kill (); $SV->TimedWait (1); + return 1; + } + if ($client->PutFile ($ior_file1) == -1) { + print STDERR "ERROR: client PutFile '$ior_file1' failed\n"; + $SV->Kill (); $SV->TimedWait (1); + return 1; + } + + # Run client — allow generous wall-clock budget: + # TC-1+TC-2+TC-3: ~3*(timeout+2) + 3 slack = ~20 s for timeout=3 + # TC-4: timeout+2 + 3 slack = ~8 s + my $client_budget = ($extra_client_args =~ /-d/) ? 15 : 60; + my $client_status = $CL->SpawnWaitKill ($client->ProcessStartWaitInterval () + + $client_budget); + if ($client_status != 0) { + print STDERR "ERROR: client returned $client_status\n"; + $status = 1; + } + + # Wait for server to exit (it calls orb->shutdown() on receiving shutdown()) + my $server_exit = $SV->WaitKill ($server->ProcessStopWaitInterval ()); + if ($server_exit != 0) { + print STDERR "ERROR: server returned $server_exit\n"; + $status = 1; + } + + $server->DeleteFile ($ior_file1); + $client->DeleteFile ($ior_file1); + + return $status; +} + +sub run_multiple_scenario { + my ($label, $svc_conf, $extra_client_args) = @_; + + print "\n### $label ###\n"; + + my $server1 = PerlACE::TestTarget::create_target (1) || die "Cannot create server1 target"; + my $server2 = PerlACE::TestTarget::create_target (2) || die "Cannot create server2 target"; + my $client = PerlACE::TestTarget::create_target (3) || die "Cannot create client target"; + + my $server1_ior = $server1->LocalFile ($ior_file1); + my $server2_ior = $server2->LocalFile ($ior_file2); + my $client1_ior = $client->LocalFile ($ior_file1); + my $client2_ior = $client->LocalFile ($ior_file2); + + $server1->DeleteFile ($ior_file1); + $server2->DeleteFile ($ior_file2); + $client->DeleteFile ($ior_file1); + $client->DeleteFile ($ior_file2); + + my $SV1 = $server1->CreateProcess ( + "server", + "-ORBdebuglevel $debug_level -ORBSvcConf $svc_conf -o $server1_ior" + ); + my $SV2 = $server2->CreateProcess ( + "server", + "-ORBdebuglevel $debug_level -ORBSvcConf $svc_conf -o $server2_ior" + ); + + my $CL = $client->CreateProcess ( + "client_multiple", + "-ORBdebuglevel $cdebug_level -ORBSvcConf $svc_conf -k file://$client1_ior -l file://$client2_ior -t $timeout_sec $extra_client_args" + ); + + # Start server 1 + my $server1_status = $SV1->Spawn (); + if ($server1_status != 0) { + print STDERR "ERROR: server1 Spawn returned $server1_status\n"; + return 1; + } + + # Start server 2 + my $server2_status = $SV2->Spawn (); + if ($server2_status != 0) { + print STDERR "ERROR: server2 Spawn returned $server2_status\n"; + return 1; + } + + # Wait for IOR file 1 to appear (up to 30 s) + if ($server1->WaitForFileTimed ($ior_file1, + $server1->ProcessStartWaitInterval ()) == -1) { + print STDERR "ERROR: IOR file '$server1_ior' not created\n"; + $SV1->Kill (); $SV1->TimedWait (1); + return 1; + } + + # Wait for IOR file 2 to appear (up to 30 s) + if ($server2->WaitForFileTimed ($ior_file2, + $server2->ProcessStartWaitInterval ()) == -1) { + print STDERR "ERROR: IOR file '$server2_ior' not created\n"; + $SV2->Kill (); $SV2->TimedWait (1); + return 1; + } + + # Transfer IOR file to client (no-op on single-host runs) + if ($server1->GetFile ($ior_file1) == -1) { + print STDERR "ERROR: server1 GetFile '$ior_file1' failed\n"; + $SV1->Kill (); $SV1->TimedWait (1); + return 1; + } + if ($client->PutFile ($ior_file1) == -1) { + print STDERR "ERROR: client PutFile '$ior_file1' failed\n"; + $SV1->Kill (); $SV1->TimedWait (1); + return 1; + } + if ($server2->GetFile ($ior_file2) == -1) { + print STDERR "ERROR: server2 GetFile '$ior_file2' failed\n"; + $SV2->Kill (); $SV2->TimedWait (1); + return 1; + } + if ($client->PutFile ($ior_file2) == -1) { + print STDERR "ERROR: client PutFile '$ior_file2' failed\n"; + $SV2->Kill (); $SV2->TimedWait (1); + return 1; + } + + # Run client — allow generous wall-clock budget: + # TC-1+TC-2+TC-3: ~3*(timeout+2) + 3 slack = ~20 s for timeout=3 + # TC-4: timeout+2 + 3 slack = ~8 s + my $client_budget = ($extra_client_args =~ /-d/) ? 15 : 60; + my $client_status = $CL->SpawnWaitKill ($client->ProcessStartWaitInterval () + + $client_budget); + if ($client_status != 0) { + print STDERR "ERROR: client returned $client_status\n"; + $status = 1; + } + + # Wait for server 1 to exit (it calls orb->shutdown() on receiving shutdown()) + my $server_exit1 = $SV1->WaitKill ($server1->ProcessStopWaitInterval ()); + if ($server_exit1 != 0) { + print STDERR "ERROR: server1 returned $server_exit1\n"; + $status = 1; + } + # Wait for server 2 to exit (it calls orb->shutdown() on receiving shutdown()) + my $server_exit2 = $SV2->WaitKill ($server2->ProcessStopWaitInterval ()); + if ($server_exit2 != 0) { + print STDERR "ERROR: server2 returned $server_exit2\n"; + $status = 1; + } + + $server1->DeleteFile ($ior_file1); + $server2->DeleteFile ($ior_file2); + $client->DeleteFile ($ior_file1); + $client->DeleteFile ($ior_file2); + + return $status; +} + +# --------------------------------------------------------------------------- +# Scenario 1: TC-1, TC-2, TC-3 (timeout enabled) +# --------------------------------------------------------------------------- +$status = 1 if run_scenario ( + "TC-1/TC-2/TC-3: idle timeout enabled (${timeout_sec}s)", + "svc.conf", + "" +); + +# --------------------------------------------------------------------------- +# Scenario 2: TC-4 (timeout disabled) +# --------------------------------------------------------------------------- +$status = 1 if run_scenario ( + "TC-4: idle timeout disabled", + "svc_disabled.conf", + "-d" +); + +$status = 1 if run_multiple_scenario ( + "TC-1/TC-2/TC-3: multiple idle timeout enabled (${timeout_sec}s)", + "svc.conf", + "" +); +# --------------------------------------------------------------------------- +# Final result +# --------------------------------------------------------------------------- +exit $status; diff --git a/TAO/tests/Transport_Idle_Timeout/server.cpp b/TAO/tests/Transport_Idle_Timeout/server.cpp new file mode 100644 index 0000000000000..f2e10bb28c67d --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/server.cpp @@ -0,0 +1,93 @@ +// Regression-test server for the idle-transport-timeout feature. +// +// Writes its IOR to a file so that run_test.pl (and the client) can +// locate it. Accepts a configurable svc.conf via -ORBSvcConf so the +// test driver can set -ORBTransportIdleTimeout to a small value. + +#include "Echo_i.h" +#include "ace/Get_Opt.h" +#include "ace/OS_NS_stdio.h" +#include "ace/Log_Msg.h" + +static const ACE_TCHAR *ior_output_file = ACE_TEXT ("test.ior"); + +static int +parse_args (int argc, ACE_TCHAR *argv[]) +{ + ACE_Get_Opt get_opts (argc, argv, ACE_TEXT ("o:")); + int c; + while ((c = get_opts ()) != -1) + switch (c) + { + case 'o': + ior_output_file = get_opts.opt_arg (); + break; + default: + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("Usage: server [-o ]\n")), + -1); + } + return 0; +} + +int +ACE_TMAIN (int argc, ACE_TCHAR *argv[]) +{ + try + { + CORBA::ORB_var orb = CORBA::ORB_init (argc, argv); + + if (parse_args (argc, argv) != 0) + return 1; + + CORBA::Object_var poa_object = + orb->resolve_initial_references ("RootPOA"); + + PortableServer::POA_var root_poa = + PortableServer::POA::_narrow (poa_object.in ()); + + if (CORBA::is_nil (root_poa.in ())) + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("Nil RootPOA\n")), 1); + + PortableServer::POAManager_var mgr = root_poa->the_POAManager (); + + Echo_i *echo_impl = nullptr; + ACE_NEW_RETURN (echo_impl, Echo_i (orb.in ()), 1); + PortableServer::ServantBase_var owner (echo_impl); + + PortableServer::ObjectId_var oid = + root_poa->activate_object (echo_impl); + + CORBA::Object_var obj = root_poa->id_to_reference (oid.in ()); + Test::Echo_var echo = Test::Echo::_narrow (obj.in ()); + + CORBA::String_var ior = orb->object_to_string (echo.in ()); + + // Write IOR to file so the test driver and client can find it + FILE *f = ACE_OS::fopen (ior_output_file, ACE_TEXT ("w")); + if (f == nullptr) + ACE_ERROR_RETURN ((LM_ERROR, + ACE_TEXT ("Cannot open output file '%s'\n"), + ior_output_file), 1); + ACE_OS::fprintf (f, "%s", ior.in ()); + ACE_OS::fclose (f); + + mgr->activate (); + + ACE_DEBUG ((LM_DEBUG, + ACE_TEXT ("(%P|%t) server: IOR written to '%s'\n"), + ior_output_file)); + + orb->run (); + + root_poa->destroy (true, true); + orb->destroy (); + } + catch (const CORBA::Exception &ex) + { + ex._tao_print_exception (ACE_TEXT ("server exception")); + return 1; + } + return 0; +} diff --git a/TAO/tests/Transport_Idle_Timeout/svc.conf b/TAO/tests/Transport_Idle_Timeout/svc.conf new file mode 100644 index 0000000000000..7bfea6122334e --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/svc.conf @@ -0,0 +1,4 @@ +# Used by server and client for TC-1, TC-2, TC-3. +# Sets a short idle timeout (3 seconds) so the test completes quickly. + +static Resource_Factory "-ORBTransportIdleTimeout 3" diff --git a/TAO/tests/Transport_Idle_Timeout/svc_disabled.conf b/TAO/tests/Transport_Idle_Timeout/svc_disabled.conf new file mode 100644 index 0000000000000..6c1175a035bbe --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/svc_disabled.conf @@ -0,0 +1,5 @@ +# Used by server and client for TC-4 (disabled idle timeout, default). +# Setting -ORBTransportIdleTimeout 0 disables the feature entirely; +# transports must persist + +static Resource_Factory "-ORBTransportIdleTimeout 0" diff --git a/TAO/tests/Transport_Idle_Timeout/test.idl b/TAO/tests/Transport_Idle_Timeout/test.idl new file mode 100644 index 0000000000000..4ca53c2a7ba55 --- /dev/null +++ b/TAO/tests/Transport_Idle_Timeout/test.idl @@ -0,0 +1,19 @@ +// Simple interface used by the transport idle timeout regression test. +// The server exposes a single 'ping' operation so the client can +// establish a real IIOP connection, then go silent and let the idle +// timer fire. + +module Test +{ + interface Echo + { + /// Ping the server, sleeps the number of seconds as passed with the call. + /// Sleeping means running the reactor to make sure idle connections are purged + /// Pass in the cache size we expect just before returning the call on the server + /// side + boolean ping (in long sleep_time, in long cache_size_expected, in Echo server, in long sleep_time_server, in long cache_size_expected2); + + /// Orderly shutdown. + oneway void shutdown (); + }; +};