hazard_pointer_test.cc 16.5 KB
Newer Older
1
/*
2
 * Copyright (c) 2014-2015, Siemens AG. All rights reserved.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "./hazard_pointer_test.h"

29 30
#include <embb/base/internal/config.h>

31 32 33
namespace embb {
namespace containers {
namespace test {
34
IntObjectTestPool::IntObjectTestPool(unsigned int pool_size) :
35
poolSize(pool_size) {
36
  simplePoolObjects = static_cast<int*>(
37
    embb::base::Allocation::Allocate(sizeof(int)*pool_size));
38 39 40

  simplePool = static_cast<embb::base::Atomic<int>*> (
    embb::base::Allocation::Allocate(sizeof(embb::base::Atomic<int>)*
41
    pool_size));
42

43
  for (unsigned int i = 0; i != pool_size; ++i) {
44
    // in-place new for each array cell
45 46 47
    new (&simplePool[i]) embb::base::Atomic<int>;
  }

48
  for (unsigned int i = 0; i != pool_size; ++i) {
49 50 51 52 53 54 55 56 57
    simplePool[i] = FREE_MARKER;
    simplePoolObjects[i] = 0;
  }
}

IntObjectTestPool::~IntObjectTestPool() {
  embb::base::Allocation::Free(simplePoolObjects);

  for (unsigned int i = 0; i != poolSize; ++i) {
58
    // in-place new for each array cell
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
    simplePool[i].~Atomic();
  }

  embb::base::Allocation::Free(simplePool);
}

int* IntObjectTestPool::Allocate() {
  for (unsigned int i = 0; i != poolSize; ++i) {
    int expected = FREE_MARKER;
    if (simplePool[i].CompareAndSwap
      (expected, ALLOCATED_MARKER)) {
      return &simplePoolObjects[i];
    }
  }
  return 0;
}

76 77
void IntObjectTestPool::Release(int* object_pointer) {
  int cell = object_pointer - simplePoolObjects;
78 79 80
  simplePool[cell].Store(FREE_MARKER);
}

81
HazardPointerTest::HazardPointerTest() :
82
#ifdef EMBB_PLATFORM_COMPILER_MSVC
83 84 85
#pragma warning(push)
#pragma warning(disable:4355)
#endif
86
delete_pointer_callback_(*this, &HazardPointerTest::DeletePointerCallback),
87
#ifdef EMBB_PLATFORM_COMPILER_MSVC
88 89
#pragma warning(pop)
#endif
90 91 92 93
  object_pool_(NULL),
  stack_(NULL),
  hazard_pointer_(NULL),
  n_threads_(static_cast<int>
94
  (partest::TestSuite::GetDefaultNumThreads())) {
95 96
  n_elements_per_thread_ = 100;
  n_elements_ = n_threads_*n_elements_per_thread_;
97
  embb::base::Function < void, embb::base::Atomic<int>* >
98
    deletePointerCallback(
99 100 101 102 103 104 105 106 107 108
    *this,
    &HazardPointerTest::DeletePointerCallback);

  // Kind of timing depending test. But tests exactly what hazard pointers are
  // designed for. One thread creates an element, does something, retires it.
  // Another thread also accesses this element (passed using a stack), by
  // placing a guard it protects this element. If the guard was successfully
  // placed, the pointer is not allowed to be deleted until the second thread
  // removes this guard.
  CreateUnit("HazardPointerTestThatGuardWorks").
109
    Pre(&HazardPointerTest::HazardPointerTest1Pre, this).
110
    Add(
111
    &HazardPointerTest::HazardPointerTest1ThreadMethod,
112
    this, static_cast<size_t>(n_threads_)).
113
    Post(&HazardPointerTest::HazardPointerTest1Post, this);
114 115
}

116
void HazardPointerTest::HazardPointerTest1Pre() {
117
  embb_internal_thread_index_reset();
118

119
  object_pool_ =
120 121
     embb::base::Allocation::
     New<embb::containers::ObjectPool< embb::base::Atomic<int> > >
122
     (static_cast<size_t>(n_elements_));
123

124
  stack_ = embb::base::Allocation::
125
    New<embb::containers::LockFreeStack< embb::base::Atomic<int>* > >
126
    (static_cast<size_t>(n_elements_));
127

128
  hazard_pointer_ = embb::base::Allocation::
129
    New<embb::containers::internal::HazardPointer < embb::base::Atomic<int>* > >
130
    (delete_pointer_callback_,
131
    static_cast<embb::base::Atomic<int>*>(NULL),
132 133 134
    1);
}

135
void HazardPointerTest::HazardPointerTest1Post() {
136 137 138
  embb::base::Allocation::Delete(hazard_pointer_);
  embb::base::Allocation::Delete(object_pool_);
  embb::base::Allocation::Delete(stack_);
139 140
}

141
void HazardPointerTest::HazardPointerTest1ThreadMethod() {
142 143 144
  unsigned int thread_index;
  embb_internal_thread_index(&thread_index);

145 146
  for (int i = 0; i != n_elements_per_thread_; ++i) {
    embb::base::Atomic<int>* allocated_object = object_pool_->Allocate(0);
147

148
    hazard_pointer_->Guard(0, allocated_object);
149

150
    bool success = stack_->TryPush(allocated_object);
151 152 153

    PT_ASSERT(success == true);

154
    embb::base::Atomic<int>* allocated_object_from_different_thread(0);
155 156 157 158 159 160 161

    int diff_count = 0;

    bool same = false;
    bool success_pop;

    while (
162
      (success_pop = stack_->TryPop(allocated_object_from_different_thread))
163 164 165
      == true
      && allocated_object_from_different_thread == allocated_object
      ) {
166 167
      // try to make it probable to get an element from a different thread
      // however, can be the same. Try 10000 times to get a different element.
168 169 170 171
      if (diff_count++ > 10000) {
        same = true;
        break;
      }
172
      bool success = stack_->TryPush(allocated_object_from_different_thread);
173 174 175 176
      PT_ASSERT(success == true);
    }
    PT_ASSERT(success_pop == true);
    allocated_object->Store(1);
177
    hazard_pointer_->EnqueueForDeletion(allocated_object);
178 179

    if (!same) {
180
      hazard_pointer_->Guard(0, allocated_object_from_different_thread);
181 182 183 184 185 186

      // if this holds, we were successful in guarding... otherwise we
      // were to late, because the pointer has already been added
      // to the retired list.
      if (*allocated_object_from_different_thread == 0) {
        // the pointer must not be deleted here!
187
        vector_mutex_.Lock();
188
        for (std::vector< embb::base::Atomic<int>* >::iterator
189 190
          it = deleted_vector_.begin();
          it != deleted_vector_.end();
191 192 193
        ++it) {
          PT_ASSERT(*it != allocated_object_from_different_thread);
        }
194
        vector_mutex_.Unlock();
195
      }
196
      hazard_pointer_->Guard(0, NULL);
197 198 199 200 201 202
    }
  }
}

void HazardPointerTest::DeletePointerCallback
(embb::base::Atomic<int>* to_delete) {
203 204 205
  vector_mutex_.Lock();
  deleted_vector_.push_back(to_delete);
  vector_mutex_.Unlock();
206 207
}

208 209
void HazardPointerTest2::DeletePointerCallback(int* to_delete) {
  test_pool_->Release(to_delete);
210 211 212
}

bool HazardPointerTest2::SetRelativeGuards() {
213 214
  unsigned int thread_index;
  embb_internal_thread_index(&thread_index);
215

216 217
  unsigned int my_begin = guards_per_phread_count_*thread_index;
  int guard_number = 0;
218 219
  unsigned int alreadyGuarded = 0;

220
  for (unsigned int i = my_begin; i != my_begin + guards_per_phread_count_;
221
    ++i) {
222
    if (shared_guarded_[i] != 0) {
223
      alreadyGuarded++;
224
      guard_number++;
225 226 227
      continue;
    }

228 229 230
    int * to_guard = shared_allocated_[i];
    if (to_guard) {
      hazard_pointer_->Guard(guard_number, to_guard);
231 232

      // changed in the meantime?
233
      if (to_guard == shared_allocated_[i].Load()) {
234
        // guard was successful. Communicate to other threads.
235
        shared_guarded_[i] = to_guard;
236
      } else {
237
        // reset the guard, couldn't guard...
238
        hazard_pointer_->RemoveGuard(guard_number);
239 240
      }
    }
241
    guard_number++;
242
  }
243
  return(alreadyGuarded == guards_per_phread_count_);
244 245 246 247 248
}

void HazardPointerTest2::HazardPointerTest2Master() {
  // while the hazard pointer guard array is not full
  int** allocatedLocal = static_cast<int**>(
249
  embb::base::Allocation::Allocate(sizeof(int*)*guaranteed_capacity_pool_));
250 251 252 253

  bool full = false;
  while (!full) {
    full = true;
254 255
    for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
      if (shared_guarded_[i] == 0) {
256 257 258 259 260 261
        full = false;
        break;
      }
    }

    // not all guards set
262 263 264
    for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
      allocatedLocal[i] = test_pool_->Allocate();
      shared_allocated_[i].Store(allocatedLocal[i]);
265 266 267 268 269 270 271
    }

    // set my hazards. We do not have to check, this must be successful
    // here.
    SetRelativeGuards();

    // free
272 273 274
    for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
      shared_allocated_[i].Store(0);
      hazard_pointer_->EnqueueForDeletion(allocatedLocal[i]);
275 276 277 278 279 280 281 282 283 284
    }
  }

  embb::base::Allocation::Free(allocatedLocal);
}

void HazardPointerTest2::HazardPointerTest2Slave() {
  unsigned int thread_index;
  embb_internal_thread_index(&thread_index);

285
  while (!SetRelativeGuards()) {}
286 287 288 289
}

void HazardPointerTest2::HazardPointerTest2Pre() {
  embb_internal_thread_index_reset();
290 291 292
  current_master_ = 0;
  sync1_ = 0;
  sync2_ = 0;
293 294

  // first the test pool has to be created
295 296
  test_pool_ = embb::base::Allocation::New<IntObjectTestPool>
    (pool_size_using_hazard_pointer_);
297 298

  // after the pool has been created, we create the hp class
299
  hazard_pointer_ = embb::base::Allocation::New <
300
    embb::containers::internal::HazardPointer<int*> >
301 302
    (delete_pointer_callback_, static_cast<int*>(NULL),
    static_cast<int>(guards_per_phread_count_), n_threads);
303

304
  shared_guarded_ = static_cast<embb::base::Atomic<int*>*>(
305
    embb::base::Allocation::Allocate(sizeof(embb::base::Atomic<int*>)*
306
    guaranteed_capacity_pool_));
307

308 309 310
  for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
    // in-place new for each array cell
    new (&shared_guarded_[i]) embb::base::Atomic < int* >;
311 312
  }

313
  shared_allocated_ = static_cast<embb::base::Atomic<int*>*>(
314
    embb::base::Allocation::Allocate(sizeof(embb::base::Atomic<int*>)*
315
    guaranteed_capacity_pool_));
316 317

  for (unsigned int i = 0; i !=
318
  guaranteed_capacity_pool_; ++i) {
319 320
    // in-place new for each array cell
    new (&shared_allocated_[i]) embb::base::Atomic < int* >;
321 322
  }

323 324 325
  for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
    shared_guarded_[i] = 0;
    shared_allocated_[i] = 0;
326 327 328 329
  }
}

void HazardPointerTest2::HazardPointerTest2Post() {
330 331 332 333 334
  for (unsigned int i = 0; i != static_cast<unsigned int>(n_threads); ++i) {
    for (unsigned int i2 = 0; i2 != static_cast<unsigned int>(n_threads)*
      guards_per_phread_count_; ++i2) {
      if (hazard_pointer_->thread_local_retired_lists_
        [i2 + i*n_threads*guards_per_phread_count_] == NULL) {
335 336 337 338 339 340 341
        // all retired lists must be completely filled
        PT_ASSERT(false);
      }
    }
  }

  unsigned int checks = 0;
342 343 344 345 346 347
  for (unsigned int i = 0; i != static_cast<unsigned int>(n_threads); ++i) {
    for (unsigned int i2 = 0; i2 != static_cast<unsigned int>(n_threads)*
      guards_per_phread_count_; ++i2) {
      for (unsigned int j = 0; j != static_cast<unsigned int>(n_threads); ++j) {
        for (unsigned int j2 = 0; j2 != static_cast<unsigned int>(n_threads)*
          guards_per_phread_count_; ++j2) {
348 349 350 351 352
          if (i2 == j2 && i == j)
            continue;

          // all retired elements have to be disjoint
          PT_ASSERT(
353 354 355
            hazard_pointer_->thread_local_retired_lists_
            [i2 + i*n_threads*guards_per_phread_count_] !=
            hazard_pointer_->thread_local_retired_lists_
356
            [j2 + j*n_threads*guards_per_phread_count_]);
357 358 359 360 361 362 363 364 365 366

          checks++;
        }
      }
    }
  }

  // sanity check on the count of expected comparisons.
  PT_ASSERT(
    checks ==
367
    n_threads*n_threads*guards_per_phread_count_ *
368
    (n_threads*n_threads*guards_per_phread_count_ - 1));
369 370 371 372 373

  std::vector< int* > additionallyAllocated;

  // we should be able to still allocate the guaranteed capacity of
  // elements from the pool.
374 375
  for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
    int* allocated = test_pool_->Allocate();
376 377 378 379 380 381 382 383 384 385

    // allocated is not allowed to be zero
    PT_ASSERT(allocated != NULL);

    // push to vector, to check if elements are disjunctive and to release
    // afterwards.
    additionallyAllocated.push_back(allocated);
  }

  // the pool should now be empty
386
  PT_ASSERT(test_pool_->Allocate() == NULL);
387 388 389

  // release allocated elements...
  for (unsigned int i = 0; i != additionallyAllocated.size(); ++i) {
390
    test_pool_->Release(additionallyAllocated[i]);
391 392 393 394 395 396 397 398 399 400 401 402 403 404
  }

  // the additionallyAllocated elements shall be disjoint
  for (unsigned int i = 0; i != additionallyAllocated.size(); ++i) {
    for (unsigned int i2 = 0; i2 != additionallyAllocated.size(); ++i2) {
      if (i == i2)
        continue;
      PT_ASSERT(additionallyAllocated[i] !=
        additionallyAllocated[i2]);
    }
  }

  // no allocated element should be in any retired list...
  for (unsigned int a = 0; a != additionallyAllocated.size(); ++a) {
405 406 407
    for (unsigned int i = 0; i != static_cast<unsigned int>(n_threads); ++i) {
      for (unsigned int i2 = 0; i2 != static_cast<unsigned int>(n_threads)*
        guards_per_phread_count_; ++i2) {
408
        PT_ASSERT(
409 410
          hazard_pointer_->thread_local_retired_lists_
          [i2 + i*n_threads*guards_per_phread_count_] !=
411
          additionallyAllocated[a]);
412 413 414 415
      }
    }
  }

416
  for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
417
    // in-place new for each array cell
418
    shared_guarded_[i].~Atomic();
419 420
  }

421
  embb::base::Allocation::Free(shared_guarded_);
422

423
  for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
424
    // in-place new for each array cell
425
    shared_allocated_[i].~Atomic();
426 427
  }

428 429
  embb::base::Allocation::Free(shared_allocated_);
  embb::base::Allocation::Delete(hazard_pointer_);
430 431 432 433 434 435

  // after deleting the hazard pointer object, all retired pointers have
  // to be returned to the pool!
  std::vector<int*> elementsInPool;

  int* nextElement;
436
  while ((nextElement = test_pool_->Allocate()) != NULL) {
437 438 439 440 441 442 443 444 445
    for (unsigned int i = 0; i != elementsInPool.size(); ++i) {
      // all elements need to be disjoint
      PT_ASSERT(elementsInPool[i] != nextElement);
    }
    elementsInPool.push_back(nextElement);
  }

  // all elements should have been returned by the hp object, so we should be
  // able to acquire all elements.
446
  PT_ASSERT(elementsInPool.size() == pool_size_using_hazard_pointer_);
447

448
  embb::base::Allocation::Delete(test_pool_);
449 450 451 452
}

void HazardPointerTest2::HazardPointerTest2ThreadMethod() {
  for (;;) {
453 454
    unsigned int thread_index;
    embb_internal_thread_index(&thread_index);
455

456
    if (thread_index == current_master_) {
457
      HazardPointerTest2Master();
458
    } else {
459 460 461
      HazardPointerTest2Slave();
    }

462
    sync1_.FetchAndAdd(1);
463 464

    // wait until cleanup thread signals to be finished
465 466 467
    while (sync1_ != 0) {
      int expected = n_threads;
      int desired = FINISH_MARKER;
468
      // select thread, responsible for cleanup
469
      if (sync1_.CompareAndSwap(expected, desired)) {
470
        // wipe arrays!
471 472 473
        for (unsigned int i = 0; i != guaranteed_capacity_pool_; ++i) {
          shared_guarded_[i] = 0;
          shared_allocated_[i] = 0;
474 475 476
        }

        // increase master
477 478 479
        current_master_.FetchAndAdd(1);
        sync2_ = 0;
        sync1_.Store(0);
480 481 482 483
      }
    }

    // wait for all threads to reach this position
484 485
    sync2_.FetchAndAdd(1);
    while (sync2_ != static_cast<unsigned int>(n_threads)) {}
486 487

    // if each thread was master once, terminate.
488
    if (current_master_ == static_cast<unsigned int>(n_threads)) {
489 490 491
      return;
    }
  }
492
}
493 494

HazardPointerTest2::HazardPointerTest2() :
495
n_threads(static_cast<int>
496 497 498 499 500 501
(partest::TestSuite::GetDefaultNumThreads())),

#ifdef EMBB_PLATFORM_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable:4355)
#endif
502 503 504
  delete_pointer_callback_(
  *this,
  &HazardPointerTest2::DeletePointerCallback)
505 506 507 508
#ifdef EMBB_PLATFORM_COMPILER_MSVC
#pragma warning(pop)
#endif
{
509 510 511 512
  guards_per_phread_count_ = 5;
  guaranteed_capacity_pool_ = guards_per_phread_count_*n_threads;
  pool_size_using_hazard_pointer_ = guaranteed_capacity_pool_ +
    guards_per_phread_count_*n_threads*n_threads;
513 514 515 516 517 518

  embb::base::Thread::GetThreadsMaxCount();
  CreateUnit("HazardPointerTestSimulateMemoryWorstCase").
    Pre(&HazardPointerTest2::HazardPointerTest2Pre, this).
    Add(
    &HazardPointerTest2::HazardPointerTest2ThreadMethod,
519
    this, static_cast<size_t>(n_threads)).
520 521
    Post(&HazardPointerTest2::HazardPointerTest2Post, this);
}
522 523 524
}  // namespace test
}  // namespace containers
}  // namespace embb