1 /* 2 Copyright (c) 2005-2021 Intel Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 //! \file test_malloc_pools.cpp 18 //! \brief Test for [memory_allocation] functionality 19 20 #define __TBB_NO_IMPLICIT_LINKAGE 1 21 22 #include "common/test.h" 23 24 #define HARNESS_TBBMALLOC_THREAD_SHUTDOWN 1 25 26 #include "common/utils.h" 27 #include "common/utils_assert.h" 28 #include "common/spin_barrier.h" 29 #include "common/tls_limit.h" 30 31 #include "tbb/scalable_allocator.h" 32 33 #include <atomic> 34 35 template<typename T> 36 static inline T alignUp (T arg, uintptr_t alignment) { 37 return T(((uintptr_t)arg+(alignment-1)) & ~(alignment-1)); 38 } 39 40 struct PoolSpace: utils::NoCopy { 41 size_t pos; 42 int regions; 43 size_t bufSize; 44 char *space; 45 46 static const size_t BUF_SIZE = 8*1024*1024; 47 48 PoolSpace(size_t bufSz = BUF_SIZE) : 49 pos(0), regions(0), 50 bufSize(bufSz), space(new char[bufSize]) { 51 memset(space, 0, bufSize); 52 } 53 ~PoolSpace() { 54 delete []space; 55 } 56 }; 57 58 static PoolSpace *poolSpace; 59 60 struct MallocPoolHeader { 61 void *rawPtr; 62 size_t userSize; 63 }; 64 65 static std::atomic<int> liveRegions; 66 67 static void *getMallocMem(intptr_t /*pool_id*/, size_t &bytes) 68 { 69 void *rawPtr = malloc(bytes+sizeof(MallocPoolHeader)+1); 70 if (!rawPtr) 71 return NULL; 72 // +1 to check working with unaligned space 73 void *ret = (void *)((uintptr_t)rawPtr+sizeof(MallocPoolHeader)+1); 74 75 MallocPoolHeader *hdr = (MallocPoolHeader*)ret-1; 76 hdr->rawPtr = rawPtr; 77 hdr->userSize = bytes; 78 79 liveRegions++; 80 81 return ret; 82 } 83 84 static int putMallocMem(intptr_t /*pool_id*/, void *ptr, size_t bytes) 85 { 86 MallocPoolHeader *hdr = (MallocPoolHeader*)ptr-1; 87 ASSERT(bytes == hdr->userSize, "Invalid size in pool callback."); 88 free(hdr->rawPtr); 89 90 liveRegions--; 91 92 return 0; 93 } 94 95 void TestPoolReset() 96 { 97 rml::MemPoolPolicy pol(getMallocMem, putMallocMem); 98 rml::MemoryPool *pool; 99 100 pool_create_v1(0, &pol, &pool); 101 for (int i=0; i<100; i++) { 102 REQUIRE(pool_malloc(pool, 8)); 103 REQUIRE(pool_malloc(pool, 50*1024)); 104 } 105 int regionsBeforeReset = liveRegions.load(std::memory_order_acquire); 106 bool ok = pool_reset(pool); 107 REQUIRE(ok); 108 for (int i=0; i<100; i++) { 109 REQUIRE(pool_malloc(pool, 8)); 110 REQUIRE(pool_malloc(pool, 50*1024)); 111 } 112 REQUIRE_MESSAGE(regionsBeforeReset == liveRegions.load(std::memory_order_relaxed), 113 "Expected no new regions allocation."); 114 ok = pool_destroy(pool); 115 REQUIRE(ok); 116 REQUIRE_MESSAGE(!liveRegions.load(std::memory_order_relaxed), "Expected all regions were released."); 117 } 118 119 class SharedPoolRun: utils::NoAssign { 120 static long threadNum; 121 static utils::SpinBarrier startB, 122 mallocDone; 123 static rml::MemoryPool *pool; 124 static void **crossThread, 125 **afterTerm; 126 public: 127 static const int OBJ_CNT = 100; 128 129 static void init(int num, rml::MemoryPool *pl, void **crThread, void **aTerm) { 130 threadNum = num; 131 pool = pl; 132 crossThread = crThread; 133 afterTerm = aTerm; 134 startB.initialize(threadNum); 135 mallocDone.initialize(threadNum); 136 } 137 138 void operator()( int id ) const { 139 const int ITERS = 1000; 140 void *local[ITERS]; 141 142 startB.wait(); 143 for (int i=id*OBJ_CNT; i<(id+1)*OBJ_CNT; i++) { 144 afterTerm[i] = pool_malloc(pool, i%2? 8*1024 : 9*1024); 145 memset(afterTerm[i], i, i%2? 8*1024 : 9*1024); 146 crossThread[i] = pool_malloc(pool, i%2? 9*1024 : 8*1024); 147 memset(crossThread[i], i, i%2? 9*1024 : 8*1024); 148 } 149 150 for (int i=1; i<ITERS; i+=2) { 151 local[i-1] = pool_malloc(pool, 6*1024); 152 memset(local[i-1], i, 6*1024); 153 local[i] = pool_malloc(pool, 16*1024); 154 memset(local[i], i, 16*1024); 155 } 156 mallocDone.wait(); 157 int myVictim = threadNum-id-1; 158 for (int i=myVictim*OBJ_CNT; i<(myVictim+1)*OBJ_CNT; i++) 159 pool_free(pool, crossThread[i]); 160 for (int i=0; i<ITERS; i++) 161 pool_free(pool, local[i]); 162 } 163 }; 164 165 long SharedPoolRun::threadNum; 166 utils::SpinBarrier SharedPoolRun::startB, 167 SharedPoolRun::mallocDone; 168 rml::MemoryPool *SharedPoolRun::pool; 169 void **SharedPoolRun::crossThread, 170 **SharedPoolRun::afterTerm; 171 172 // single pool shared by different threads 173 void TestSharedPool() 174 { 175 rml::MemPoolPolicy pol(getMallocMem, putMallocMem); 176 rml::MemoryPool *pool; 177 178 pool_create_v1(0, &pol, &pool); 179 void **crossThread = new void*[utils::MaxThread * SharedPoolRun::OBJ_CNT]; 180 void **afterTerm = new void*[utils::MaxThread * SharedPoolRun::OBJ_CNT]; 181 182 for (int p=utils::MinThread; p<=utils::MaxThread; p++) { 183 SharedPoolRun::init(p, pool, crossThread, afterTerm); 184 SharedPoolRun thr; 185 186 void *hugeObj = pool_malloc(pool, 10*1024*1024); 187 REQUIRE(hugeObj); 188 189 utils::NativeParallelFor( p, thr ); 190 191 pool_free(pool, hugeObj); 192 for (int i=0; i<p*SharedPoolRun::OBJ_CNT; i++) 193 pool_free(pool, afterTerm[i]); 194 } 195 delete []afterTerm; 196 delete []crossThread; 197 198 bool ok = pool_destroy(pool); 199 REQUIRE(ok); 200 REQUIRE_MESSAGE(!liveRegions.load(std::memory_order_relaxed), "Expected all regions were released."); 201 } 202 203 void *CrossThreadGetMem(intptr_t pool_id, size_t &bytes) 204 { 205 if (poolSpace[pool_id].pos + bytes > poolSpace[pool_id].bufSize) 206 return NULL; 207 208 void *ret = poolSpace[pool_id].space + poolSpace[pool_id].pos; 209 poolSpace[pool_id].pos += bytes; 210 poolSpace[pool_id].regions++; 211 212 return ret; 213 } 214 215 int CrossThreadPutMem(intptr_t pool_id, void* /*raw_ptr*/, size_t /*raw_bytes*/) 216 { 217 poolSpace[pool_id].regions--; 218 return 0; 219 } 220 221 class CrossThreadRun: utils::NoAssign { 222 static long number_of_threads; 223 static utils::SpinBarrier barrier; 224 static rml::MemoryPool **pool; 225 static char **obj; 226 public: 227 static void initBarrier(unsigned thrds) { barrier.initialize(thrds); } 228 static void init(long num) { 229 number_of_threads = num; 230 pool = new rml::MemoryPool*[number_of_threads]; 231 poolSpace = new PoolSpace[number_of_threads]; 232 obj = new char*[number_of_threads]; 233 } 234 static void destroy() { 235 for (long i=0; i<number_of_threads; i++) 236 REQUIRE_MESSAGE(!poolSpace[i].regions, "Memory leak detected"); 237 delete []pool; 238 delete []poolSpace; 239 delete []obj; 240 } 241 CrossThreadRun() {} 242 void operator()( int id ) const { 243 rml::MemPoolPolicy pol(CrossThreadGetMem, CrossThreadPutMem); 244 const int objLen = 10*id; 245 246 pool_create_v1(id, &pol, &pool[id]); 247 obj[id] = (char*)pool_malloc(pool[id], objLen); 248 REQUIRE(obj[id]); 249 memset(obj[id], id, objLen); 250 251 { 252 const size_t lrgSz = 2*16*1024; 253 void *ptrLarge = pool_malloc(pool[id], lrgSz); 254 REQUIRE(ptrLarge); 255 memset(ptrLarge, 1, lrgSz); 256 // consume all small objects 257 while (pool_malloc(pool[id], 5 * 1024)); 258 // releasing of large object will not give a chance to allocate more 259 // since only fixed pool can look at other bins aligned/notAligned 260 pool_free(pool[id], ptrLarge); 261 CHECK(!pool_malloc(pool[id], 5*1024)); 262 } 263 264 barrier.wait(); 265 int myPool = number_of_threads-id-1; 266 for (int i=0; i<10*myPool; i++) 267 REQUIRE(myPool==obj[myPool][i]); 268 pool_free(pool[myPool], obj[myPool]); 269 bool ok = pool_destroy(pool[myPool]); 270 REQUIRE(ok); 271 } 272 }; 273 274 long CrossThreadRun::number_of_threads; 275 utils::SpinBarrier CrossThreadRun::barrier; 276 rml::MemoryPool **CrossThreadRun::pool; 277 char **CrossThreadRun::obj; 278 279 // pools created, used and destroyed by different threads 280 void TestCrossThreadPools() 281 { 282 for (int p=utils::MinThread; p<=utils::MaxThread; p++) { 283 CrossThreadRun::initBarrier(p); 284 CrossThreadRun::init(p); 285 utils::NativeParallelFor( p, CrossThreadRun() ); 286 for (int i=0; i<p; i++) 287 REQUIRE_MESSAGE(!poolSpace[i].regions, "Region leak detected"); 288 CrossThreadRun::destroy(); 289 } 290 } 291 292 // buffer is too small to pool be created, but must not leak resources 293 void TestTooSmallBuffer() 294 { 295 poolSpace = new PoolSpace(8*1024); 296 297 rml::MemPoolPolicy pol(CrossThreadGetMem, CrossThreadPutMem); 298 rml::MemoryPool *pool; 299 pool_create_v1(0, &pol, &pool); 300 bool ok = pool_destroy(pool); 301 REQUIRE(ok); 302 REQUIRE_MESSAGE(!poolSpace[0].regions, "No leaks."); 303 304 delete poolSpace; 305 } 306 307 class FixedPoolHeadBase : utils::NoAssign { 308 size_t size; 309 std::atomic<bool> used; 310 char* data; 311 public: 312 FixedPoolHeadBase(size_t s) : size(s), used(false) { 313 data = new char[size]; 314 } 315 void *useData(size_t &bytes) { 316 bool wasUsed = used.exchange(true); 317 REQUIRE_MESSAGE(!wasUsed, "The buffer must not be used twice."); 318 bytes = size; 319 return data; 320 } 321 ~FixedPoolHeadBase() { 322 delete []data; 323 } 324 }; 325 326 template<size_t SIZE> 327 class FixedPoolHead : FixedPoolHeadBase { 328 public: 329 FixedPoolHead() : FixedPoolHeadBase(SIZE) { } 330 }; 331 332 static void *fixedBufGetMem(intptr_t pool_id, size_t &bytes) 333 { 334 return ((FixedPoolHeadBase*)pool_id)->useData(bytes); 335 } 336 337 class FixedPoolUse: utils::NoAssign { 338 static utils::SpinBarrier startB; 339 rml::MemoryPool *pool; 340 size_t reqSize; 341 int iters; 342 public: 343 FixedPoolUse(unsigned threads, rml::MemoryPool *p, size_t sz, int it) : 344 pool(p), reqSize(sz), iters(it) { 345 startB.initialize(threads); 346 } 347 void operator()( int /*id*/ ) const { 348 startB.wait(); 349 for (int i=0; i<iters; i++) { 350 void *o = pool_malloc(pool, reqSize); 351 ASSERT(o, "Invalid object"); 352 pool_free(pool, o); 353 } 354 } 355 }; 356 357 utils::SpinBarrier FixedPoolUse::startB; 358 359 class FixedPoolNomem: utils::NoAssign { 360 utils::SpinBarrier *startB; 361 rml::MemoryPool *pool; 362 public: 363 FixedPoolNomem(utils::SpinBarrier *b, rml::MemoryPool *p) : 364 startB(b), pool(p) {} 365 void operator()(int id) const { 366 startB->wait(); 367 void *o = pool_malloc(pool, id%2? 64 : 128*1024); 368 ASSERT(!o, "All memory must be consumed."); 369 } 370 }; 371 372 class FixedPoolSomeMem: utils::NoAssign { 373 utils::SpinBarrier *barrier; 374 rml::MemoryPool *pool; 375 public: 376 FixedPoolSomeMem(utils::SpinBarrier *b, rml::MemoryPool *p) : 377 barrier(b), pool(p) {} 378 void operator()(int id) const { 379 barrier->wait(); 380 utils::Sleep(2*id); 381 void *o = pool_malloc(pool, id%2? 64 : 128*1024); 382 barrier->wait(); 383 pool_free(pool, o); 384 } 385 }; 386 387 bool haveEnoughSpace(rml::MemoryPool *pool, size_t sz) 388 { 389 if (void *p = pool_malloc(pool, sz)) { 390 pool_free(pool, p); 391 return true; 392 } 393 return false; 394 } 395 396 void TestFixedBufferPool() 397 { 398 const int ITERS = 7; 399 const size_t MAX_OBJECT = 7*1024*1024; 400 void *ptrs[ITERS]; 401 rml::MemPoolPolicy pol(fixedBufGetMem, NULL, 0, /*fixedSizePool=*/true, 402 /*keepMemTillDestroy=*/false); 403 rml::MemoryPool *pool; 404 { 405 FixedPoolHead<MAX_OBJECT + 1024*1024> head; 406 407 pool_create_v1((intptr_t)&head, &pol, &pool); 408 { 409 utils::NativeParallelFor( 1, FixedPoolUse(1, pool, MAX_OBJECT, 2) ); 410 411 for (int i=0; i<ITERS; i++) { 412 ptrs[i] = pool_malloc(pool, MAX_OBJECT/ITERS); 413 REQUIRE(ptrs[i]); 414 } 415 for (int i=0; i<ITERS; i++) 416 pool_free(pool, ptrs[i]); 417 418 utils::NativeParallelFor( 1, FixedPoolUse(1, pool, MAX_OBJECT, 1) ); 419 } 420 // each thread asks for an MAX_OBJECT/p/2 object, 421 // /2 is to cover fragmentation 422 for (int p=utils::MinThread; p<=utils::MaxThread; p++) { 423 utils::NativeParallelFor( p, FixedPoolUse(p, pool, MAX_OBJECT/p/2, 10000) ); 424 } 425 { 426 const int p = 128; 427 utils::NativeParallelFor( p, FixedPoolUse(p, pool, MAX_OBJECT/p/2, 1) ); 428 } 429 { 430 size_t maxSz; 431 const int p = 256; 432 utils::SpinBarrier barrier(p); 433 434 // Find maximal useful object size. Start with MAX_OBJECT/2, 435 // as the pool might be fragmented by BootStrapBlocks consumed during 436 // FixedPoolRun. 437 size_t l, r; 438 REQUIRE(haveEnoughSpace(pool, MAX_OBJECT/2)); 439 for (l = MAX_OBJECT/2, r = MAX_OBJECT + 1024*1024; l < r-1; ) { 440 size_t mid = (l+r)/2; 441 if (haveEnoughSpace(pool, mid)) 442 l = mid; 443 else 444 r = mid; 445 } 446 maxSz = l; 447 REQUIRE_MESSAGE(!haveEnoughSpace(pool, maxSz+1), "Expect to find boundary value."); 448 // consume all available memory 449 void *largeObj = pool_malloc(pool, maxSz); 450 REQUIRE(largeObj); 451 void *o = pool_malloc(pool, 64); 452 if (o) // pool fragmented, skip FixedPoolNomem 453 pool_free(pool, o); 454 else 455 utils::NativeParallelFor( p, FixedPoolNomem(&barrier, pool) ); 456 pool_free(pool, largeObj); 457 // keep some space unoccupied 458 largeObj = pool_malloc(pool, maxSz-512*1024); 459 REQUIRE(largeObj); 460 utils::NativeParallelFor( p, FixedPoolSomeMem(&barrier, pool) ); 461 pool_free(pool, largeObj); 462 } 463 bool ok = pool_destroy(pool); 464 REQUIRE(ok); 465 } 466 // check that fresh untouched pool can successfully fulfil requests from 128 threads 467 { 468 FixedPoolHead<MAX_OBJECT + 1024*1024> head; 469 pool_create_v1((intptr_t)&head, &pol, &pool); 470 int p=128; 471 utils::NativeParallelFor( p, FixedPoolUse(p, pool, MAX_OBJECT/p/2, 1) ); 472 bool ok = pool_destroy(pool); 473 REQUIRE(ok); 474 } 475 } 476 477 static size_t currGranularity; 478 479 static void *getGranMem(intptr_t /*pool_id*/, size_t &bytes) 480 { 481 REQUIRE_MESSAGE(!(bytes%currGranularity), "Region size mismatch granularity."); 482 return malloc(bytes); 483 } 484 485 static int putGranMem(intptr_t /*pool_id*/, void *ptr, size_t bytes) 486 { 487 REQUIRE_MESSAGE(!(bytes%currGranularity), "Region size mismatch granularity."); 488 free(ptr); 489 return 0; 490 } 491 492 void TestPoolGranularity() 493 { 494 rml::MemPoolPolicy pol(getGranMem, putGranMem); 495 const size_t grans[] = {4*1024, 2*1024*1024, 6*1024*1024, 10*1024*1024}; 496 497 for (unsigned i=0; i<sizeof(grans)/sizeof(grans[0]); i++) { 498 pol.granularity = currGranularity = grans[i]; 499 rml::MemoryPool *pool; 500 501 pool_create_v1(0, &pol, &pool); 502 for (int sz=500*1024; sz<16*1024*1024; sz+=101*1024) { 503 void *p = pool_malloc(pool, sz); 504 REQUIRE_MESSAGE(p, "Can't allocate memory in pool."); 505 pool_free(pool, p); 506 } 507 bool ok = pool_destroy(pool); 508 REQUIRE(ok); 509 } 510 } 511 512 static size_t putMemAll, getMemAll, getMemSuccessful; 513 514 static void *getMemMalloc(intptr_t /*pool_id*/, size_t &bytes) 515 { 516 getMemAll++; 517 void *p = malloc(bytes); 518 if (p) 519 getMemSuccessful++; 520 return p; 521 } 522 523 static int putMemFree(intptr_t /*pool_id*/, void *ptr, size_t /*bytes*/) 524 { 525 putMemAll++; 526 free(ptr); 527 return 0; 528 } 529 530 void TestPoolKeepTillDestroy() 531 { 532 const int ITERS = 50*1024; 533 void *ptrs[2*ITERS+1]; 534 rml::MemPoolPolicy pol(getMemMalloc, putMemFree); 535 rml::MemoryPool *pool; 536 537 // 1st create default pool that returns memory back to callback, 538 // then use keepMemTillDestroy policy 539 for (int keep=0; keep<2; keep++) { 540 getMemAll = putMemAll = 0; 541 if (keep) 542 pol.keepAllMemory = 1; 543 pool_create_v1(0, &pol, &pool); 544 for (int i=0; i<2*ITERS; i+=2) { 545 ptrs[i] = pool_malloc(pool, 7*1024); 546 ptrs[i+1] = pool_malloc(pool, 10*1024); 547 } 548 ptrs[2*ITERS] = pool_malloc(pool, 8*1024*1024); 549 REQUIRE(!putMemAll); 550 for (int i=0; i<2*ITERS; i++) 551 pool_free(pool, ptrs[i]); 552 pool_free(pool, ptrs[2*ITERS]); 553 size_t totalPutMemCalls = putMemAll; 554 if (keep) 555 REQUIRE(!putMemAll); 556 else { 557 REQUIRE(putMemAll); 558 putMemAll = 0; 559 } 560 size_t getCallsBefore = getMemAll; 561 void *p = pool_malloc(pool, 8*1024*1024); 562 REQUIRE(p); 563 if (keep) 564 REQUIRE_MESSAGE(getCallsBefore == getMemAll, "Must not lead to new getMem call"); 565 size_t putCallsBefore = putMemAll; 566 bool ok = pool_reset(pool); 567 REQUIRE(ok); 568 REQUIRE_MESSAGE(putCallsBefore == putMemAll, "Pool is not releasing memory during reset."); 569 ok = pool_destroy(pool); 570 REQUIRE(ok); 571 REQUIRE(putMemAll); 572 totalPutMemCalls += putMemAll; 573 REQUIRE_MESSAGE(getMemAll == totalPutMemCalls, "Memory leak detected."); 574 } 575 576 } 577 578 static bool memEqual(char *buf, size_t size, int val) 579 { 580 bool memEq = true; 581 for (size_t k=0; k<size; k++) 582 if (buf[k] != val) 583 memEq = false; 584 return memEq; 585 } 586 587 void TestEntries() 588 { 589 const int SZ = 4; 590 const int ALGN = 4; 591 size_t size[SZ] = {8, 8000, 9000, 100*1024}; 592 size_t algn[ALGN] = {8, 64, 4*1024, 8*1024*1024}; 593 594 rml::MemPoolPolicy pol(getGranMem, putGranMem); 595 currGranularity = 1; // not check granularity in the test 596 rml::MemoryPool *pool; 597 598 pool_create_v1(0, &pol, &pool); 599 for (int i=0; i<SZ; i++) 600 for (int j=0; j<ALGN; j++) { 601 char *p = (char*)pool_aligned_malloc(pool, size[i], algn[j]); 602 REQUIRE((p && 0==((uintptr_t)p & (algn[j]-1)))); 603 memset(p, j, size[i]); 604 605 size_t curr_algn = algn[rand() % ALGN]; 606 size_t curr_sz = size[rand() % SZ]; 607 char *p1 = (char*)pool_aligned_realloc(pool, p, curr_sz, curr_algn); 608 REQUIRE((p1 && 0==((uintptr_t)p1 & (curr_algn-1)))); 609 REQUIRE(memEqual(p1, utils::min(size[i], curr_sz), j)); 610 611 memset(p1, j+1, curr_sz); 612 size_t curr_sz1 = size[rand() % SZ]; 613 char *p2 = (char*)pool_realloc(pool, p1, curr_sz1); 614 REQUIRE(p2); 615 REQUIRE(memEqual(p2, utils::min(curr_sz1, curr_sz), j+1)); 616 617 pool_free(pool, p2); 618 } 619 620 bool ok = pool_destroy(pool); 621 REQUIRE(ok); 622 623 bool fail = rml::pool_destroy(NULL); 624 REQUIRE(!fail); 625 fail = rml::pool_reset(NULL); 626 REQUIRE(!fail); 627 } 628 629 rml::MemoryPool *CreateUsablePool(size_t size) 630 { 631 rml::MemoryPool *pool; 632 rml::MemPoolPolicy okPolicy(getMemMalloc, putMemFree); 633 634 putMemAll = getMemAll = getMemSuccessful = 0; 635 rml::MemPoolError res = pool_create_v1(0, &okPolicy, &pool); 636 if (res != rml::POOL_OK) { 637 REQUIRE_MESSAGE((!getMemAll && !putMemAll), "No callbacks after fail."); 638 return NULL; 639 } 640 void *o = pool_malloc(pool, size); 641 if (!getMemSuccessful) { 642 // no memory from callback, valid reason to leave 643 REQUIRE_MESSAGE(!o, "The pool must be unusable."); 644 return NULL; 645 } 646 REQUIRE_MESSAGE(o, "Created pool must be useful."); 647 REQUIRE_MESSAGE((getMemSuccessful == 1 || getMemSuccessful == 5 || getMemAll > getMemSuccessful), 648 "Multiple requests are allowed when unsuccessful request occurred or cannot search in bootstrap memory. "); 649 REQUIRE(!putMemAll); 650 pool_free(pool, o); 651 652 return pool; 653 } 654 655 void CheckPoolLeaks(size_t poolsAlwaysAvailable) 656 { 657 const size_t MAX_POOLS = 16*1000; 658 const int ITERS = 20, CREATED_STABLE = 3; 659 rml::MemoryPool *pools[MAX_POOLS]; 660 size_t created, maxCreated = MAX_POOLS; 661 int maxNotChangedCnt = 0; 662 663 // expecting that for ITERS runs, max number of pools that can be created 664 // can be stabilized and still stable CREATED_STABLE times 665 for (int j=0; j<ITERS && maxNotChangedCnt<CREATED_STABLE; j++) { 666 for (created=0; created<maxCreated; created++) { 667 rml::MemoryPool *p = CreateUsablePool(1024); 668 if (!p) 669 break; 670 pools[created] = p; 671 } 672 REQUIRE_MESSAGE(created>=poolsAlwaysAvailable, 673 "Expect that the reasonable number of pools can be always created."); 674 for (size_t i=0; i<created; i++) { 675 bool ok = pool_destroy(pools[i]); 676 REQUIRE(ok); 677 } 678 if (created < maxCreated) { 679 maxCreated = created; 680 maxNotChangedCnt = 0; 681 } else 682 maxNotChangedCnt++; 683 } 684 REQUIRE_MESSAGE(maxNotChangedCnt == CREATED_STABLE, "The number of created pools must be stabilized."); 685 } 686 687 void TestPoolCreation() 688 { 689 putMemAll = getMemAll = getMemSuccessful = 0; 690 691 rml::MemPoolPolicy nullPolicy(NULL, putMemFree), 692 emptyFreePolicy(getMemMalloc, NULL), 693 okPolicy(getMemMalloc, putMemFree); 694 rml::MemoryPool *pool; 695 696 rml::MemPoolError res = pool_create_v1(0, &nullPolicy, &pool); 697 REQUIRE_MESSAGE(res==rml::INVALID_POLICY, "pool with empty pAlloc can't be created"); 698 res = pool_create_v1(0, &emptyFreePolicy, &pool); 699 REQUIRE_MESSAGE(res==rml::INVALID_POLICY, "pool with empty pFree can't be created"); 700 REQUIRE_MESSAGE((!putMemAll && !getMemAll), "no callback calls are expected"); 701 res = pool_create_v1(0, &okPolicy, &pool); 702 REQUIRE(res==rml::POOL_OK); 703 bool ok = pool_destroy(pool); 704 REQUIRE(ok); 705 REQUIRE_MESSAGE(putMemAll == getMemSuccessful, "no leaks after pool_destroy"); 706 707 // 32 is a guess for a number of pools that is acceptable everywere 708 CheckPoolLeaks(32); 709 // try to consume all but 16 TLS keys 710 LimitTLSKeysTo limitTLSTo(16); 711 // ...and check that we can create at least 16 pools 712 CheckPoolLeaks(16); 713 } 714 715 struct AllocatedObject { 716 rml::MemoryPool *pool; 717 }; 718 719 const size_t BUF_SIZE = 1024*1024; 720 721 class PoolIdentityCheck : utils::NoAssign { 722 rml::MemoryPool** const pools; 723 AllocatedObject** const objs; 724 public: 725 PoolIdentityCheck(rml::MemoryPool** p, AllocatedObject** o) : pools(p), objs(o) {} 726 void operator()(int id) const { 727 objs[id] = (AllocatedObject*)pool_malloc(pools[id], BUF_SIZE/2); 728 REQUIRE(objs[id]); 729 rml::MemoryPool *act_pool = rml::pool_identify(objs[id]); 730 REQUIRE(act_pool == pools[id]); 731 732 for (size_t total=0; total<2*BUF_SIZE; total+=256) { 733 AllocatedObject *o = (AllocatedObject*)pool_malloc(pools[id], 256); 734 REQUIRE(o); 735 act_pool = rml::pool_identify(o); 736 REQUIRE(act_pool == pools[id]); 737 pool_free(act_pool, o); 738 } 739 if( id&1 ) { // make every second returned object "small" 740 pool_free(act_pool, objs[id]); 741 objs[id] = (AllocatedObject*)pool_malloc(pools[id], 16); 742 REQUIRE(objs[id]); 743 } 744 objs[id]->pool = act_pool; 745 } 746 }; 747 748 void TestPoolDetection() 749 { 750 const int POOLS = 4; 751 rml::MemPoolPolicy pol(fixedBufGetMem, NULL, 0, /*fixedSizePool=*/true, 752 /*keepMemTillDestroy=*/false); 753 rml::MemoryPool *pools[POOLS]; 754 FixedPoolHead<BUF_SIZE*POOLS> head[POOLS]; 755 AllocatedObject *objs[POOLS]; 756 757 for (int i=0; i<POOLS; i++) 758 pool_create_v1((intptr_t)(head+i), &pol, &pools[i]); 759 // if object somehow released to different pools, subsequent allocation 760 // from affected pools became impossible 761 for (int k=0; k<10; k++) { 762 PoolIdentityCheck check(pools, objs); 763 if( k&1 ) 764 utils::NativeParallelFor( POOLS, check); 765 else 766 for (int i=0; i<POOLS; i++) check(i); 767 768 for (int i=0; i<POOLS; i++) { 769 rml::MemoryPool *p = rml::pool_identify(objs[i]); 770 REQUIRE(p == objs[i]->pool); 771 pool_free(p, objs[i]); 772 } 773 } 774 for (int i=0; i<POOLS; i++) { 775 bool ok = pool_destroy(pools[i]); 776 REQUIRE(ok); 777 } 778 } 779 780 void TestLazyBootstrap() 781 { 782 rml::MemPoolPolicy pol(getMemMalloc, putMemFree); 783 const size_t sizes[] = {8, 9*1024, 0}; 784 785 for (int i=0; sizes[i]; i++) { 786 rml::MemoryPool *pool = CreateUsablePool(sizes[i]); 787 bool ok = pool_destroy(pool); 788 REQUIRE(ok); 789 REQUIRE_MESSAGE(getMemSuccessful == putMemAll, "No leak."); 790 } 791 } 792 793 class NoLeakOnDestroyRun: utils::NoAssign { 794 rml::MemoryPool *pool; 795 utils::SpinBarrier *barrier; 796 public: 797 NoLeakOnDestroyRun(rml::MemoryPool *p, utils::SpinBarrier *b) : pool(p), barrier(b) {} 798 void operator()(int id) const { 799 void *p = pool_malloc(pool, id%2? 8 : 9000); 800 REQUIRE((p && liveRegions.load(std::memory_order_relaxed))); 801 barrier->wait(); 802 if (!id) { 803 bool ok = pool_destroy(pool); 804 REQUIRE(ok); 805 REQUIRE_MESSAGE(!liveRegions.load(std::memory_order_relaxed), "Expected all regions were released."); 806 } 807 // other threads must wait till pool destruction, 808 // to not call thread destruction cleanup before this 809 barrier->wait(); 810 } 811 }; 812 813 void TestNoLeakOnDestroy() 814 { 815 liveRegions.store(0, std::memory_order_release); 816 for (int p=utils::MinThread; p<=utils::MaxThread; p++) { 817 rml::MemPoolPolicy pol(getMallocMem, putMallocMem); 818 utils::SpinBarrier barrier(p); 819 rml::MemoryPool *pool; 820 821 pool_create_v1(0, &pol, &pool); 822 utils::NativeParallelFor(p, NoLeakOnDestroyRun(pool, &barrier)); 823 } 824 } 825 826 static int putMallocMemError(intptr_t /*pool_id*/, void *ptr, size_t bytes) 827 { 828 MallocPoolHeader *hdr = (MallocPoolHeader*)ptr-1; 829 REQUIRE_MESSAGE(bytes == hdr->userSize, "Invalid size in pool callback."); 830 free(hdr->rawPtr); 831 832 liveRegions--; 833 834 return -1; 835 } 836 837 void TestDestroyFailed() 838 { 839 rml::MemPoolPolicy pol(getMallocMem, putMallocMemError); 840 rml::MemoryPool *pool; 841 pool_create_v1(0, &pol, &pool); 842 void *ptr = pool_malloc(pool, 16); 843 REQUIRE(ptr); 844 bool fail = pool_destroy(pool); 845 REQUIRE_MESSAGE(fail==false, "putMemPolicyError callback returns error, " 846 "expect pool_destroy() failure"); 847 } 848 849 void TestPoolMSize() { 850 rml::MemoryPool *pool = CreateUsablePool(1024); 851 852 const int SZ = 10; 853 // Original allocation requests, random numbers from small to large 854 size_t requestedSz[SZ] = {8, 16, 500, 1000, 2000, 4000, 8000, 1024*1024, 4242+4242, 8484+8484}; 855 856 // Unlike large objects, small objects do not store its original size along with the object itself 857 // On Power architecture TLS bins are divided differently. 858 size_t allocatedSz[SZ] = 859 #if __powerpc64__ || __ppc64__ || __bgp__ 860 {8, 16, 512, 1024, 2688, 5376, 8064, 1024*1024, 4242+4242, 8484+8484}; 861 #else 862 {8, 16, 512, 1024, 2688, 4032, 8128, 1024*1024, 4242+4242, 8484+8484}; 863 #endif 864 for (int i = 0; i < SZ; i++) { 865 void* obj = pool_malloc(pool, requestedSz[i]); 866 size_t objSize = pool_msize(pool, obj); 867 REQUIRE_MESSAGE(objSize == allocatedSz[i], "pool_msize returned the wrong value"); 868 pool_free(pool, obj); 869 } 870 bool destroyed = pool_destroy(pool); 871 REQUIRE(destroyed); 872 } 873 874 //! \brief \ref error_guessing 875 TEST_CASE("Too small buffer") { 876 TestTooSmallBuffer(); 877 } 878 879 //! \brief \ref error_guessing 880 TEST_CASE("Pool reset") { 881 TestPoolReset(); 882 } 883 TEST_CASE("Shared pool") { 884 TestSharedPool(); 885 } 886 887 //! \brief \ref error_guessing 888 TEST_CASE("Cross thread pools") { 889 TestCrossThreadPools(); 890 } 891 892 //! \brief \ref interface 893 TEST_CASE("Fixed buffer pool") { 894 TestFixedBufferPool(); 895 } 896 897 //! \brief \ref interface 898 TEST_CASE("Pool granularity") { 899 TestPoolGranularity(); 900 } 901 902 //! \brief \ref error_guessing 903 TEST_CASE("Keep pool till destroy") { 904 TestPoolKeepTillDestroy(); 905 } 906 907 //! \brief \ref error_guessing 908 TEST_CASE("Entries") { 909 TestEntries(); 910 } 911 912 //! \brief \ref interface 913 TEST_CASE("Pool creation") { 914 TestPoolCreation(); 915 } 916 917 //! \brief \ref error_guessing 918 TEST_CASE("Pool detection") { 919 TestPoolDetection(); 920 } 921 922 //! \brief \ref error_guessing 923 TEST_CASE("Lazy bootstrap") { 924 TestLazyBootstrap(); 925 } 926 927 //! \brief \ref error_guessing 928 TEST_CASE("No leak on destroy") { 929 TestNoLeakOnDestroy(); 930 } 931 932 //! \brief \ref error_guessing 933 TEST_CASE("Destroy failed") { 934 TestDestroyFailed(); 935 } 936 937 //! \brief \ref interface 938 TEST_CASE("Pool msize") { 939 TestPoolMSize(); 940 } 941