--- a/hotspot/src/share/vm/memory/metaspace.cpp Thu Mar 16 09:43:26 2017 +0000
+++ b/hotspot/src/share/vm/memory/metaspace.cpp Wed Dec 14 09:14:16 2016 +0100
@@ -74,6 +74,22 @@
NumberOfInUseLists = 4
};
+// Helper, returns a descriptive name for the given index.
+static const char* chunk_size_name(ChunkIndex index) {
+ switch (index) {
+ case SpecializedIndex:
+ return "specialized";
+ case SmallIndex:
+ return "small";
+ case MediumIndex:
+ return "medium";
+ case HumongousIndex:
+ return "humongous";
+ default:
+ return "Invalid index";
+ }
+}
+
enum ChunkSizes { // in words.
ClassSpecializedChunk = 128,
SpecializedChunk = 128,
@@ -102,20 +118,32 @@
// SpecializedChunk
// SmallChunk
// MediumChunk
- // HumongousChunk
ChunkList _free_chunks[NumberOfFreeLists];
+ // Return non-humongous chunk list by its index.
+ ChunkList* free_chunks(ChunkIndex index);
+
+ // Returns non-humongous chunk list for the given chunk word size.
+ ChunkList* find_free_chunks_list(size_t word_size);
+
// HumongousChunk
ChunkTreeDictionary _humongous_dictionary;
+ // Returns the humongous chunk dictionary.
+ ChunkTreeDictionary* humongous_dictionary() {
+ return &_humongous_dictionary;
+ }
+
// ChunkManager in all lists of this type
size_t _free_chunks_total;
size_t _free_chunks_count;
void dec_free_chunks_total(size_t v) {
- assert(_free_chunks_count > 0 &&
- _free_chunks_total > 0,
- "About to go negative");
+ assert(_free_chunks_count > 0,
+ "ChunkManager::_free_chunks_count: about to go negative (" SIZE_FORMAT ").", _free_chunks_count);
+ assert(_free_chunks_total >= v,
+ "ChunkManager::_free_chunks_total: about to go negative"
+ "(now: " SIZE_FORMAT ", decrement value: " SIZE_FORMAT ").", _free_chunks_total, v);
Atomic::add_ptr(-1, &_free_chunks_count);
jlong minus_v = (jlong) - (jlong) v;
Atomic::add_ptr(minus_v, &_free_chunks_total);
@@ -156,13 +184,20 @@
// for special, small, medium, and humongous chunks.
ChunkIndex list_index(size_t size);
- // Remove the chunk from its freelist. It is
- // expected to be on one of the _free_chunks[] lists.
+ // Map a given index to the chunk size.
+ size_t size_by_index(ChunkIndex index);
+
+ // Take a chunk from the ChunkManager. The chunk is expected to be in
+ // the chunk manager (the freelist if non-humongous, the dictionary if
+ // humongous).
void remove_chunk(Metachunk* chunk);
+ // Return a single chunk of type index to the ChunkManager.
+ void return_single_chunk(ChunkIndex index, Metachunk* chunk);
+
// Add the simple linked list of chunks to the freelist of chunks
// of type index.
- void return_chunks(ChunkIndex index, Metachunk* chunks);
+ void return_chunk_list(ChunkIndex index, Metachunk* chunk);
// Total of the space in the free chunks list
size_t free_chunks_total_words();
@@ -175,14 +210,6 @@
Atomic::add_ptr(count, &_free_chunks_count);
Atomic::add_ptr(v, &_free_chunks_total);
}
- ChunkTreeDictionary* humongous_dictionary() {
- return &_humongous_dictionary;
- }
-
- ChunkList* free_chunks(ChunkIndex index);
-
- // Returns the list for the given chunk word size.
- ChunkList* find_free_chunks_list(size_t word_size);
// Remove from a list by size. Selects list based on size of chunk.
Metachunk* free_chunks_get(size_t chunk_word_size);
@@ -735,8 +762,6 @@
Mutex* lock() const { return _lock; }
- const char* chunk_size_name(ChunkIndex index) const;
-
protected:
void initialize();
@@ -1246,13 +1271,13 @@
DEBUG_ONLY(verify_container_count();)
for (int i = (int)MediumIndex; i >= (int)ZeroIndex; --i) {
ChunkIndex index = (ChunkIndex)i;
- size_t chunk_size = chunk_manager->free_chunks(index)->size();
+ size_t chunk_size = chunk_manager->size_by_index(index);
while (free_words_in_vs() >= chunk_size) {
Metachunk* chunk = get_chunk_vs(chunk_size);
assert(chunk != NULL, "allocation should have been successful");
- chunk_manager->return_chunks(index, chunk);
+ chunk_manager->return_single_chunk(index, chunk);
chunk_manager->inc_free_chunks_total(chunk_size);
}
DEBUG_ONLY(verify_container_count();)
@@ -1743,6 +1768,28 @@
return _free_chunks_count;
}
+ChunkIndex ChunkManager::list_index(size_t size) {
+ if (size_by_index(SpecializedIndex) == size) {
+ return SpecializedIndex;
+ }
+ if (size_by_index(SmallIndex) == size) {
+ return SmallIndex;
+ }
+ const size_t med_size = size_by_index(MediumIndex);
+ if (med_size == size) {
+ return MediumIndex;
+ }
+
+ assert(size > med_size, "Not a humongous chunk");
+ return HumongousIndex;
+}
+
+size_t ChunkManager::size_by_index(ChunkIndex index) {
+ index_bounds_check(index);
+ assert(index != HumongousIndex, "Do not call for humongous chunks.");
+ return free_chunks(index)->size();
+}
+
void ChunkManager::locked_verify_free_chunks_total() {
assert_lock_strong(SpaceManager::expand_lock());
assert(sum_free_chunks() == _free_chunks_total,
@@ -1923,6 +1970,73 @@
return chunk;
}
+void ChunkManager::return_single_chunk(ChunkIndex index, Metachunk* chunk) {
+ assert_lock_strong(SpaceManager::expand_lock());
+ assert(chunk != NULL, "Expected chunk.");
+ assert(chunk->container() != NULL, "Container should have been set.");
+ assert(chunk->is_tagged_free() == false, "Chunk should be in use.");
+ index_bounds_check(index);
+
+ // Note: mangle *before* returning the chunk to the freelist or dictionary. It does not
+ // matter for the freelist (non-humongous chunks), but the humongous chunk dictionary
+ // keeps tree node pointers in the chunk payload area which mangle will overwrite.
+ NOT_PRODUCT(chunk->mangle(badMetaWordVal);)
+
+ if (index != HumongousIndex) {
+ // Return non-humongous chunk to freelist.
+ ChunkList* list = free_chunks(index);
+ assert(list->size() == chunk->word_size(), "Wrong chunk type.");
+ list->return_chunk_at_head(chunk);
+ log_trace(gc, metaspace, freelist)("returned one %s chunk at " PTR_FORMAT " to freelist.",
+ chunk_size_name(index), p2i(chunk));
+ } else {
+ // Return humongous chunk to dictionary.
+ assert(chunk->word_size() > free_chunks(MediumIndex)->size(), "Wrong chunk type.");
+ assert(chunk->word_size() % free_chunks(SpecializedIndex)->size() == 0,
+ "Humongous chunk has wrong alignment.");
+ _humongous_dictionary.return_chunk(chunk);
+ log_trace(gc, metaspace, freelist)("returned one %s chunk at " PTR_FORMAT " (word size " SIZE_FORMAT ") to freelist.",
+ chunk_size_name(index), p2i(chunk), chunk->word_size());
+ }
+ chunk->container()->dec_container_count();
+ DEBUG_ONLY(chunk->set_is_tagged_free(true);)
+
+}
+
+void ChunkManager::return_chunk_list(ChunkIndex index, Metachunk* chunks) {
+ index_bounds_check(index);
+ if (chunks == NULL) {
+ return;
+ }
+ LogTarget(Trace, gc, metaspace, freelist) log;
+ if (log.is_enabled()) { // tracing
+ log.print("returning list of %s chunks...", chunk_size_name(index));
+ }
+ unsigned num_chunks_returned = 0;
+ size_t size_chunks_returned = 0;
+ Metachunk* cur = chunks;
+ while (cur != NULL) {
+ // Capture the next link before it is changed
+ // by the call to return_chunk_at_head();
+ Metachunk* next = cur->next();
+ if (log.is_enabled()) { // tracing
+ num_chunks_returned ++;
+ size_chunks_returned += cur->word_size();
+ }
+ return_single_chunk(index, cur);
+ cur = next;
+ }
+ if (log.is_enabled()) { // tracing
+ log.print("returned %u %s chunks to freelist, total word size " SIZE_FORMAT ".",
+ num_chunks_returned, chunk_size_name(index), size_chunks_returned);
+ if (index != HumongousIndex) {
+ log.print("updated freelist count: " SIZE_FORMAT ".", free_chunks(index)->size());
+ } else {
+ log.print("updated dictionary count " SIZE_FORMAT ".", _humongous_dictionary.total_count());
+ }
+ }
+}
+
void ChunkManager::print_on(outputStream* out) const {
const_cast<ChunkManager *>(this)->humongous_dictionary()->report_statistics(out);
}
@@ -2256,32 +2370,6 @@
log_trace(gc, metaspace, freelist)("SpaceManager(): " PTR_FORMAT, p2i(this));
}
-void ChunkManager::return_chunks(ChunkIndex index, Metachunk* chunks) {
- if (chunks == NULL) {
- return;
- }
- ChunkList* list = free_chunks(index);
- assert(list->size() == chunks->word_size(), "Mismatch in chunk sizes");
- assert_lock_strong(SpaceManager::expand_lock());
- Metachunk* cur = chunks;
-
- // This returns chunks one at a time. If a new
- // class List can be created that is a base class
- // of FreeList then something like FreeList::prepend()
- // can be used in place of this loop
- while (cur != NULL) {
- assert(cur->container() != NULL, "Container should have been set");
- cur->container()->dec_container_count();
- // Capture the next link before it is changed
- // by the call to return_chunk_at_head();
- Metachunk* next = cur->next();
- DEBUG_ONLY(cur->set_is_tagged_free(true);)
- NOT_PRODUCT(cur->mangle(badMetaWordVal);)
- list->return_chunk_at_head(cur);
- cur = next;
- }
-}
-
SpaceManager::~SpaceManager() {
// This call this->_lock which can't be done while holding expand_lock()
assert(sum_capacity_in_chunks_in_use() == allocated_chunks_words(),
@@ -2317,42 +2405,12 @@
// Follow each list of chunks-in-use and add them to the
// free lists. Each list is NULL terminated.
- for (ChunkIndex i = ZeroIndex; i < HumongousIndex; i = next_chunk_index(i)) {
- log.trace("returned " SIZE_FORMAT " %s chunks to freelist", sum_count_in_chunks_in_use(i), chunk_size_name(i));
+ for (ChunkIndex i = ZeroIndex; i <= HumongousIndex; i = next_chunk_index(i)) {
Metachunk* chunks = chunks_in_use(i);
- chunk_manager()->return_chunks(i, chunks);
+ chunk_manager()->return_chunk_list(i, chunks);
set_chunks_in_use(i, NULL);
- log.trace("updated freelist count " SSIZE_FORMAT " %s", chunk_manager()->free_chunks(i)->count(), chunk_size_name(i));
- assert(i != HumongousIndex, "Humongous chunks are handled explicitly later");
}
- // The medium chunk case may be optimized by passing the head and
- // tail of the medium chunk list to add_at_head(). The tail is often
- // the current chunk but there are probably exceptions.
-
- // Humongous chunks
- log.trace("returned " SIZE_FORMAT " %s humongous chunks to dictionary",
- sum_count_in_chunks_in_use(HumongousIndex), chunk_size_name(HumongousIndex));
- log.trace("Humongous chunk dictionary: ");
- // Humongous chunks are never the current chunk.
- Metachunk* humongous_chunks = chunks_in_use(HumongousIndex);
-
- while (humongous_chunks != NULL) {
- DEBUG_ONLY(humongous_chunks->set_is_tagged_free(true);)
- NOT_PRODUCT(humongous_chunks->mangle(badMetaWordVal);)
- log.trace(PTR_FORMAT " (" SIZE_FORMAT ") ", p2i(humongous_chunks), humongous_chunks->word_size());
- assert(humongous_chunks->word_size() == (size_t)
- align_size_up(humongous_chunks->word_size(),
- smallest_chunk_size()),
- "Humongous chunk size is wrong: word size " SIZE_FORMAT
- " granularity " SIZE_FORMAT,
- humongous_chunks->word_size(), smallest_chunk_size());
- Metachunk* next_humongous_chunks = humongous_chunks->next();
- humongous_chunks->container()->dec_container_count();
- chunk_manager()->humongous_dictionary()->return_chunk(humongous_chunks);
- humongous_chunks = next_humongous_chunks;
- }
- log.trace("updated dictionary count " SIZE_FORMAT " %s", chunk_manager()->humongous_dictionary()->total_count(), chunk_size_name(HumongousIndex));
chunk_manager()->slow_locked_verify();
if (_block_freelists != NULL) {
@@ -2360,36 +2418,6 @@
}
}
-const char* SpaceManager::chunk_size_name(ChunkIndex index) const {
- switch (index) {
- case SpecializedIndex:
- return "Specialized";
- case SmallIndex:
- return "Small";
- case MediumIndex:
- return "Medium";
- case HumongousIndex:
- return "Humongous";
- default:
- return NULL;
- }
-}
-
-ChunkIndex ChunkManager::list_index(size_t size) {
- if (free_chunks(SpecializedIndex)->size() == size) {
- return SpecializedIndex;
- }
- if (free_chunks(SmallIndex)->size() == size) {
- return SmallIndex;
- }
- if (free_chunks(MediumIndex)->size() == size) {
- return MediumIndex;
- }
-
- assert(size > free_chunks(MediumIndex)->size(), "Not a humongous chunk");
- return HumongousIndex;
-}
-
void SpaceManager::deallocate(MetaWord* p, size_t word_size) {
assert_lock_strong(_lock);
// Allocations and deallocations are in raw_word_size
@@ -4073,6 +4101,273 @@
}
}
+// ChunkManagerReturnTest stresses taking/returning chunks from the ChunkManager. It takes and
+// returns chunks from/to the ChunkManager while keeping track of the expected ChunkManager
+// content.
+class ChunkManagerReturnTestImpl {
+
+ VirtualSpaceNode _vsn;
+ ChunkManager _cm;
+
+ // The expected content of the chunk manager.
+ unsigned _chunks_in_chunkmanager;
+ size_t _words_in_chunkmanager;
+
+ // A fixed size pool of chunks. Chunks may be in the chunk manager (free) or not (in use).
+ static const int num_chunks = 256;
+ Metachunk* _pool[num_chunks];
+
+ // Helper, return a random position into the chunk pool.
+ static int get_random_position() {
+ return os::random() % num_chunks;
+ }
+
+ // Asserts that ChunkManager counters match expectations.
+ void assert_counters() {
+ assert(_vsn.container_count() == num_chunks - _chunks_in_chunkmanager, "vsn counter mismatch.");
+ assert(_cm.free_chunks_count() == _chunks_in_chunkmanager, "cm counter mismatch.");
+ assert(_cm.free_chunks_total_words() == _words_in_chunkmanager, "cm counter mismatch.");
+ }
+
+ // Get a random chunk size. Equal chance to get spec/med/small chunk size or
+ // a humongous chunk size. The latter itself is random in the range of [med+spec..4*med).
+ size_t get_random_chunk_size() {
+ const size_t sizes [] = { SpecializedChunk, SmallChunk, MediumChunk };
+ const int rand = os::random() % 4;
+ if (rand < 3) {
+ return sizes[rand];
+ } else {
+ // Note: this affects the max. size of space (see _vsn initialization in ctor).
+ return align_size_up(MediumChunk + 1 + (os::random() % (MediumChunk * 4)), SpecializedChunk);
+ }
+ }
+
+ // Starting at pool index <start>+1, find the next chunk tagged as either free or in use, depending
+ // on <is_free>. Search wraps. Returns its position, or -1 if no matching chunk was found.
+ int next_matching_chunk(int start, bool is_free) const {
+ assert(start >= 0 && start < num_chunks, "invalid parameter");
+ int pos = start;
+ do {
+ if (++pos == num_chunks) {
+ pos = 0;
+ }
+ if (_pool[pos]->is_tagged_free() == is_free) {
+ return pos;
+ }
+ } while (pos != start);
+ return -1;
+ }
+
+ // A structure to keep information about a chunk list including which
+ // chunks are part of this list. This is needed to keep information about a chunk list
+ // we will to return to the ChunkManager, because the original list will be destroyed.
+ struct AChunkList {
+ Metachunk* head;
+ Metachunk* all[num_chunks];
+ size_t size;
+ int num;
+ ChunkIndex index;
+ };
+
+ // Assemble, from the in-use chunks (not in the chunk manager) in the pool,
+ // a random chunk list of max. length <list_size> of chunks with the same
+ // ChunkIndex (chunk size).
+ // Returns false if list cannot be assembled. List is returned in the <out>
+ // structure. Returned list may be smaller than <list_size>.
+ bool assemble_random_chunklist(AChunkList* out, int list_size) {
+ // Choose a random in-use chunk from the pool...
+ const int headpos = next_matching_chunk(get_random_position(), false);
+ if (headpos == -1) {
+ return false;
+ }
+ Metachunk* const head = _pool[headpos];
+ out->all[0] = head;
+ assert(head->is_tagged_free() == false, "Chunk state mismatch");
+ // ..then go from there, chain it up with up to list_size - 1 number of other
+ // in-use chunks of the same index.
+ const ChunkIndex index = _cm.list_index(head->word_size());
+ int num_added = 1;
+ size_t size_added = head->word_size();
+ int pos = headpos;
+ Metachunk* tail = head;
+ do {
+ pos = next_matching_chunk(pos, false);
+ if (pos != headpos) {
+ Metachunk* c = _pool[pos];
+ assert(c->is_tagged_free() == false, "Chunk state mismatch");
+ if (index == _cm.list_index(c->word_size())) {
+ tail->set_next(c);
+ c->set_prev(tail);
+ tail = c;
+ out->all[num_added] = c;
+ num_added ++;
+ size_added += c->word_size();
+ }
+ }
+ } while (num_added < list_size && pos != headpos);
+ out->head = head;
+ out->index = index;
+ out->size = size_added;
+ out->num = num_added;
+ return true;
+ }
+
+ // Take a single random chunk from the ChunkManager.
+ bool take_single_random_chunk_from_chunkmanager() {
+ assert_counters();
+ _cm.locked_verify();
+ int pos = next_matching_chunk(get_random_position(), true);
+ if (pos == -1) {
+ return false;
+ }
+ Metachunk* c = _pool[pos];
+ assert(c->is_tagged_free(), "Chunk state mismatch");
+ // Note: instead of using ChunkManager::remove_chunk on this one chunk, we call
+ // ChunkManager::free_chunks_get() with this chunk's word size. We really want
+ // to exercise ChunkManager::free_chunks_get() because that one gets called for
+ // normal chunk allocation.
+ Metachunk* c2 = _cm.free_chunks_get(c->word_size());
+ assert(c2 != NULL, "Unexpected.");
+ assert(!c2->is_tagged_free(), "Chunk state mismatch");
+ assert(c2->next() == NULL && c2->prev() == NULL, "Chunk should be outside of a list.");
+ _chunks_in_chunkmanager --;
+ _words_in_chunkmanager -= c->word_size();
+ assert_counters();
+ _cm.locked_verify();
+ return true;
+ }
+
+ // Returns a single random chunk to the chunk manager. Returns false if that
+ // was not possible (all chunks are already in the chunk manager).
+ bool return_single_random_chunk_to_chunkmanager() {
+ assert_counters();
+ _cm.locked_verify();
+ int pos = next_matching_chunk(get_random_position(), false);
+ if (pos == -1) {
+ return false;
+ }
+ Metachunk* c = _pool[pos];
+ assert(c->is_tagged_free() == false, "wrong chunk information");
+ _cm.return_single_chunk(_cm.list_index(c->word_size()), c);
+ _chunks_in_chunkmanager ++;
+ _words_in_chunkmanager += c->word_size();
+ // (Note: until 8170520 is fixed, internal ChunkManager counters have to be updated
+ // by the caller - but only when returning chunks, ChunkManager->remove_chunk()
+ // already updates the counter.)
+ _cm.inc_free_chunks_total(c->word_size(), 1);
+ assert(c->is_tagged_free() == true, "wrong chunk information");
+ assert_counters();
+ _cm.locked_verify();
+ return true;
+ }
+
+ // Return a random chunk list to the chunk manager. Returns the length of the
+ // returned list.
+ int return_random_chunk_list_to_chunkmanager(int list_size) {
+ assert_counters();
+ _cm.locked_verify();
+ AChunkList aChunkList;
+ if (!assemble_random_chunklist(&aChunkList, list_size)) {
+ return 0;
+ }
+ // Before returning chunks are returned, they should be tagged in use.
+ for (int i = 0; i < aChunkList.num; i ++) {
+ assert(!aChunkList.all[i]->is_tagged_free(), "chunk state mismatch.");
+ }
+ _cm.return_chunk_list(aChunkList.index, aChunkList.head);
+ _chunks_in_chunkmanager += aChunkList.num;
+ _words_in_chunkmanager += aChunkList.size;
+ // (Note: until 8170520 is fixed, internal ChunkManager counters have to be updated
+ // by the caller - but only when returning chunks, ChunkManager->remove_chunk()
+ // already updates the counter.)
+ _cm.inc_free_chunks_total(aChunkList.size, aChunkList.num);
+ // After all chunks are returned, check that they are now tagged free.
+ for (int i = 0; i < aChunkList.num; i ++) {
+ assert(aChunkList.all[i]->is_tagged_free(), "chunk state mismatch.");
+ }
+ assert_counters();
+ _cm.locked_verify();
+ return aChunkList.num;
+ }
+
+public:
+
+ ChunkManagerReturnTestImpl()
+ : _vsn(align_size_up(MediumChunk * num_chunks * 5 * sizeof(MetaWord), Metaspace::reserve_alignment()))
+ , _cm(SpecializedChunk, SmallChunk, MediumChunk)
+ , _chunks_in_chunkmanager(0)
+ , _words_in_chunkmanager(0)
+ {
+ MutexLockerEx ml(SpaceManager::expand_lock(), Mutex::_no_safepoint_check_flag);
+ // Allocate virtual space and allocate random chunks. Keep these chunks in the _pool. These chunks are
+ // "in use", because not yet added to any chunk manager.
+ _vsn.initialize();
+ _vsn.expand_by(_vsn.reserved_words(), _vsn.reserved_words());
+ for (int i = 0; i < num_chunks; i ++) {
+ const size_t size = get_random_chunk_size();
+ _pool[i] = _vsn.get_chunk_vs(size);
+ assert(_pool[i] != NULL, "allocation failed");
+ }
+ assert_counters();
+ _cm.locked_verify();
+ }
+
+ // Test entry point.
+ // Return some chunks to the chunk manager (return phase). Take some chunks out (take phase). Repeat.
+ // Chunks are choosen randomly. Number of chunks to return or taken are choosen randomly, but affected
+ // by the <phase_length_factor> argument: a factor of 0.0 will cause the test to quickly alternate between
+ // returning and taking, whereas a factor of 1.0 will take/return all chunks from/to the
+ // chunks manager, thereby emptying or filling it completely.
+ void do_test(float phase_length_factor) {
+ MutexLockerEx ml(SpaceManager::expand_lock(), Mutex::_no_safepoint_check_flag);
+ assert_counters();
+ // Execute n operations, and operation being the move of a single chunk to/from the chunk manager.
+ const int num_max_ops = num_chunks * 100;
+ int num_ops = num_max_ops;
+ const int average_phase_length = (int)(phase_length_factor * num_chunks);
+ int num_ops_until_switch = MAX2(1, (int)(average_phase_length + os::random() % 8 - 4));
+ bool return_phase = true;
+ while (num_ops > 0) {
+ int chunks_moved = 0;
+ if (return_phase) {
+ // Randomly switch between returning a single chunk or a random length chunk list.
+ if (os::random() % 2 == 0) {
+ if (return_single_random_chunk_to_chunkmanager()) {
+ chunks_moved = 1;
+ }
+ } else {
+ const int list_length = MAX2(1, (int)(os::random() % num_ops_until_switch));
+ chunks_moved = return_random_chunk_list_to_chunkmanager(list_length);
+ }
+ } else {
+ // Breath out.
+ if (take_single_random_chunk_from_chunkmanager()) {
+ chunks_moved = 1;
+ }
+ }
+ num_ops -= chunks_moved;
+ num_ops_until_switch -= chunks_moved;
+ if (chunks_moved == 0 || num_ops_until_switch <= 0) {
+ return_phase = !return_phase;
+ num_ops_until_switch = MAX2(1, (int)(average_phase_length + os::random() % 8 - 4));
+ }
+ }
+ }
+};
+
+void* setup_chunkmanager_returntests() {
+ ChunkManagerReturnTestImpl* p = new ChunkManagerReturnTestImpl();
+ return p;
+}
+
+void teardown_chunkmanager_returntests(void* p) {
+ delete (ChunkManagerReturnTestImpl*) p;
+}
+
+void run_chunkmanager_returntests(void* p, float phase_length) {
+ ChunkManagerReturnTestImpl* test = (ChunkManagerReturnTestImpl*) p;
+ test->do_test(phase_length);
+}
// The following test is placed here instead of a gtest / unittest file
// because the ChunkManager class is only available in this file.