Skip to content

Commit 9afb997

Browse files
authored
Merge pull request #446 from PavelGuzenfeld/fix/try-enqueue-implicit-index-docs-418
Document try_enqueue implicit producer index limit (#418)
2 parents d37a467 + 217f4af commit 9afb997

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

concurrentqueue.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,12 @@ struct ConcurrentQueueDefaultTraits
355355

356356
// How many full blocks can be expected for a single implicit producer? This should
357357
// reflect that number's maximum for optimal performance. Must be a power of 2.
358+
// Note: This impacts the maximum number of elements that can be enqueued by a
359+
// single implicit producer when using try_enqueue/try_enqeue_bulk exclusively (which
360+
// cannot allocate), since it limits the number of blocks that the producer can hold to
361+
// store elements. When pre-allocating blocks for use with try-enqueueing, configure
362+
// this initial size to the desired maximum number of blocks per implicit producer.
363+
// Alternately, use the regular enqueue methods, which can grow the index as needed.
358364
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32;
359365

360366
// The initial size of the hash table mapping thread IDs to implicit producers.
@@ -1063,6 +1069,9 @@ class ConcurrentQueue
10631069
// Does not allocate memory. Fails if not enough room to enqueue (or implicit
10641070
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
10651071
// is 0).
1072+
// Note: If using only try_enqueue/try_enqueue_bulk with pre-allocated blocks, configure
1073+
// Traits::IMPLICIT_INITIAL_INDEX_SIZE appropriately to ensure the index has sufficient
1074+
// capacity for the number of blocks each producer may need.
10661075
// Thread-safe.
10671076
inline bool try_enqueue(T const& item)
10681077
{
@@ -1074,6 +1083,9 @@ class ConcurrentQueue
10741083
// Does not allocate memory (except for one-time implicit producer).
10751084
// Fails if not enough room to enqueue (or implicit production is
10761085
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
1086+
// Note: If using only try_enqueue/try_enqueue_bulk with pre-allocated blocks, configure
1087+
// Traits::IMPLICIT_INITIAL_INDEX_SIZE appropriately to ensure the index has sufficient
1088+
// capacity for the number of blocks each producer may need.
10771089
// Thread-safe.
10781090
inline bool try_enqueue(T&& item)
10791091
{
@@ -1101,6 +1113,9 @@ class ConcurrentQueue
11011113
// Does not allocate memory (except for one-time implicit producer).
11021114
// Fails if not enough room to enqueue (or implicit production is
11031115
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
1116+
// Note: If using only try_enqueue/try_enqueue_bulk with pre-allocated blocks, configure
1117+
// Traits::IMPLICIT_INITIAL_INDEX_SIZE appropriately to ensure the index has sufficient
1118+
// capacity for the number of blocks each producer may need.
11041119
// Note: Use std::make_move_iterator if the elements should be moved
11051120
// instead of copied.
11061121
// Thread-safe.

tests/unittests/unittests.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,18 @@ struct LargeTraits : public MallocTrackingTraits
136136
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 128;
137137
};
138138

139+
struct SmallImplicitIndexTraits : public MallocTrackingTraits
140+
{
141+
static const size_t BLOCK_SIZE = 4;
142+
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 4;
143+
};
144+
145+
struct LargerImplicitIndexTraits : public MallocTrackingTraits
146+
{
147+
static const size_t BLOCK_SIZE = 4;
148+
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 16;
149+
};
150+
139151
// Note: Not thread safe!
140152
struct Foo
141153
{
@@ -375,6 +387,7 @@ class ConcurrentQueueTests : public TestClass<ConcurrentQueueTests>
375387

376388
REGISTER_TEST(explicit_strings_threaded);
377389
REGISTER_TEST(large_traits);
390+
REGISTER_TEST(implicit_producer_index_limit);
378391
}
379392

380393
bool postTest(bool testSucceeded) override
@@ -5031,6 +5044,68 @@ class ConcurrentQueueTests : public TestClass<ConcurrentQueueTests>
50315044
return true;
50325045
}
50335046

5047+
bool implicit_producer_index_limit()
5048+
{
5049+
// Issue #418: try_enqueue() fails around BLOCK_SIZE * IMPLICIT_INITIAL_INDEX_SIZE
5050+
// elements even when blocks have been pre-allocated, because the block index
5051+
// (not the blocks themselves) needs to grow, which requires allocation that
5052+
// try_enqueue refuses to do.
5053+
5054+
// SmallImplicitIndexTraits: BLOCK_SIZE=4, IMPLICIT_INITIAL_INDEX_SIZE=4, limit=16
5055+
// LargerImplicitIndexTraits: BLOCK_SIZE=4, IMPLICIT_INITIAL_INDEX_SIZE=16, limit=64
5056+
5057+
{
5058+
// Demonstrate the limit: try_enqueue fails at BLOCK_SIZE * IMPLICIT_INITIAL_INDEX_SIZE
5059+
const int limit = (int)(SmallImplicitIndexTraits::BLOCK_SIZE * SmallImplicitIndexTraits::IMPLICIT_INITIAL_INDEX_SIZE);
5060+
ConcurrentQueue<int, SmallImplicitIndexTraits> q(limit + 64); // Pre-allocate plenty of blocks
5061+
5062+
int successCount = 0;
5063+
for (int i = 0; i < limit + 64; ++i) {
5064+
if (!q.try_enqueue(i))
5065+
break;
5066+
++successCount;
5067+
}
5068+
// try_enqueue should stop succeeding at the index limit
5069+
ASSERT_OR_FAIL(successCount == limit);
5070+
}
5071+
5072+
{
5073+
// Workaround #1: enqueue() (which can allocate) can grow the index
5074+
const int limit = (int)(SmallImplicitIndexTraits::BLOCK_SIZE * SmallImplicitIndexTraits::IMPLICIT_INITIAL_INDEX_SIZE);
5075+
ConcurrentQueue<int, SmallImplicitIndexTraits> q(limit + 64);
5076+
5077+
for (int i = 0; i < limit + 64; ++i) {
5078+
ASSERT_OR_FAIL(q.enqueue(i));
5079+
}
5080+
// Verify all elements are dequeued correctly
5081+
int item;
5082+
for (int i = 0; i < limit + 64; ++i) {
5083+
ASSERT_OR_FAIL(q.try_dequeue(item));
5084+
ASSERT_OR_FAIL(item == i);
5085+
}
5086+
ASSERT_OR_FAIL(!q.try_dequeue(item));
5087+
}
5088+
5089+
{
5090+
// Workaround #2: larger IMPLICIT_INITIAL_INDEX_SIZE allows more try_enqueue calls
5091+
const int small_limit = (int)(SmallImplicitIndexTraits::BLOCK_SIZE * SmallImplicitIndexTraits::IMPLICIT_INITIAL_INDEX_SIZE); // 16
5092+
const int large_limit = (int)(LargerImplicitIndexTraits::BLOCK_SIZE * LargerImplicitIndexTraits::IMPLICIT_INITIAL_INDEX_SIZE); // 64
5093+
ConcurrentQueue<int, LargerImplicitIndexTraits> q(large_limit + 64);
5094+
5095+
int successCount = 0;
5096+
for (int i = 0; i < large_limit + 64; ++i) {
5097+
if (!q.try_enqueue(i))
5098+
break;
5099+
++successCount;
5100+
}
5101+
// Should succeed well past the old small limit
5102+
ASSERT_OR_FAIL(successCount > small_limit);
5103+
ASSERT_OR_FAIL(successCount == large_limit);
5104+
}
5105+
5106+
return true;
5107+
}
5108+
50345109
bool large_traits()
50355110
{
50365111
union Elem { uint32_t x; char dummy[156]; };

0 commit comments

Comments
 (0)