diff --git a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp index 9437078d..fe4ba9e2 100644 --- a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp +++ b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp @@ -2530,6 +2530,9 @@ STEAMNETWORKINGSOCKETS_INTERFACE void GameNetworkingSockets_Kill() s_pSteamNetworkingSockets->Destroy(); s_pSteamNetworkingSockets = nullptr; } + + // Release message objects that were being kept around for reuse + CSteamNetworkingMessage::FreePooledMessages(); } STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets_LibV12() diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp index 2719caf5..75fff1b3 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp @@ -79,6 +79,169 @@ bool BCheckGlobalSpamReplyRateLimit( SteamNetworkingMicroseconds usecNow ) // ///////////////////////////////////////////////////////////////////////////// +// CSteamNetworkingMessage objects are allocated and freed for every message +// sent and received, so we recycle them instead of hitting the heap each time. +// +// Each thread keeps a small cache of free objects, so the common case is a +// lock-free push/pop. Messages are often allocated on one thread and freed +// on another (e.g. received on the service thread and released by the app), +// so when a thread's cache fills up it hands a batch to a shared depot, and a +// thread with an empty cache refills from the depot before going to the heap. +// Both the caches and the depot are bounded; anything beyond that is freed. +// +// Pooling hides use-after-free bugs from AddressSanitizer, so it is off in +// ASan builds. It can also be disabled with +// STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL=0. +#ifndef STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL + #if defined( __SANITIZE_ADDRESS__ ) + #define STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL 0 + #elif defined( __has_feature ) + #if __has_feature( address_sanitizer ) + #define STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL 0 + #endif + #endif +#endif +#ifndef STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL + // Old Apple toolchains don't have thread_local (see GetThreadDebugInfo) + #if defined(__APPLE__) && __clang_major__ < 8 + #define STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL 0 + #else + #define STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL 1 + #endif +#endif + +#if STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL + +constexpr int k_nMessagePoolThreadCacheMax = 64; // Max free objects cached per thread +constexpr int k_nMessagePoolTransferBatch = 32; // Objects moved to/from the depot at once +constexpr int k_nMessagePoolDepotMax = 1024; // Max free objects in the shared depot + +// Per-thread cache. Deliberately trivially destructible, so that it remains +// usable (in "disabled" mode) even after this thread's cleanup has run. +struct MessagePoolThreadCache +{ + CSteamNetworkingMessage *m_arpFree[ k_nMessagePoolThreadCacheMax ]; + int m_nFree; + bool m_bCleanupRegistered; + bool m_bDisabled; // Thread is exiting, don't cache anything else +}; +static thread_local MessagePoolThreadCache tls_messagePoolCache; + +static ShortDurationMutexImpl s_lockMessagePoolDepot; // Leaf lock, never take another lock while holding it +static CSteamNetworkingMessage *s_arpMessagePoolDepot[ k_nMessagePoolDepotMax ]; +static int s_nMessagePoolDepot; + +struct MessagePool +{ + // Move free objects from the end of a thread's cache into the depot. + // Objects that don't fit are freed. + static void ReturnToDepot( MessagePoolThreadCache &cache, int nCount ) + { + Assert( nCount <= cache.m_nFree ); + int nMoved; + { + std::lock_guard lock( s_lockMessagePoolDepot ); + nMoved = std::min( nCount, k_nMessagePoolDepotMax - s_nMessagePoolDepot ); + memcpy( &s_arpMessagePoolDepot[ s_nMessagePoolDepot ], &cache.m_arpFree[ cache.m_nFree - nMoved ], nMoved * sizeof(CSteamNetworkingMessage *) ); + s_nMessagePoolDepot += nMoved; + } + cache.m_nFree -= nMoved; + for ( int i = nMoved ; i < nCount ; ++i ) + delete cache.m_arpFree[ --cache.m_nFree ]; + } + + static void RegisterThreadCleanup( MessagePoolThreadCache &cache ); + + static CSteamNetworkingMessage *Alloc() + { + MessagePoolThreadCache &cache = tls_messagePoolCache; + if ( likely( cache.m_nFree > 0 ) ) + return cache.m_arpFree[ --cache.m_nFree ]; + + // Cache is empty. Try to grab a batch from the depot + if ( !cache.m_bDisabled ) + { + { + std::lock_guard lock( s_lockMessagePoolDepot ); + int n = std::min( k_nMessagePoolTransferBatch, s_nMessagePoolDepot ); + s_nMessagePoolDepot -= n; + memcpy( cache.m_arpFree, &s_arpMessagePoolDepot[ s_nMessagePoolDepot ], n * sizeof(CSteamNetworkingMessage *) ); + cache.m_nFree = n; + } + if ( cache.m_nFree > 0 ) + { + if ( !cache.m_bCleanupRegistered ) + RegisterThreadCleanup( cache ); + return cache.m_arpFree[ --cache.m_nFree ]; + } + } + + return new CSteamNetworkingMessage; + } + + static void Free( CSteamNetworkingMessage *pMsg ) + { + MessagePoolThreadCache &cache = tls_messagePoolCache; + if ( unlikely( cache.m_bDisabled ) ) + { + delete pMsg; + return; + } + if ( unlikely( !cache.m_bCleanupRegistered ) ) + RegisterThreadCleanup( cache ); + if ( unlikely( cache.m_nFree >= k_nMessagePoolThreadCacheMax ) ) + ReturnToDepot( cache, k_nMessagePoolTransferBatch ); + cache.m_arpFree[ cache.m_nFree++ ] = pMsg; + } + + static void FreeAll() + { + MessagePoolThreadCache &cache = tls_messagePoolCache; + while ( cache.m_nFree > 0 ) + delete cache.m_arpFree[ --cache.m_nFree ]; + + std::lock_guard lock( s_lockMessagePoolDepot ); + while ( s_nMessagePoolDepot > 0 ) + delete s_arpMessagePoolDepot[ --s_nMessagePoolDepot ]; + } +}; + +// Runs when a thread that used the pool exits: give its free objects back to +// the depot for other threads to use. +struct MessagePoolThreadCleanup +{ + ~MessagePoolThreadCleanup() + { + MessagePoolThreadCache &cache = tls_messagePoolCache; + cache.m_bDisabled = true; + MessagePool::ReturnToDepot( cache, cache.m_nFree ); + } +}; + +void MessagePool::RegisterThreadCleanup( MessagePoolThreadCache &cache ) +{ + // Constructing this thread_local registers its destructor to run at thread exit + static thread_local MessagePoolThreadCleanup tls_cleanup; + (void)tls_cleanup; + cache.m_bCleanupRegistered = true; +} + +#else // #if STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL + +struct MessagePool +{ + static inline CSteamNetworkingMessage *Alloc() { return new CSteamNetworkingMessage; } + static inline void Free( CSteamNetworkingMessage *pMsg ) { delete pMsg; } + static inline void FreeAll() {} +}; + +#endif // #if STEAMNETWORKINGSOCKETS_ENABLE_MESSAGE_POOL + +void CSteamNetworkingMessage::FreePooledMessages() +{ + MessagePool::FreeAll(); +} + void CSteamNetworkingMessage::DefaultFreeData( SteamNetworkingMessage_t *pMsg ) { free( pMsg->m_pData ); @@ -103,15 +266,18 @@ void CSteamNetworkingMessage::ReleaseFunc( SteamNetworkingMessage_t *pIMsg ) Assert( !pMsg->m_linksSecondaryQueue.m_pPrev ); Assert( !pMsg->m_linksSecondaryQueue.m_pNext ); - // Self destruct - // FIXME Should avoid this dynamic memory call with some sort of pooling - delete pMsg; + // Self destruct (return the object to the pool) + MessagePool::Free( pMsg ); } CSteamNetworkingMessage *CSteamNetworkingMessage::New( uint32 cbSize ) { - // FIXME Should avoid this dynamic memory call with some sort of pooling - CSteamNetworkingMessage *pMsg = new CSteamNetworkingMessage; + CSteamNetworkingMessage *pMsg = MessagePool::Alloc(); + if ( pMsg == nullptr ) + { + SpewError( "Failed to allocate message" ); + return nullptr; + } // NOTE: Intentionally not memsetting the whole thing; // this struct is pretty big. @@ -122,7 +288,7 @@ CSteamNetworkingMessage *CSteamNetworkingMessage::New( uint32 cbSize ) pMsg->m_pData = malloc( cbSize ); if ( pMsg->m_pData == nullptr ) { - delete pMsg; + MessagePool::Free( pMsg ); SpewError( "Failed to allocate %d-byte message buffer", cbSize ); return nullptr; } @@ -150,6 +316,7 @@ CSteamNetworkingMessage *CSteamNetworkingMessage::New( uint32 cbSize ) pMsg->m_nMessageNumber = 0; pMsg->m_nChannel = -1; pMsg->m_nFlags = 0; + pMsg->m_nUserData = 0; // Objects are reused, don't leak the previous message's value pMsg->m_idxLane = 0; pMsg->m_links.Clear(); pMsg->m_linksSecondaryQueue.Clear(); diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h index 73996e61..8d082f52 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.h @@ -85,6 +85,13 @@ class CSteamNetworkingMessage : public SteamNetworkingMessage_t static CSteamNetworkingMessage *New( uint32 cbSize ); static void DefaultFreeData( SteamNetworkingMessage_t *pMsg ); + /// Free the message objects that are cached for reuse by the current + /// thread and the shared pool. (Other threads free their own caches + /// when they exit.) + static void FreePooledMessages(); + + friend struct MessagePool; + /// OK to delay sending this message until this time. Set to zero to explicitly force /// Nagle timer to expire and send now (but this should behave the same as if the /// timer < usecNow). If the timer is cleared, then all messages with lower message numbers diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9746282e..3b98fb2e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,3 +57,10 @@ if(ENABLE_ICE) add_custom_target( publish_test_script ALL DEPENDS ${P2P_TEST_SCRIPT} ${STUN_SERVER_SCRIPT} ) add_test(NAME p2p COMMAND ${P2P_TEST_SCRIPT} WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) endif() + +# Message allocation / pipe throughput microbenchmark (not run by ctest) +add_executable( + bench_messages + bench_messages.cpp) +set_target_common_gns_properties( bench_messages ) +target_link_libraries(bench_messages ${GAMENETWORKINGSOCKETS_LIB}) diff --git a/tests/bench_messages.cpp b/tests/bench_messages.cpp new file mode 100644 index 00000000..731cffe4 --- /dev/null +++ b/tests/bench_messages.cpp @@ -0,0 +1,184 @@ +// Microbenchmarks for SteamNetworkingMessage_t allocation and the +// send/receive path of a pipe (CreateSocketPair without network loopback), +// where the message objects themselves are the main per-message cost. +// +// Usage: bench_messages [iterations] + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using Clock = std::chrono::steady_clock; + +static double NsPerOp( Clock::time_point start, Clock::time_point end, long long ops ) +{ + return std::chrono::duration( end - start ).count() / (double)ops; +} + +// AllocateMessage + Release in a tight loop on one thread +static double BenchAllocRelease( long long nIters, int cbPayload ) +{ + ISteamNetworkingUtils *pUtils = SteamNetworkingUtils(); + auto start = Clock::now(); + for ( long long i = 0 ; i < nIters ; ++i ) + { + SteamNetworkingMessage_t *pMsg = pUtils->AllocateMessage( cbPayload ); + pMsg->Release(); + } + return NsPerOp( start, Clock::now(), nIters ); +} + +// Allocate a burst of messages, then release them all (more realistic +// working set than alloc/free of a single object) +static double BenchAllocReleaseBurst( long long nIters, int cbPayload, int nBurst ) +{ + ISteamNetworkingUtils *pUtils = SteamNetworkingUtils(); + std::vector vec( nBurst ); + long long nRounds = nIters / nBurst; + auto start = Clock::now(); + for ( long long r = 0 ; r < nRounds ; ++r ) + { + for ( int i = 0 ; i < nBurst ; ++i ) + vec[i] = pUtils->AllocateMessage( cbPayload ); + for ( int i = 0 ; i < nBurst ; ++i ) + vec[i]->Release(); + } + return NsPerOp( start, Clock::now(), nRounds * nBurst ); +} + +// Send batches of small reliable messages over a pipe and receive them on the +// same thread. +static double BenchPipeSameThread( long long nIters, int cbPayload, int nBatch ) +{ + HSteamNetConnection hA, hB; + bool ok = SteamNetworkingSockets()->CreateSocketPair( &hA, &hB, false, nullptr, nullptr ); + assert( ok ); (void)ok; + ISteamNetworkingUtils *pUtils = SteamNetworkingUtils(); + std::vector vecSend( nBatch ), vecRecv( nBatch ); + long long nRounds = nIters / nBatch; + auto start = Clock::now(); + for ( long long r = 0 ; r < nRounds ; ++r ) + { + for ( int i = 0 ; i < nBatch ; ++i ) + { + SteamNetworkingMessage_t *pMsg = pUtils->AllocateMessage( cbPayload ); + pMsg->m_conn = hA; + pMsg->m_nFlags = k_nSteamNetworkingSend_Reliable; + vecSend[i] = pMsg; + } + SteamNetworkingSockets()->SendMessages( nBatch, vecSend.data(), nullptr, true ); + int nGot = 0; + while ( nGot < nBatch ) + { + int n = SteamNetworkingSockets()->ReceiveMessagesOnConnection( hB, vecRecv.data(), nBatch ); + for ( int i = 0 ; i < n ; ++i ) + vecRecv[i]->Release(); + nGot += n; + } + } + double ns = NsPerOp( start, Clock::now(), nRounds * nBatch ); + SteamNetworkingSockets()->CloseConnection( hA, 0, nullptr, false ); + SteamNetworkingSockets()->CloseConnection( hB, 0, nullptr, false ); + return ns; +} + +// Producer thread allocates and sends; consumer thread receives and releases. +// Messages are allocated on one thread and freed on another. +static double BenchPipeTwoThreads( long long nIters, int cbPayload, int nBatch ) +{ + HSteamNetConnection hA, hB; + bool ok = SteamNetworkingSockets()->CreateSocketPair( &hA, &hB, false, nullptr, nullptr ); + assert( ok ); (void)ok; + long long nRounds = nIters / nBatch; + long long nTotal = nRounds * nBatch; + std::atomic bGo{ false }; + auto start = Clock::now(); + std::thread consumer( [&]() { + std::vector vecRecv( nBatch ); + long long nGot = 0; + while ( !bGo.load() ) {} + while ( nGot < nTotal ) + { + int n = SteamNetworkingSockets()->ReceiveMessagesOnConnection( hB, vecRecv.data(), nBatch ); + for ( int i = 0 ; i < n ; ++i ) + vecRecv[i]->Release(); + nGot += n; + } + } ); + { + ISteamNetworkingUtils *pUtils = SteamNetworkingUtils(); + std::vector vecSend( nBatch ); + start = Clock::now(); + bGo = true; + for ( long long r = 0 ; r < nRounds ; ++r ) + { + for ( int i = 0 ; i < nBatch ; ++i ) + { + SteamNetworkingMessage_t *pMsg = pUtils->AllocateMessage( cbPayload ); + pMsg->m_conn = hA; + pMsg->m_nFlags = k_nSteamNetworkingSend_Reliable; + vecSend[i] = pMsg; + } + SteamNetworkingSockets()->SendMessages( nBatch, vecSend.data(), nullptr, true ); + } + } + consumer.join(); + double ns = NsPerOp( start, Clock::now(), nTotal ); + SteamNetworkingSockets()->CloseConnection( hA, 0, nullptr, false ); + SteamNetworkingSockets()->CloseConnection( hB, 0, nullptr, false ); + return ns; +} + +static double Median( std::vector v ) +{ + std::sort( v.begin(), v.end() ); + return v[ v.size() / 2 ]; +} + +int main( int argc, const char **argv ) +{ + long long nIters = argc > 1 ? atoll( argv[1] ) : 2000000; + const int nRuns = 7; + + SteamDatagramErrMsg errMsg; + if ( !GameNetworkingSockets_Init( nullptr, errMsg ) ) + { + fprintf( stderr, "GameNetworkingSockets_Init failed. %s\n", errMsg ); + return 1; + } + // Don't let the send rate limit or buffer size throttle the pipe benchmarks + SteamNetworkingUtils()->SetGlobalConfigValueInt32( k_ESteamNetworkingConfig_SendBufferSize, 64*1024*1024 ); + + struct Result { const char *name; std::vector ns; }; + std::vector results = { + { "alloc+release, 0 B payload", {} }, + { "alloc+release, 64 B payload", {} }, + { "alloc+release burst of 256, 64 B", {} }, + { "pipe send+recv, 1 thread, 64 B, batch 64", {} }, + { "pipe send+recv, 2 threads, 64 B, batch 64", {} }, + }; + for ( int run = 0 ; run < nRuns ; ++run ) + { + results[0].ns.push_back( BenchAllocRelease( nIters, 0 ) ); + results[1].ns.push_back( BenchAllocRelease( nIters, 64 ) ); + results[2].ns.push_back( BenchAllocReleaseBurst( nIters, 64, 256 ) ); + results[3].ns.push_back( BenchPipeSameThread( nIters / 4, 64, 64 ) ); + results[4].ns.push_back( BenchPipeTwoThreads( nIters / 4, 64, 64 ) ); + } + printf( "%-45s %12s %12s %12s\n", "benchmark (median of 7 runs)", "ns/msg", "min", "max" ); + for ( const Result &r: results ) + printf( "%-45s %12.1f %12.1f %12.1f\n", r.name, Median( r.ns ), + *std::min_element( r.ns.begin(), r.ns.end() ), *std::max_element( r.ns.begin(), r.ns.end() ) ); + + GameNetworkingSockets_Kill(); + return 0; +} diff --git a/tests/test_connection.cpp b/tests/test_connection.cpp index 44284160..67ad236d 100644 --- a/tests/test_connection.cpp +++ b/tests/test_connection.cpp @@ -8,6 +8,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -1561,6 +1565,125 @@ void Test_send_buffer_full() SteamNetworkingSockets()->CloseConnection( hRecver, 0, nullptr, false ); } + +// Message objects are recycled through per-thread caches and a shared pool. +// Make sure recycled messages come back fully initialized, including when they +// are allocated on one thread and released on another, and when threads exit. +void Test_message_pool() +{ + TEST_Printf( "***************************************************\n" ); + TEST_Printf( "Message pool\n" ); + TEST_Printf( "***************************************************\n" ); + + ISteamNetworkingUtils *pUtils = SteamNetworkingUtils(); + + auto CheckFresh = []( SteamNetworkingMessage_t *pMsg, int cbSize ) + { + assert( pMsg ); + assert( pMsg->m_cbSize == cbSize ); + assert( ( pMsg->m_pData != nullptr ) == ( cbSize > 0 ) ); + assert( ( pMsg->m_pfnFreeData != nullptr ) == ( cbSize > 0 ) ); + assert( pMsg->m_pfnRelease != nullptr ); + assert( pMsg->m_conn == k_HSteamNetConnection_Invalid ); + assert( pMsg->m_identityPeer.IsInvalid() ); + assert( pMsg->m_nConnUserData == 0 ); + assert( pMsg->m_usecTimeReceived == 0 ); + assert( pMsg->m_nMessageNumber == 0 ); + assert( pMsg->m_nChannel == -1 ); + assert( pMsg->m_nFlags == 0 ); + assert( pMsg->m_nUserData == 0 ); + assert( pMsg->m_idxLane == 0 ); + }; + + // Dirty every field we are allowed to touch, so a recycled message that + // isn't reinitialized would be caught. + auto Scribble = []( SteamNetworkingMessage_t *pMsg ) + { + if ( pMsg->m_cbSize > 0 ) + memset( pMsg->m_pData, 0xcc, pMsg->m_cbSize ); + pMsg->m_conn = 1234; + pMsg->m_nConnUserData = 5678; + pMsg->m_nChannel = 3; + pMsg->m_nFlags = k_nSteamNetworkingSend_Reliable; + pMsg->m_nUserData = 0x1badf00d; + pMsg->m_idxLane = 2; + }; + + // Many more messages than any cache holds, same thread + { + std::vector vecMsgs; + for ( int round = 0 ; round < 3 ; ++round ) + { + for ( int i = 0 ; i < 5000 ; ++i ) + { + int cbSize = ( i % 3 ) * 100; + SteamNetworkingMessage_t *pMsg = pUtils->AllocateMessage( cbSize ); + CheckFresh( pMsg, cbSize ); + Scribble( pMsg ); + vecMsgs.push_back( pMsg ); + } + for ( SteamNetworkingMessage_t *pMsg: vecMsgs ) + pMsg->Release(); + vecMsgs.clear(); + } + } + + // Allocate on producer threads, release on consumer threads. The threads + // exit afterwards, so their cached objects go back to the shared pool. + { + const int k_nPairs = 4; + const int k_nMsgsPerPair = 20000; + std::vector threads; + std::vector> handoff( k_nPairs ); + std::vector> locks; + for ( int i = 0 ; i < k_nPairs ; ++i ) + locks.emplace_back( new std::mutex ); + std::atomic nReleased{ 0 }; + for ( int p = 0 ; p < k_nPairs ; ++p ) + { + threads.emplace_back( [&, p]() { + for ( int i = 0 ; i < k_nMsgsPerPair ; ++i ) + { + int cbSize = ( i & 1 ) ? 16 : 0; + SteamNetworkingMessage_t *pMsg = pUtils->AllocateMessage( cbSize ); + CheckFresh( pMsg, cbSize ); + Scribble( pMsg ); + std::lock_guard lock( *locks[p] ); + handoff[p].push_back( pMsg ); + } + } ); + threads.emplace_back( [&, p]() { + int nDone = 0; + std::vector vecLocal; + while ( nDone < k_nMsgsPerPair ) + { + { + std::lock_guard lock( *locks[p] ); + vecLocal.swap( handoff[p] ); + } + for ( SteamNetworkingMessage_t *pMsg: vecLocal ) + pMsg->Release(); + nDone += (int)vecLocal.size(); + vecLocal.clear(); + std::this_thread::yield(); + } + nReleased += nDone; + } ); + } + for ( std::thread &t: threads ) + t.join(); + assert( nReleased == k_nPairs * k_nMsgsPerPair ); + } + + // Messages recycled from exited threads come back clean + for ( int i = 0 ; i < 2000 ; ++i ) + { + SteamNetworkingMessage_t *pMsg = pUtils->AllocateMessage( 32 ); + CheckFresh( pMsg, 32 ); + pMsg->Release(); + } +} + int main( int argc, const char **argv ) { typedef void (*FnTest)(void); @@ -1580,7 +1703,8 @@ int main( int argc, const char **argv ) TEST(lane_quick_priority_and_background), TEST(pipe), TEST(send_buffer_full), - TEST(recv_buf_full) + TEST(recv_buf_full), + TEST(message_pool) }; struct Suite_t { @@ -1588,7 +1712,7 @@ int main( int argc, const char **argv ) std::vector< Test_t > m_vecTests; }; static const Suite_t test_suites[] = { - { "suite-quick", { TEST(identity), TEST(quick), TEST(lane_quick_queueanddrain), TEST(lane_quick_priority_and_background), TEST(pipe), TEST(send_buffer_full), TEST(recv_buf_full) } } + { "suite-quick", { TEST(identity), TEST(quick), TEST(lane_quick_queueanddrain), TEST(lane_quick_priority_and_background), TEST(pipe), TEST(send_buffer_full), TEST(recv_buf_full), TEST(message_pool) } } }; if ( argc < 2 )