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 @@
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 ();
+ };
+};