Skip to content

Commit 5f0f236

Browse files
stal76Georgy Kirichenko
authored andcommitted
Fix error in counters allocator
Also add some unittests.
1 parent 0d29ff4 commit 5f0f236

File tree

2 files changed

+252
-6
lines changed

2 files changed

+252
-6
lines changed

controlplane/segment_allocator.h

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ class SegmentAllocator
164164
return {errors_external_, errors_internal_};
165165
}
166166

167-
private:
168167
struct OneBlock
169168
{
170169
// indexes of previous and next blocks in list
@@ -248,21 +247,57 @@ class SegmentAllocator
248247

249248
// set bits usage
250249
DISABLE_BIT(masks[group], index);
251-
if (masks[group] == 0)
252-
{
253-
DISABLE_BIT(group_mask, group);
254-
}
250+
DISABLE_BIT(group_mask, group);
255251

256252
// change number of free segments
257253
free_segments++;
258254

259255
return 0;
260256
}
257+
258+
bool CheckInvariants()
259+
{
260+
uint16_t total_free = 0;
261+
for (uint16_t index = 0; index < total_segments; index++)
262+
{
263+
uint16_t group = index / 64;
264+
if (GET_BIT(masks[group], index % 64) == 0)
265+
{
266+
total_free++;
267+
}
268+
}
269+
270+
if (total_free != free_segments)
271+
{
272+
std::cout << "total_free != free_segments: " << total_free << ", " << free_segments << "\n";
273+
return false;
274+
}
275+
276+
uint16_t groups = sizeof(masks) / sizeof(masks[0]);
277+
for (uint16_t group = 0; group < 64; group++)
278+
{
279+
uint16_t bit_expected = 0;
280+
if ((group < groups) && (~masks[group] == 0))
281+
{
282+
bit_expected = 1;
283+
}
284+
uint16_t bit_value = GET_BIT(group_mask, group);
285+
if (bit_expected != bit_value)
286+
{
287+
std::cout << "bad bit in group mask " << group << ", expected=" << bit_expected << ", value=" << bit_value << "\n";
288+
return false;
289+
}
290+
}
291+
292+
return true;
293+
}
261294
};
262295

296+
static constexpr uint16_t error_in_block_ = static_cast<uint16_t>(-1);
297+
298+
private:
263299
static constexpr uint32_t total_blocks_ = (IndexEnd - IndexBegin + BlockSize - 1) / BlockSize;
264300
static constexpr uint32_t null_block_ = static_cast<uint32_t>(-1);
265-
static constexpr uint16_t error_in_block_ = static_cast<uint16_t>(-1);
266301

267302
OneBlock all_blocks_[total_blocks_];
268303
OneSizeBlockInfo sizes_info_[MaxBufferSize + 1];

controlplane/unittest/segment_allocator.cpp

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include <gtest/gtest.h>
2+
#include <vector>
23

4+
#include "common/type.h"
35
#include "controlplane/segment_allocator.h"
46

57
static constexpr uint32_t error_result = 0;
@@ -225,6 +227,215 @@ TEST(SegmentAllocator, OnlyErrors)
225227
EXPECT_EQ(allocator.GetErrors(), Errors(9, 0));
226228
}
227229

230+
bool TestBlockSegmentAllocator(uint16_t segment_size, uint16_t groups, uint16_t count_group)
231+
{
232+
static constexpr uint32_t counter_index_begin = (((uint32_t)common::globalBase::static_counter_type::size + 63) / 64) * 64;
233+
static constexpr uint32_t max_buffer_size = 64;
234+
using SegmentAllocatorType = SegmentAllocator<counter_index_begin, YANET_CONFIG_COUNTERS_SIZE, 64 * 64, max_buffer_size, 0>;
235+
SegmentAllocatorType::OneBlock block;
236+
237+
block.Initialize(0, 0);
238+
block.SetSize(segment_size);
239+
int free_segments = block.free_segments;
240+
241+
for (uint32_t retries = 0; retries < groups; retries++)
242+
{
243+
std::vector<uint16_t> data;
244+
// Try allocate "count_group" segments
245+
for (uint16_t index = 0; index < count_group && free_segments > 0; index++)
246+
{
247+
uint16_t value = block.Allocate();
248+
if (value == SegmentAllocatorType::error_in_block_)
249+
{
250+
std::cerr << "Error allocate segment in block\n";
251+
return false;
252+
}
253+
free_segments--;
254+
data.push_back(value);
255+
256+
if (!block.CheckInvariants())
257+
{
258+
return false;
259+
}
260+
}
261+
262+
for (uint16_t index = 0; index < data.size(); index += 2)
263+
{
264+
if (block.Free(data[index]) == SegmentAllocatorType::error_in_block_)
265+
{
266+
std::cout << "Error free segment in block\n";
267+
return false;
268+
}
269+
free_segments++;
270+
271+
if (!block.CheckInvariants())
272+
{
273+
return false;
274+
}
275+
}
276+
}
277+
278+
return true;
279+
}
280+
281+
TEST(SegmentAllocator, Block)
282+
{
283+
EXPECT_TRUE(TestBlockSegmentAllocator(2, 10, 1000));
284+
EXPECT_TRUE(TestBlockSegmentAllocator(4, 10, 500));
285+
EXPECT_TRUE(TestBlockSegmentAllocator(6, 10, 500));
286+
EXPECT_TRUE(TestBlockSegmentAllocator(37, 10, 100));
287+
}
288+
289+
struct TestOneSizeInfo
290+
{
291+
uint32_t size;
292+
uint32_t accumulated_weight;
293+
uint32_t elements_in_block;
294+
uint32_t used_blocks = 0;
295+
std::vector<uint32_t> segments;
296+
std::set<uint32_t> segments_map;
297+
};
298+
299+
bool TestSegmentAllocatorDifferentSizes(std::vector<std::pair<uint32_t, uint32_t>> test_data)
300+
{
301+
static constexpr uint32_t counter_index_begin = (((uint32_t)common::globalBase::static_counter_type::size + 63) / 64) * 64;
302+
static constexpr uint32_t max_buffer_size = 64;
303+
static constexpr uint32_t block_size = 64 * 64;
304+
static constexpr uint32_t counters_size = YANET_CONFIG_COUNTERS_SIZE / 8;
305+
static constexpr uint32_t total_full_blocks = (counters_size - counter_index_begin) / block_size;
306+
SegmentAllocator<counter_index_begin, counters_size, block_size, max_buffer_size, 0> allocator;
307+
308+
uint32_t sizes = test_data.size();
309+
uint32_t total_weight = 0;
310+
std::vector<TestOneSizeInfo> tests_info(sizes);
311+
for (uint32_t index = 0; index < sizes; index++)
312+
{
313+
total_weight += test_data[index].second;
314+
TestOneSizeInfo& test_info = tests_info[index];
315+
test_info.size = test_data[index].first;
316+
test_info.accumulated_weight = total_weight;
317+
test_info.elements_in_block = block_size / test_info.size;
318+
}
319+
320+
std::srand(17);
321+
uint32_t used_blocks = 0;
322+
while (used_blocks + sizes <= total_full_blocks)
323+
{
324+
// Select size
325+
uint32_t weight = std::rand() % total_weight;
326+
uint32_t cur_index = 0;
327+
for (uint32_t index = 1; index < sizes; index++)
328+
{
329+
if (weight >= tests_info[index - 1].accumulated_weight)
330+
{
331+
cur_index = index;
332+
}
333+
}
334+
335+
// Select action
336+
TestOneSizeInfo& test_info = tests_info[cur_index];
337+
static constexpr int32_t probability_free = 30;
338+
bool action_free = ((std::rand() % 100) < probability_free);
339+
if (action_free)
340+
{
341+
// Free
342+
uint32_t current_size = test_info.segments.size();
343+
if (current_size != 0)
344+
{
345+
uint32_t index = std::rand() % current_size;
346+
uint32_t value = test_info.segments[index];
347+
test_info.segments_map.erase(value);
348+
test_info.segments[index] = test_info.segments.back();
349+
test_info.segments.pop_back();
350+
351+
if (allocator.Free(value, test_info.size) == 0)
352+
{
353+
std::cout << "Error free segment in allocator\n";
354+
return false;
355+
}
356+
}
357+
}
358+
else
359+
{
360+
// Allocate
361+
uint32_t value = allocator.Allocate(test_info.size);
362+
if (value == 0)
363+
{
364+
std::cout << "can't allocate\n";
365+
return false;
366+
}
367+
else if (test_info.segments_map.find(value) != test_info.segments_map.end())
368+
{
369+
std::cout << "Allocated segment exists\n";
370+
return false;
371+
}
372+
test_info.segments_map.insert(value);
373+
test_info.segments.push_back(value);
374+
if (test_info.segments.size() % test_info.elements_in_block == 1)
375+
{
376+
used_blocks = 0;
377+
for (const TestOneSizeInfo& info : tests_info)
378+
{
379+
used_blocks += (info.segments.size() + info.elements_in_block - 1) / info.elements_in_block;
380+
}
381+
}
382+
}
383+
}
384+
385+
EXPECT_EQ(allocator.GetErrors(), Errors(0, 0));
386+
387+
uint32_t used_counters = 0;
388+
for (const TestOneSizeInfo& info : tests_info)
389+
{
390+
used_counters += info.size * info.segments.size();
391+
}
392+
uint32_t total_counters = counters_size - counter_index_begin;
393+
EXPECT_GE(used_counters, 0.95 * total_counters);
394+
395+
return true;
396+
}
397+
398+
TEST(SegmentAllocator, DifferentSizes)
399+
{
400+
EXPECT_TRUE(TestSegmentAllocatorDifferentSizes({{2, 10}, {4, 5}, {6, 1}}));
401+
EXPECT_TRUE(TestSegmentAllocatorDifferentSizes({{8, 1}, {17, 1}, {37, 1}}));
402+
}
403+
404+
TEST(SegmentAllocator, AddAndHalfDelete)
405+
{
406+
static constexpr uint32_t counter_index_begin = (((uint32_t)common::globalBase::static_counter_type::size + 63) / 64) * 64;
407+
static constexpr uint32_t max_buffer_size = 64;
408+
SegmentAllocator<counter_index_begin, YANET_CONFIG_COUNTERS_SIZE, 64 * 64, max_buffer_size, 0> allocator;
409+
410+
uint16_t size = 2;
411+
uint32_t count = 100000;
412+
bool errors = false;
413+
for (uint32_t retries = 0; retries < 10 && !errors; retries++)
414+
{
415+
std::vector<uint32_t> data;
416+
417+
for (uint32_t index = 0; index < count && !errors; index++)
418+
{
419+
uint32_t value = allocator.Allocate(size);
420+
EXPECT_NE(value, 0);
421+
if (value == 0)
422+
{
423+
errors = true;
424+
break;
425+
}
426+
data.push_back(value);
427+
}
428+
429+
for (uint32_t index = 0; index < count && !errors; index += 2)
430+
{
431+
EXPECT_TRUE(allocator.Free(data[index], size));
432+
}
433+
434+
EXPECT_EQ(allocator.GetErrors(), Errors(0, 0));
435+
}
436+
EXPECT_EQ(allocator.GetErrors(), Errors(0, 0));
437+
}
438+
228439
int main(int argc, char** argv)
229440
{
230441
::testing::InitGoogleTest(&argc, argv);

0 commit comments

Comments
 (0)