From 007abbc45b4dbf2208ea7f47b99b0c567c94b2ce Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 20 Mar 2019 10:11:12 -0700 Subject: [PATCH 001/118] Added 96-byte FastAllocator Since storage queue nodes account for a large portion of memory usage, we can save space by only allocating 96 bytes instead of 128 bytes for each node. --- fdbclient/VersionedMap.h | 2 +- fdbserver/storageserver.actor.cpp | 2 +- flow/Arena.h | 6 ++++-- flow/FastAlloc.cpp | 20 ++++++++++++-------- flow/FastAlloc.h | 10 ++++++---- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/fdbclient/VersionedMap.h b/fdbclient/VersionedMap.h index f0dc5e2b46..fae6d85b13 100644 --- a/fdbclient/VersionedMap.h +++ b/fdbclient/VersionedMap.h @@ -483,7 +483,7 @@ public: return r->second; } - static const int overheadPerItem = 128*4; + static const int overheadPerItem = 96*4; struct iterator; VersionedMap() : oldestVersion(0), latestVersion(0) { diff --git a/fdbserver/storageserver.actor.cpp b/fdbserver/storageserver.actor.cpp index 9d0f07f025..7c09e8f6c1 100644 --- a/fdbserver/storageserver.actor.cpp +++ b/fdbserver/storageserver.actor.cpp @@ -3693,7 +3693,7 @@ void versionedMapTest() { printf("SS Ptree node is %zu bytes\n", sizeof( StorageServer::VersionedData::PTreeT ) ); const int NSIZE = sizeof(VersionedMap::PTreeT); - const int ASIZE = NSIZE<=64 ? 64 : NextPowerOfTwo::Result; + const int ASIZE = NSIZE<=64 ? 64 : NextFastAllocatedSize::Result; auto before = FastAllocator< ASIZE >::getTotalMemory(); diff --git a/flow/Arena.h b/flow/Arena.h index 258be4d3cb..263074d2b8 100644 --- a/flow/Arena.h +++ b/flow/Arena.h @@ -222,7 +222,8 @@ struct ArenaBlock : NonCopyable, ThreadSafeReferenceCounted } if (reqSize < LARGE) { - if (reqSize <= 128) { b = (ArenaBlock*)FastAllocator<128>::allocate(); b->bigSize = 128; INSTRUMENT_ALLOCATE("Arena128"); } + if (reqSize <= 96) { b = (ArenaBlock*)FastAllocator<96>::allocate(); b->bigSize = 96; INSTRUMENT_ALLOCATE("Arena96"); } + else if (reqSize <= 128) { b = (ArenaBlock*)FastAllocator<128>::allocate(); b->bigSize = 128; INSTRUMENT_ALLOCATE("Arena128"); } else if (reqSize <= 256) { b = (ArenaBlock*)FastAllocator<256>::allocate(); b->bigSize = 256; INSTRUMENT_ALLOCATE("Arena256"); } else if (reqSize <= 512) { b = (ArenaBlock*)FastAllocator<512>::allocate(); b->bigSize = 512; INSTRUMENT_ALLOCATE("Arena512"); } else if (reqSize <= 1024) { b = (ArenaBlock*)FastAllocator<1024>::allocate(); b->bigSize = 1024; INSTRUMENT_ALLOCATE("Arena1024"); } @@ -264,7 +265,8 @@ struct ArenaBlock : NonCopyable, ThreadSafeReferenceCounted else if (tinySize <= 32) { FastAllocator<32>::release(this); INSTRUMENT_RELEASE("Arena32"); } else { FastAllocator<64>::release(this); INSTRUMENT_RELEASE("Arena64"); } } else { - if (bigSize <= 128) { FastAllocator<128>::release(this); INSTRUMENT_RELEASE("Arena128"); } + if (bigSize <= 96) { FastAllocator<96>::release(this); INSTRUMENT_RELEASE("Arena96"); } + else if (bigSize <= 128) { FastAllocator<128>::release(this); INSTRUMENT_RELEASE("Arena128"); } else if (bigSize <= 256) { FastAllocator<256>::release(this); INSTRUMENT_RELEASE("Arena256"); } else if (bigSize <= 512) { FastAllocator<512>::release(this); INSTRUMENT_RELEASE("Arena512"); } else if (bigSize <= 1024) { FastAllocator<1024>::release(this); INSTRUMENT_RELEASE("Arena1024"); } diff --git a/flow/FastAlloc.cpp b/flow/FastAlloc.cpp index 83766000ac..d2230cf395 100644 --- a/flow/FastAlloc.cpp +++ b/flow/FastAlloc.cpp @@ -219,14 +219,15 @@ static int64_t getSizeCode(int i) { case 16: return 1; case 32: return 2; case 64: return 3; - case 128: return 4; - case 256: return 5; - case 512: return 6; - case 1024: return 7; - case 2048: return 8; - case 4096: return 9; - case 8192: return 10; - default: return 11; + case 96: return 4; + case 128: return 5; + case 256: return 6; + case 512: return 7; + case 1024: return 8; + case 2048: return 9; + case 4096: return 10; + case 8192: return 11; + default: return 12; } } @@ -475,6 +476,7 @@ void releaseAllThreadMagazines() { FastAllocator<16>::releaseThreadMagazines(); FastAllocator<32>::releaseThreadMagazines(); FastAllocator<64>::releaseThreadMagazines(); + FastAllocator<96>::releaseThreadMagazines(); FastAllocator<128>::releaseThreadMagazines(); FastAllocator<256>::releaseThreadMagazines(); FastAllocator<512>::releaseThreadMagazines(); @@ -490,6 +492,7 @@ int64_t getTotalUnusedAllocatedMemory() { unusedMemory += FastAllocator<16>::getApproximateMemoryUnused(); unusedMemory += FastAllocator<32>::getApproximateMemoryUnused(); unusedMemory += FastAllocator<64>::getApproximateMemoryUnused(); + unusedMemory += FastAllocator<96>::getApproximateMemoryUnused(); unusedMemory += FastAllocator<128>::getApproximateMemoryUnused(); unusedMemory += FastAllocator<256>::getApproximateMemoryUnused(); unusedMemory += FastAllocator<512>::getApproximateMemoryUnused(); @@ -504,6 +507,7 @@ int64_t getTotalUnusedAllocatedMemory() { template class FastAllocator<16>; template class FastAllocator<32>; template class FastAllocator<64>; +template class FastAllocator<96>; template class FastAllocator<128>; template class FastAllocator<256>; template class FastAllocator<512>; diff --git a/flow/FastAlloc.h b/flow/FastAlloc.h index 06f35711c3..b522f5bf0b 100644 --- a/flow/FastAlloc.h +++ b/flow/FastAlloc.h @@ -156,7 +156,7 @@ int64_t getTotalUnusedAllocatedMemory(); void setFastAllocatorThreadInitFunction( void (*)() ); // The given function will be called at least once in each thread that allocates from a FastAllocator. Currently just one such function is tracked. template -class NextPowerOfTwo { +class NextFastAllocatedSize { static const int A = X-1; static const int B = A | (A>>1); static const int C = B | (B>>2); @@ -164,7 +164,7 @@ class NextPowerOfTwo { static const int E = D | (D>>8); static const int F = E | (E>>16); public: - static const int Result = F+1; + static const int Result = (X > 64 && X <= 96) ? 96 : F+1; }; template @@ -173,13 +173,13 @@ public: static void* operator new(size_t s) { if (s != sizeof(Object)) abort(); INSTRUMENT_ALLOCATE(typeid(Object).name()); - void* p = FastAllocator::Result>::allocate(); + void* p = FastAllocator::Result>::allocate(); return p; } static void operator delete(void* s) { INSTRUMENT_RELEASE(typeid(Object).name()); - FastAllocator::Result>::release(s); + FastAllocator::Result>::release(s); } // Redefine placement new so you can still use it static void* operator new( size_t, void* p ) { return p; } @@ -190,6 +190,7 @@ static void* allocateFast(int size) { if (size <= 16) return FastAllocator<16>::allocate(); if (size <= 32) return FastAllocator<32>::allocate(); if (size <= 64) return FastAllocator<64>::allocate(); + if (size <= 96) return FastAllocator<96>::allocate(); if (size <= 128) return FastAllocator<128>::allocate(); if (size <= 256) return FastAllocator<256>::allocate(); if (size <= 512) return FastAllocator<512>::allocate(); @@ -200,6 +201,7 @@ static void freeFast(int size, void* ptr) { if (size <= 16) return FastAllocator<16>::release(ptr); if (size <= 32) return FastAllocator<32>::release(ptr); if (size <= 64) return FastAllocator<64>::release(ptr); + if (size <= 96) return FastAllocator<96>::release(ptr); if (size <= 128) return FastAllocator<128>::release(ptr); if (size <= 256) return FastAllocator<256>::release(ptr); if (size <= 512) return FastAllocator<512>::release(ptr); From 4a3467296f3c92ea67f290d96ee72091f55ab183 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Wed, 20 Mar 2019 17:11:53 -0700 Subject: [PATCH 002/118] Removed Arena96 --- flow/Arena.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/flow/Arena.h b/flow/Arena.h index 263074d2b8..258be4d3cb 100644 --- a/flow/Arena.h +++ b/flow/Arena.h @@ -222,8 +222,7 @@ struct ArenaBlock : NonCopyable, ThreadSafeReferenceCounted } if (reqSize < LARGE) { - if (reqSize <= 96) { b = (ArenaBlock*)FastAllocator<96>::allocate(); b->bigSize = 96; INSTRUMENT_ALLOCATE("Arena96"); } - else if (reqSize <= 128) { b = (ArenaBlock*)FastAllocator<128>::allocate(); b->bigSize = 128; INSTRUMENT_ALLOCATE("Arena128"); } + if (reqSize <= 128) { b = (ArenaBlock*)FastAllocator<128>::allocate(); b->bigSize = 128; INSTRUMENT_ALLOCATE("Arena128"); } else if (reqSize <= 256) { b = (ArenaBlock*)FastAllocator<256>::allocate(); b->bigSize = 256; INSTRUMENT_ALLOCATE("Arena256"); } else if (reqSize <= 512) { b = (ArenaBlock*)FastAllocator<512>::allocate(); b->bigSize = 512; INSTRUMENT_ALLOCATE("Arena512"); } else if (reqSize <= 1024) { b = (ArenaBlock*)FastAllocator<1024>::allocate(); b->bigSize = 1024; INSTRUMENT_ALLOCATE("Arena1024"); } @@ -265,8 +264,7 @@ struct ArenaBlock : NonCopyable, ThreadSafeReferenceCounted else if (tinySize <= 32) { FastAllocator<32>::release(this); INSTRUMENT_RELEASE("Arena32"); } else { FastAllocator<64>::release(this); INSTRUMENT_RELEASE("Arena64"); } } else { - if (bigSize <= 96) { FastAllocator<96>::release(this); INSTRUMENT_RELEASE("Arena96"); } - else if (bigSize <= 128) { FastAllocator<128>::release(this); INSTRUMENT_RELEASE("Arena128"); } + if (bigSize <= 128) { FastAllocator<128>::release(this); INSTRUMENT_RELEASE("Arena128"); } else if (bigSize <= 256) { FastAllocator<256>::release(this); INSTRUMENT_RELEASE("Arena256"); } else if (bigSize <= 512) { FastAllocator<512>::release(this); INSTRUMENT_RELEASE("Arena512"); } else if (bigSize <= 1024) { FastAllocator<1024>::release(this); INSTRUMENT_RELEASE("Arena1024"); } From dbcf1d717c28837f02427e684e6f70c3f1a3fcd8 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 21 Mar 2019 13:18:14 -0700 Subject: [PATCH 003/118] Added calls to TRACEALLOCATOR(96) and DETAILALLOCATORMEMUSAGE(96) --- flow/Platform.cpp | 1 + flow/SystemMonitor.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/flow/Platform.cpp b/flow/Platform.cpp index e08527eaa5..c9815300cd 100644 --- a/flow/Platform.cpp +++ b/flow/Platform.cpp @@ -2370,6 +2370,7 @@ void outOfMemory() { TRACEALLOCATOR(16); TRACEALLOCATOR(32); TRACEALLOCATOR(64); + TRACEALLOCATOR(96); TRACEALLOCATOR(128); TRACEALLOCATOR(256); TRACEALLOCATOR(512); diff --git a/flow/SystemMonitor.cpp b/flow/SystemMonitor.cpp index 207e5c30d7..18a6c1b6d2 100644 --- a/flow/SystemMonitor.cpp +++ b/flow/SystemMonitor.cpp @@ -105,6 +105,7 @@ SystemStatistics customSystemMonitor(std::string eventName, StatisticsState *sta .DETAILALLOCATORMEMUSAGE(16) .DETAILALLOCATORMEMUSAGE(32) .DETAILALLOCATORMEMUSAGE(64) + .DETAILALLOCATORMEMUSAGE(96) .DETAILALLOCATORMEMUSAGE(128) .DETAILALLOCATORMEMUSAGE(256) .DETAILALLOCATORMEMUSAGE(512) @@ -254,6 +255,7 @@ SystemStatistics customSystemMonitor(std::string eventName, StatisticsState *sta TRACEALLOCATOR(16); TRACEALLOCATOR(32); TRACEALLOCATOR(64); + TRACEALLOCATOR(96); TRACEALLOCATOR(128); TRACEALLOCATOR(256); TRACEALLOCATOR(512); From 4293a3e230f20fe90346b54c391d86a1bec56127 Mon Sep 17 00:00:00 2001 From: Trevor Clinkenbeard Date: Thu, 21 Mar 2019 13:59:20 -0700 Subject: [PATCH 004/118] Generalized VersionedMap::overheadPerItem calculation --- fdbclient/VersionedMap.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fdbclient/VersionedMap.h b/fdbclient/VersionedMap.h index fae6d85b13..a9b1eb00ab 100644 --- a/fdbclient/VersionedMap.h +++ b/fdbclient/VersionedMap.h @@ -483,7 +483,8 @@ public: return r->second; } - static const int overheadPerItem = 96*4; + // For each item in the versioned map, 4 PTree nodes are potentially allocated: + static const int overheadPerItem = NextFastAllocatedSize::Result*4; struct iterator; VersionedMap() : oldestVersion(0), latestVersion(0) { From d6a4a0a0bb0d79f71ab896dada40a33ef0c679cb Mon Sep 17 00:00:00 2001 From: Amir Abu Shareb Date: Wed, 3 Apr 2019 18:47:18 +0300 Subject: [PATCH 005/118] bindings/go: keep futures alive on use --- bindings/go/src/fdb/futures.go | 36 ++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/bindings/go/src/fdb/futures.go b/bindings/go/src/fdb/futures.go index 747a1135e7..fe7b946c75 100644 --- a/bindings/go/src/fdb/futures.go +++ b/bindings/go/src/fdb/futures.go @@ -100,15 +100,18 @@ func fdb_future_block_until_ready(f *C.FDBFuture) { m.Lock() } -func (f future) BlockUntilReady() { +func (f *future) BlockUntilReady() { + defer runtime.KeepAlive(f) fdb_future_block_until_ready(f.ptr) } -func (f future) IsReady() bool { +func (f *future) IsReady() bool { + defer runtime.KeepAlive(f) return C.fdb_future_is_ready(f.ptr) != 0 } -func (f future) Cancel() { +func (f *future) Cancel() { + defer runtime.KeepAlive(f) C.fdb_future_cancel(f.ptr) } @@ -140,6 +143,8 @@ type futureByteSlice struct { func (f *futureByteSlice) Get() ([]byte, error) { f.o.Do(func() { + defer runtime.KeepAlive(f.future) + var present C.fdb_bool_t var value *C.uint8_t var length C.int @@ -195,6 +200,8 @@ type futureKey struct { func (f *futureKey) Get() (Key, error) { f.o.Do(func() { + defer runtime.KeepAlive(f.future) + var value *C.uint8_t var length C.int @@ -241,7 +248,9 @@ type futureNil struct { *future } -func (f futureNil) Get() error { +func (f *futureNil) Get() error { + defer runtime.KeepAlive(f.future) + f.BlockUntilReady() if err := C.fdb_future_get_error(f.ptr); err != 0 { return Error{int(err)} @@ -250,7 +259,7 @@ func (f futureNil) Get() error { return nil } -func (f futureNil) MustGet() { +func (f *futureNil) MustGet() { if err := f.Get(); err != nil { panic(err) } @@ -272,7 +281,9 @@ func stringRefToSlice(ptr unsafe.Pointer) []byte { return C.GoBytes(src, size) } -func (f futureKeyValueArray) Get() ([]KeyValue, bool, error) { +func (f *futureKeyValueArray) Get() ([]KeyValue, bool, error) { + defer runtime.KeepAlive(f.future) + f.BlockUntilReady() var kvs *C.FDBKeyValue @@ -316,17 +327,20 @@ type futureInt64 struct { *future } -func (f futureInt64) Get() (int64, error) { +func (f *futureInt64) Get() (int64, error) { + defer runtime.KeepAlive(f.future) + f.BlockUntilReady() var ver C.int64_t if err := C.fdb_future_get_version(f.ptr, &ver); err != 0 { return 0, Error{int(err)} } + return int64(ver), nil } -func (f futureInt64) MustGet() int64 { +func (f *futureInt64) MustGet() int64 { val, err := f.Get() if err != nil { panic(err) @@ -356,7 +370,9 @@ type futureStringSlice struct { *future } -func (f futureStringSlice) Get() ([]string, error) { +func (f *futureStringSlice) Get() ([]string, error) { + defer runtime.KeepAlive(f.future) + f.BlockUntilReady() var strings **C.char @@ -375,7 +391,7 @@ func (f futureStringSlice) Get() ([]string, error) { return ret, nil } -func (f futureStringSlice) MustGet() []string { +func (f *futureStringSlice) MustGet() []string { val, err := f.Get() if err != nil { panic(err) From 2462d292357d320013a0b8c0a3828fd707f59329 Mon Sep 17 00:00:00 2001 From: Nikolas Ioannou Date: Tue, 30 Apr 2019 11:33:12 +0200 Subject: [PATCH 006/118] AsyncFileCached: switch from a random to an LRU cache eviction policy --- fdbrpc/AsyncFileCached.actor.cpp | 11 ++++++----- fdbrpc/AsyncFileCached.actor.h | 31 ++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/fdbrpc/AsyncFileCached.actor.cpp b/fdbrpc/AsyncFileCached.actor.cpp index e8fb62e2e3..3242215652 100644 --- a/fdbrpc/AsyncFileCached.actor.cpp +++ b/fdbrpc/AsyncFileCached.actor.cpp @@ -33,11 +33,8 @@ EvictablePage::~EvictablePage() { else aligned_free(data); } - if (index > -1) { - pageCache->pages[index] = pageCache->pages.back(); - pageCache->pages[index]->index = index; - pageCache->pages.pop_back(); - } + // remove it from the LRU + pageCache->lruPages.erase(EvictablePageCache::List::s_iterator_to(*this)); } std::map< std::string, OpenFileInfo > AsyncFileCached::openFiles; @@ -97,6 +94,8 @@ Future AsyncFileCached::read_write_impl( AsyncFileCached* self, void* data if ( p == self->pages.end() ) { AFCPage* page = new AFCPage( self, pageOffset ); p = self->pages.insert( std::make_pair(pageOffset, page) ).first; + } else { + self->pageCache->updateHit(p->second); } int bytesInPage = std::min(self->pageCache->pageSize - offsetInPage, remaining); @@ -133,6 +132,8 @@ Future AsyncFileCached::readZeroCopy( void** data, int* length, int64_t of if ( p == pages.end() ) { AFCPage* page = new AFCPage( this, offset ); p = pages.insert( std::make_pair(offset, page) ).first; + } else { + p->second->pageCache->updateHit(p->second); } *data = p->second->data; diff --git a/fdbrpc/AsyncFileCached.actor.h b/fdbrpc/AsyncFileCached.actor.h index 51c04abc2e..5f2ca3a2a7 100644 --- a/fdbrpc/AsyncFileCached.actor.h +++ b/fdbrpc/AsyncFileCached.actor.h @@ -27,6 +27,8 @@ #elif !defined(FLOW_ASYNCFILECACHED_ACTOR_H) #define FLOW_ASYNCFILECACHED_ACTOR_H +#include + #include "flow/flow.h" #include "fdbrpc/IAsyncFile.h" #include "flow/Knobs.h" @@ -34,18 +36,21 @@ #include "flow/network.h" #include "flow/actorcompiler.h" // This must be the last #include. +namespace bi = boost::intrusive; struct EvictablePage { void* data; - int index; class Reference pageCache; + bi::list_member_hook<> member_hook; virtual bool evict() = 0; // true if page was evicted, false if it isn't immediately evictable (but will be evicted regardless if possible) - EvictablePage(Reference pageCache) : data(0), index(-1), pageCache(pageCache) {} + EvictablePage(Reference pageCache) : data(0), pageCache(pageCache) {} virtual ~EvictablePage(); }; struct EvictablePageCache : ReferenceCounted { + using List = bi::list< EvictablePage, bi::member_hook< EvictablePage, bi::list_member_hook<>, &EvictablePage::member_hook>>; + EvictablePageCache() : pageSize(0), maxPages(0) {} explicit EvictablePageCache(int pageSize, int64_t maxSize) : pageSize(pageSize), maxPages(maxSize / pageSize) {} @@ -53,21 +58,29 @@ struct EvictablePageCache : ReferenceCounted { try_evict(); try_evict(); page->data = pageSize == 4096 ? FastAllocator<4096>::allocate() : aligned_alloc(4096,pageSize); - page->index = pages.size(); - pages.push_back(page); + lruPages.push_back(*page); // new page is considered the most recently used (placed at LRU tail) + } + + void updateHit(EvictablePage* page) { + // on a hit, update page's location in the LRU so that it's most recent (tail) + lruPages.erase(List::s_iterator_to(*page)); + lruPages.push_back(*page); } void try_evict() { - if (pages.size() >= (uint64_t)maxPages && !pages.empty()) { - for (int i = 0; i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; i++) { // If we don't manage to evict anything, just go ahead and exceed the cache limit - int toEvict = g_random->randomInt(0, pages.size()); - if (pages[toEvict]->evict()) + if (lruPages.size() >= (uint64_t)maxPages) { + int i = 0; + // try the least recently used pages first (starting at head of the LRU list) + for (List::iterator it = lruPages.begin(); + it != lruPages.end() && i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; + ++it, ++i) { // If we don't manage to evict anything, just go ahead and exceed the cache limit + if (it->evict()) break; } } } - std::vector pages; + List lruPages; int pageSize; int64_t maxPages; }; From b2280e15bfa46d1ccfb6077eaf9baffd90cc1e64 Mon Sep 17 00:00:00 2001 From: Nikolas Ioannou Date: Thu, 2 May 2019 17:35:30 +0200 Subject: [PATCH 007/118] AsyncFileCached: support for lru cache eviction policy - Added a knob to control page cache eviction policy (0=random default, 1=lru) - Added page cache hit, miss, and eviction stats --- fdbrpc/AsyncFileCached.actor.cpp | 12 +++++- fdbrpc/AsyncFileCached.actor.h | 63 +++++++++++++++++++++++++------- flow/Knobs.cpp | 1 + flow/Knobs.h | 1 + 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/fdbrpc/AsyncFileCached.actor.cpp b/fdbrpc/AsyncFileCached.actor.cpp index 3242215652..7daecd99e2 100644 --- a/fdbrpc/AsyncFileCached.actor.cpp +++ b/fdbrpc/AsyncFileCached.actor.cpp @@ -33,8 +33,16 @@ EvictablePage::~EvictablePage() { else aligned_free(data); } - // remove it from the LRU - pageCache->lruPages.erase(EvictablePageCache::List::s_iterator_to(*this)); + if (EvictablePageCache::RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) { + if (index > -1) { + pageCache->pages[index] = pageCache->pages.back(); + pageCache->pages[index]->index = index; + pageCache->pages.pop_back(); + } + } else { + // remove it from the LRU + pageCache->lruPages.erase(EvictablePageCache::List::s_iterator_to(*this)); + } } std::map< std::string, OpenFileInfo > AsyncFileCached::openFiles; diff --git a/fdbrpc/AsyncFileCached.actor.h b/fdbrpc/AsyncFileCached.actor.h index 5f2ca3a2a7..55e6892775 100644 --- a/fdbrpc/AsyncFileCached.actor.h +++ b/fdbrpc/AsyncFileCached.actor.h @@ -39,50 +39,85 @@ namespace bi = boost::intrusive; struct EvictablePage { void* data; + int index; class Reference pageCache; bi::list_member_hook<> member_hook; virtual bool evict() = 0; // true if page was evicted, false if it isn't immediately evictable (but will be evicted regardless if possible) - EvictablePage(Reference pageCache) : data(0), pageCache(pageCache) {} + EvictablePage(Reference pageCache) : data(0), index(-1), pageCache(pageCache) {} virtual ~EvictablePage(); }; struct EvictablePageCache : ReferenceCounted { using List = bi::list< EvictablePage, bi::member_hook< EvictablePage, bi::list_member_hook<>, &EvictablePage::member_hook>>; - EvictablePageCache() : pageSize(0), maxPages(0) {} + EvictablePageCache() : pageSize(0), maxPages(0) { + cacheHits.init(LiteralStringRef("EvictablePageCache.CacheHits")); + cacheMisses.init(LiteralStringRef("EvictablePageCache.CacheMisses")); + cacheEvictions.init(LiteralStringRef("EvictablePageCache.CacheEviction")); + } + explicit EvictablePageCache(int pageSize, int64_t maxSize) : pageSize(pageSize), maxPages(maxSize / pageSize) {} void allocate(EvictablePage* page) { try_evict(); try_evict(); page->data = pageSize == 4096 ? FastAllocator<4096>::allocate() : aligned_alloc(4096,pageSize); - lruPages.push_back(*page); // new page is considered the most recently used (placed at LRU tail) + if (RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) { + page->index = pages.size(); + pages.push_back(page); + } else { + lruPages.push_back(*page); // new page is considered the most recently used (placed at LRU tail) + } + cacheMisses++; } void updateHit(EvictablePage* page) { - // on a hit, update page's location in the LRU so that it's most recent (tail) - lruPages.erase(List::s_iterator_to(*page)); - lruPages.push_back(*page); + if (RANDOM != FLOW_KNOBS->CACHE_EVICTION_POLICY) { + // on a hit, update page's location in the LRU so that it's most recent (tail) + lruPages.erase(List::s_iterator_to(*page)); + lruPages.push_back(*page); + } + cacheHits++; } void try_evict() { - if (lruPages.size() >= (uint64_t)maxPages) { - int i = 0; - // try the least recently used pages first (starting at head of the LRU list) - for (List::iterator it = lruPages.begin(); - it != lruPages.end() && i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; - ++it, ++i) { // If we don't manage to evict anything, just go ahead and exceed the cache limit - if (it->evict()) - break; + if (RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) { + if (pages.size() >= (uint64_t)maxPages && !pages.empty()) { + for (int i = 0; i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; i++) { // If we don't manage to evict anything, just go ahead and exceed the cache limit + int toEvict = g_random->randomInt(0, pages.size()); + if (pages[toEvict]->evict()) { + cacheEvictions++; + break; + } + } + } + } else { + // For now, LRU is the only other CACHE_EVICTION option + if (lruPages.size() >= (uint64_t)maxPages) { + int i = 0; + // try the least recently used pages first (starting at head of the LRU list) + for (List::iterator it = lruPages.begin(); + it != lruPages.end() && i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; + ++it, ++i) { // If we don't manage to evict anything, just go ahead and exceed the cache limit + if (it->evict()) { + cacheEvictions++; + break; + } + } } } } + std::vector pages; List lruPages; int pageSize; int64_t maxPages; + Int64MetricHandle cacheHits; + Int64MetricHandle cacheMisses; + Int64MetricHandle cacheEvictions; + enum CacheEvictionType { RANDOM = 0, LRU = 1 }; }; struct OpenFileInfo : NonCopyable { diff --git a/flow/Knobs.cpp b/flow/Knobs.cpp index 75e7001bc8..64049c72aa 100644 --- a/flow/Knobs.cpp +++ b/flow/Knobs.cpp @@ -77,6 +77,7 @@ FlowKnobs::FlowKnobs(bool randomize, bool isSimulated) { init( BUGGIFY_SIM_PAGE_CACHE_4K, 1e6 ); init( BUGGIFY_SIM_PAGE_CACHE_64K, 1e6 ); init( MAX_EVICT_ATTEMPTS, 100 ); if( randomize && BUGGIFY ) MAX_EVICT_ATTEMPTS = 2; + init( CACHE_EVICTION_POLICY, 0 ); init( PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION, 0.1 ); if( randomize && BUGGIFY ) PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION = 0.0; else if( randomize && BUGGIFY ) PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION = 1.0; //AsyncFileKAIO diff --git a/flow/Knobs.h b/flow/Knobs.h index 9becb6f052..5490752ad7 100644 --- a/flow/Knobs.h +++ b/flow/Knobs.h @@ -93,6 +93,7 @@ public: int64_t SIM_PAGE_CACHE_64K; int64_t BUGGIFY_SIM_PAGE_CACHE_4K; int64_t BUGGIFY_SIM_PAGE_CACHE_64K; + int CACHE_EVICTION_POLICY; // 0 RANDOM (default), 1 LRU int MAX_EVICT_ATTEMPTS; double PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION; double TOO_MANY_CONNECTIONS_CLOSED_RESET_DELAY; From fdb39929902dc818021374a32f89bd0d5af63d39 Mon Sep 17 00:00:00 2001 From: Nikolas Ioannou Date: Fri, 3 May 2019 14:04:43 +0200 Subject: [PATCH 008/118] AsyncFileCached: switch to string for eviction policy knob --- fdbrpc/AsyncFileCached.actor.cpp | 2 +- fdbrpc/AsyncFileCached.actor.h | 22 +++++++++++++++------- flow/Knobs.cpp | 2 +- flow/Knobs.h | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/fdbrpc/AsyncFileCached.actor.cpp b/fdbrpc/AsyncFileCached.actor.cpp index 7daecd99e2..ec5b6f6e73 100644 --- a/fdbrpc/AsyncFileCached.actor.cpp +++ b/fdbrpc/AsyncFileCached.actor.cpp @@ -33,7 +33,7 @@ EvictablePage::~EvictablePage() { else aligned_free(data); } - if (EvictablePageCache::RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) { + if (EvictablePageCache::RANDOM == pageCache->cacheEvictionType) { if (index > -1) { pageCache->pages[index] = pageCache->pages.back(); pageCache->pages[index]->index = index; diff --git a/fdbrpc/AsyncFileCached.actor.h b/fdbrpc/AsyncFileCached.actor.h index 55e6892775..84a1d29849 100644 --- a/fdbrpc/AsyncFileCached.actor.h +++ b/fdbrpc/AsyncFileCached.actor.h @@ -52,19 +52,26 @@ struct EvictablePage { struct EvictablePageCache : ReferenceCounted { using List = bi::list< EvictablePage, bi::member_hook< EvictablePage, bi::list_member_hook<>, &EvictablePage::member_hook>>; - EvictablePageCache() : pageSize(0), maxPages(0) { + EvictablePageCache() : pageSize(0), maxPages(0) {} + + explicit EvictablePageCache(int pageSize, int64_t maxSize) : pageSize(pageSize), maxPages(maxSize / pageSize) { + std::string cep = FLOW_KNOBS->CACHE_EVICTION_POLICY; + std::transform(cep.begin(), cep.end(), cep.begin(), ::tolower); + if (0 == cep.compare("random")) + cacheEvictionType = RANDOM; + else + cacheEvictionType = LRU; + cacheHits.init(LiteralStringRef("EvictablePageCache.CacheHits")); cacheMisses.init(LiteralStringRef("EvictablePageCache.CacheMisses")); cacheEvictions.init(LiteralStringRef("EvictablePageCache.CacheEviction")); } - explicit EvictablePageCache(int pageSize, int64_t maxSize) : pageSize(pageSize), maxPages(maxSize / pageSize) {} - void allocate(EvictablePage* page) { try_evict(); try_evict(); page->data = pageSize == 4096 ? FastAllocator<4096>::allocate() : aligned_alloc(4096,pageSize); - if (RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) { + if (RANDOM == cacheEvictionType) { page->index = pages.size(); pages.push_back(page); } else { @@ -74,7 +81,7 @@ struct EvictablePageCache : ReferenceCounted { } void updateHit(EvictablePage* page) { - if (RANDOM != FLOW_KNOBS->CACHE_EVICTION_POLICY) { + if (RANDOM != cacheEvictionType) { // on a hit, update page's location in the LRU so that it's most recent (tail) lruPages.erase(List::s_iterator_to(*page)); lruPages.push_back(*page); @@ -83,7 +90,7 @@ struct EvictablePageCache : ReferenceCounted { } void try_evict() { - if (RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) { + if (RANDOM == cacheEvictionType) { if (pages.size() >= (uint64_t)maxPages && !pages.empty()) { for (int i = 0; i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; i++) { // If we don't manage to evict anything, just go ahead and exceed the cache limit int toEvict = g_random->randomInt(0, pages.size()); @@ -110,6 +117,7 @@ struct EvictablePageCache : ReferenceCounted { } } + enum CacheEvictionType { RANDOM = 0, LRU = 1 }; std::vector pages; List lruPages; int pageSize; @@ -117,7 +125,7 @@ struct EvictablePageCache : ReferenceCounted { Int64MetricHandle cacheHits; Int64MetricHandle cacheMisses; Int64MetricHandle cacheEvictions; - enum CacheEvictionType { RANDOM = 0, LRU = 1 }; + CacheEvictionType cacheEvictionType; }; struct OpenFileInfo : NonCopyable { diff --git a/flow/Knobs.cpp b/flow/Knobs.cpp index 64049c72aa..0b7df45839 100644 --- a/flow/Knobs.cpp +++ b/flow/Knobs.cpp @@ -77,7 +77,7 @@ FlowKnobs::FlowKnobs(bool randomize, bool isSimulated) { init( BUGGIFY_SIM_PAGE_CACHE_4K, 1e6 ); init( BUGGIFY_SIM_PAGE_CACHE_64K, 1e6 ); init( MAX_EVICT_ATTEMPTS, 100 ); if( randomize && BUGGIFY ) MAX_EVICT_ATTEMPTS = 2; - init( CACHE_EVICTION_POLICY, 0 ); + init( CACHE_EVICTION_POLICY, "random" ); init( PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION, 0.1 ); if( randomize && BUGGIFY ) PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION = 0.0; else if( randomize && BUGGIFY ) PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION = 1.0; //AsyncFileKAIO diff --git a/flow/Knobs.h b/flow/Knobs.h index 5490752ad7..fd3c61368d 100644 --- a/flow/Knobs.h +++ b/flow/Knobs.h @@ -93,7 +93,7 @@ public: int64_t SIM_PAGE_CACHE_64K; int64_t BUGGIFY_SIM_PAGE_CACHE_4K; int64_t BUGGIFY_SIM_PAGE_CACHE_64K; - int CACHE_EVICTION_POLICY; // 0 RANDOM (default), 1 LRU + std::string CACHE_EVICTION_POLICY; // for now, "random", "lru", are supported int MAX_EVICT_ATTEMPTS; double PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION; double TOO_MANY_CONNECTIONS_CLOSED_RESET_DELAY; From d6170210e71d8458781b26e64bf42e42338796f7 Mon Sep 17 00:00:00 2001 From: Nikolas Ioannou Date: Mon, 6 May 2019 10:11:46 +0200 Subject: [PATCH 009/118] AsyncFileCached: throw (new) exception, through helper static fn, if cache eviction polity not recognized. --- fdbrpc/AsyncFileCached.actor.h | 24 ++++++++++++++---------- flow/error_definitions.h | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/fdbrpc/AsyncFileCached.actor.h b/fdbrpc/AsyncFileCached.actor.h index 84a1d29849..3efa52d9fa 100644 --- a/fdbrpc/AsyncFileCached.actor.h +++ b/fdbrpc/AsyncFileCached.actor.h @@ -51,17 +51,22 @@ struct EvictablePage { struct EvictablePageCache : ReferenceCounted { using List = bi::list< EvictablePage, bi::member_hook< EvictablePage, bi::list_member_hook<>, &EvictablePage::member_hook>>; + enum CacheEvictionType { RANDOM = 0, LRU = 1 }; - EvictablePageCache() : pageSize(0), maxPages(0) {} - - explicit EvictablePageCache(int pageSize, int64_t maxSize) : pageSize(pageSize), maxPages(maxSize / pageSize) { - std::string cep = FLOW_KNOBS->CACHE_EVICTION_POLICY; + static CacheEvictionType evictionPolicyStringToEnum(const std::string &policy) { + std::string cep = policy; std::transform(cep.begin(), cep.end(), cep.begin(), ::tolower); - if (0 == cep.compare("random")) - cacheEvictionType = RANDOM; - else - cacheEvictionType = LRU; + if (cep != "random" && cep != "lru") + throw invalid_cache_eviction_policy(); + if (cep == "random") + return RANDOM; + return LRU; + } + + EvictablePageCache() : pageSize(0), maxPages(0), cacheEvictionType(RANDOM) {} + + explicit EvictablePageCache(int pageSize, int64_t maxSize) : pageSize(pageSize), maxPages(maxSize / pageSize), cacheEvictionType(evictionPolicyStringToEnum(FLOW_KNOBS->CACHE_EVICTION_POLICY)) { cacheHits.init(LiteralStringRef("EvictablePageCache.CacheHits")); cacheMisses.init(LiteralStringRef("EvictablePageCache.CacheMisses")); cacheEvictions.init(LiteralStringRef("EvictablePageCache.CacheEviction")); @@ -117,7 +122,6 @@ struct EvictablePageCache : ReferenceCounted { } } - enum CacheEvictionType { RANDOM = 0, LRU = 1 }; std::vector pages; List lruPages; int pageSize; @@ -125,7 +129,7 @@ struct EvictablePageCache : ReferenceCounted { Int64MetricHandle cacheHits; Int64MetricHandle cacheMisses; Int64MetricHandle cacheEvictions; - CacheEvictionType cacheEvictionType; + const CacheEvictionType cacheEvictionType; }; struct OpenFileInfo : NonCopyable { diff --git a/flow/error_definitions.h b/flow/error_definitions.h index d57e75c032..82752ca938 100755 --- a/flow/error_definitions.h +++ b/flow/error_definitions.h @@ -131,6 +131,7 @@ ERROR( transaction_invalid_version, 2020, "Transaction does not have a valid com ERROR( no_commit_version, 2021, "Transaction is read-only and therefore does not have a commit version" ) ERROR( environment_variable_network_option_failed, 2022, "Environment variable network option could not be set" ) ERROR( transaction_read_only, 2023, "Attempted to commit a transaction specified as read-only" ) +ERROR( invalid_cache_eviction_policy, 2024, "Invalid cache eviction policy, only random and lru are supported" ) ERROR( incompatible_protocol_version, 2100, "Incompatible protocol version" ) ERROR( transaction_too_large, 2101, "Transaction exceeds byte limit" ) From 8033a0e7173e7406aba4726260ec7d58ad1f57d6 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Thu, 14 Feb 2019 17:47:30 -0600 Subject: [PATCH 010/118] bindings/go: format lines over 120 characters long --- bindings/go/src/fdb/database.go | 5 +- bindings/go/src/fdb/fdb.go | 5 +- bindings/go/src/fdb/transaction.go | 77 ++++++++++++++++++++++++++---- 3 files changed, 77 insertions(+), 10 deletions(-) diff --git a/bindings/go/src/fdb/database.go b/bindings/go/src/fdb/database.go index db888307b4..34b90ee926 100644 --- a/bindings/go/src/fdb/database.go +++ b/bindings/go/src/fdb/database.go @@ -216,7 +216,10 @@ func (d Database) LocalityGetBoundaryKeys(er ExactRange, limit int, readVersion tr.Options().SetLockAware() bk, ek := er.FDBRangeKeys() - ffer := KeyRange{append(Key("\xFF/keyServers/"), bk.FDBKey()...), append(Key("\xFF/keyServers/"), ek.FDBKey()...)} + ffer := KeyRange{ + append(Key("\xFF/keyServers/"), bk.FDBKey()...), + append(Key("\xFF/keyServers/"), ek.FDBKey()...), + } kvs, e := tr.Snapshot().GetRange(ffer, RangeOptions{Limit: limit}).GetSliceWithError() if e != nil { diff --git a/bindings/go/src/fdb/fdb.go b/bindings/go/src/fdb/fdb.go index a65a9552f0..3566c7f2ce 100644 --- a/bindings/go/src/fdb/fdb.go +++ b/bindings/go/src/fdb/fdb.go @@ -139,7 +139,10 @@ func APIVersion(version int) error { if e == 2203 { maxSupportedVersion := C.fdb_get_max_api_version() if headerVersion > int(maxSupportedVersion) { - return fmt.Errorf("This version of the FoundationDB Go binding is not supported by the installed FoundationDB C library. The binding requires a library that supports API version %d, but the installed library supports a maximum version of %d.", headerVersion, maxSupportedVersion) + return fmt.Errorf("This version of the FoundationDB Go binding is "+ + "not supported by the installed FoundationDB C library. "+ + "The binding requires a library that supports API version %d, "+ + "but the installed library supports a maximum version of %d.", headerVersion, maxSupportedVersion) } return fmt.Errorf("API version %d is not supported by the installed FoundationDB C library.", version) } diff --git a/bindings/go/src/fdb/transaction.go b/bindings/go/src/fdb/transaction.go index 79a8011fa5..2f9c270044 100644 --- a/bindings/go/src/fdb/transaction.go +++ b/bindings/go/src/fdb/transaction.go @@ -236,7 +236,14 @@ func (t Transaction) Watch(key KeyConvertible) FutureNil { } func (t *transaction) get(key []byte, snapshot int) FutureByteSlice { - return &futureByteSlice{future: newFuture(C.fdb_transaction_get(t.ptr, byteSliceToPtr(key), C.int(len(key)), C.fdb_bool_t(snapshot)))} + return &futureByteSlice{ + newFuture(C.fdb_transaction_get( + t.ptr, + byteSliceToPtr(key), + C.int(len(key)), + C.fdb_bool_t(snapshot), + )), + } } // Get returns the (future) value associated with the specified key. The read is @@ -253,7 +260,24 @@ func (t *transaction) doGetRange(r Range, options RangeOptions, snapshot bool, i bkey := bsel.Key.FDBKey() ekey := esel.Key.FDBKey() - return futureKeyValueArray{newFuture(C.fdb_transaction_get_range(t.ptr, byteSliceToPtr(bkey), C.int(len(bkey)), C.fdb_bool_t(boolToInt(bsel.OrEqual)), C.int(bsel.Offset), byteSliceToPtr(ekey), C.int(len(ekey)), C.fdb_bool_t(boolToInt(esel.OrEqual)), C.int(esel.Offset), C.int(options.Limit), C.int(0), C.FDBStreamingMode(options.Mode-1), C.int(iteration), C.fdb_bool_t(boolToInt(snapshot)), C.fdb_bool_t(boolToInt(options.Reverse))))} + return futureKeyValueArray{ + newFuture(C.fdb_transaction_get_range( + t.ptr, + byteSliceToPtr(bkey), + C.int(len(bkey)), + C.fdb_bool_t(boolToInt(bsel.OrEqual)), + C.int(bsel.Offset), + byteSliceToPtr(ekey), + C.int(len(ekey)), + C.fdb_bool_t(boolToInt(esel.OrEqual)), + C.int(esel.Offset), + C.int(options.Limit), + C.int(0), + C.FDBStreamingMode(options.Mode-1), + C.int(iteration), + C.fdb_bool_t(boolToInt(snapshot)), + C.fdb_bool_t(boolToInt(options.Reverse)), + ))} } func (t *transaction) getRange(r Range, options RangeOptions, snapshot bool) RangeResult { @@ -358,7 +382,16 @@ func boolToInt(b bool) int { func (t *transaction) getKey(sel KeySelector, snapshot int) FutureKey { key := sel.Key.FDBKey() - return &futureKey{future: newFuture(C.fdb_transaction_get_key(t.ptr, byteSliceToPtr(key), C.int(len(key)), C.fdb_bool_t(boolToInt(sel.OrEqual)), C.int(sel.Offset), C.fdb_bool_t(snapshot)))} + return &futureKey{ + newFuture(C.fdb_transaction_get_key( + t.ptr, + byteSliceToPtr(key), + C.int(len(key)), + C.fdb_bool_t(boolToInt(sel.OrEqual)), + C.int(sel.Offset), + C.fdb_bool_t(snapshot), + )), + } } // GetKey returns the future key referenced by the provided key selector. The @@ -375,14 +408,28 @@ func (t Transaction) GetKey(sel Selectable) FutureKey { } func (t Transaction) atomicOp(key []byte, param []byte, code int) { - C.fdb_transaction_atomic_op(t.ptr, byteSliceToPtr(key), C.int(len(key)), byteSliceToPtr(param), C.int(len(param)), C.FDBMutationType(code)) + C.fdb_transaction_atomic_op( + t.ptr, + byteSliceToPtr(key), + C.int(len(key)), + byteSliceToPtr(param), + C.int(len(param)), + C.FDBMutationType(code), + ) } func addConflictRange(t *transaction, er ExactRange, crtype conflictRangeType) error { begin, end := er.FDBRangeKeys() bkb := begin.FDBKey() ekb := end.FDBKey() - if err := C.fdb_transaction_add_conflict_range(t.ptr, byteSliceToPtr(bkb), C.int(len(bkb)), byteSliceToPtr(ekb), C.int(len(ekb)), C.FDBConflictRangeType(crtype)); err != 0 { + if err := C.fdb_transaction_add_conflict_range( + t.ptr, + byteSliceToPtr(bkb), + C.int(len(bkb)), + byteSliceToPtr(ekb), + C.int(len(ekb)), + C.FDBConflictRangeType(crtype), + ); err != 0 { return Error{int(err)} } @@ -414,7 +461,11 @@ func copyAndAppend(orig []byte, b byte) []byte { // For more information on conflict ranges, see // https://apple.github.io/foundationdb/developer-guide.html#conflict-ranges. func (t Transaction) AddReadConflictKey(key KeyConvertible) error { - return addConflictRange(t.transaction, KeyRange{key, Key(copyAndAppend(key.FDBKey(), 0x00))}, conflictRangeTypeRead) + return addConflictRange( + t.transaction, + KeyRange{key, Key(copyAndAppend(key.FDBKey(), 0x00))}, + conflictRangeTypeRead, + ) } // AddWriteConflictRange adds a range of keys to the transactions write @@ -435,7 +486,11 @@ func (t Transaction) AddWriteConflictRange(er ExactRange) error { // For more information on conflict ranges, see // https://apple.github.io/foundationdb/developer-guide.html#conflict-ranges. func (t Transaction) AddWriteConflictKey(key KeyConvertible) error { - return addConflictRange(t.transaction, KeyRange{key, Key(copyAndAppend(key.FDBKey(), 0x00))}, conflictRangeTypeWrite) + return addConflictRange( + t.transaction, + KeyRange{key, Key(copyAndAppend(key.FDBKey(), 0x00))}, + conflictRangeTypeWrite, + ) } // Options returns a TransactionOptions instance suitable for setting options @@ -446,7 +501,13 @@ func (t Transaction) Options() TransactionOptions { func localityGetAddressesForKey(t *transaction, key KeyConvertible) FutureStringSlice { kb := key.FDBKey() - return &futureStringSlice{newFuture(C.fdb_transaction_get_addresses_for_key(t.ptr, byteSliceToPtr(kb), C.int(len(kb))))} + return &futureStringSlice{ + newFuture(C.fdb_transaction_get_addresses_for_key( + t.ptr, + byteSliceToPtr(kb), + C.int(len(kb)), + )), + } } // LocalityGetAddressesForKey returns the (future) public network addresses of From 0d7748d59fbef8930cd157451bda5363e030b89e Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Thu, 14 Feb 2019 19:03:03 -0600 Subject: [PATCH 011/118] bindings/go: use binary.LittleEndian.PutUint64 instead of binary.Write Benchmarks can be found in bindings/go/bench --- bindings/go/bench/int64ToBytes_bench_test.go | 41 +++++++++++++++++++ .../go/src/_util/translate_fdb_options.go | 10 ++--- bindings/go/src/fdb/generated.go | 24 +++++------ 3 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 bindings/go/bench/int64ToBytes_bench_test.go diff --git a/bindings/go/bench/int64ToBytes_bench_test.go b/bindings/go/bench/int64ToBytes_bench_test.go new file mode 100644 index 0000000000..95c2e1f420 --- /dev/null +++ b/bindings/go/bench/int64ToBytes_bench_test.go @@ -0,0 +1,41 @@ +package bench + +import ( + "bytes" + "encoding/binary" + "testing" +) + +var result []byte + +func Benchmark_Int64ToBytesBuffer(b *testing.B) { + b.ReportAllocs() + + var r []byte + for n := 0; n < b.N; n++ { + buf := new(bytes.Buffer) + if err := binary.Write(buf, binary.LittleEndian, int64(n)); err != nil { + b.Error("failed to write int64:", err) + } + + b.SetBytes(int64(buf.Len())) + r = buf.Bytes() + } + + result = r +} + +func Benchmark_Int64ToBytesPut(b *testing.B) { + b.ReportAllocs() + + var r []byte + for n := 0; n < b.N; n++ { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(n)) + + b.SetBytes(int64(len(buf))) + r = buf + } + + result = r +} diff --git a/bindings/go/src/_util/translate_fdb_options.go b/bindings/go/src/_util/translate_fdb_options.go index 418a6b2e1c..327e716648 100644 --- a/bindings/go/src/_util/translate_fdb_options.go +++ b/bindings/go/src/_util/translate_fdb_options.go @@ -209,12 +209,10 @@ import ( "encoding/binary" ) -func int64ToBytes(i int64) ([]byte, error) { - buf := new(bytes.Buffer) - if e := binary.Write(buf, binary.LittleEndian, i); e != nil { - return nil, e - } - return buf.Bytes(), nil +func int64ToBytes(i int64) []byte { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(i)) + return buf } `) diff --git a/bindings/go/src/fdb/generated.go b/bindings/go/src/fdb/generated.go index f6b3ccc18b..9f5ea8dda9 100644 --- a/bindings/go/src/fdb/generated.go +++ b/bindings/go/src/fdb/generated.go @@ -34,12 +34,10 @@ import ( "encoding/binary" ) -func int64ToBytes(i int64) ([]byte, error) { - buf := new(bytes.Buffer) - if e := binary.Write(buf, binary.LittleEndian, i); e != nil { - return nil, e - } - return buf.Bytes(), nil +func int64ToBytes(i int64) []byte { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(i)) + return buf } // Deprecated @@ -495,7 +493,7 @@ const ( // Infrequently used. The client has passed a specific row limit and wants // that many rows delivered in a single batch. Because of iterator operation // in client drivers make request batches transparent to the user, consider - // ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this + // “WANT_ALL“ StreamingMode instead. A row limit must be specified if this // mode is used. StreamingModeExact StreamingMode = 1 @@ -612,15 +610,15 @@ type ErrorPredicate int const ( - // Returns ``true`` if the error indicates the operations in the - // transactions should be retried because of transient error. + // Returns “true“ if the error indicates the operations in the transactions + // should be retried because of transient error. ErrorPredicateRetryable ErrorPredicate = 50000 - // Returns ``true`` if the error indicates the transaction may have - // succeeded, though not in a way the system can verify. + // Returns “true“ if the error indicates the transaction may have succeeded, + // though not in a way the system can verify. ErrorPredicateMaybeCommitted ErrorPredicate = 50001 - // Returns ``true`` if the error indicates the transaction has not - // committed, though in a way that can be retried. + // Returns “true“ if the error indicates the transaction has not committed, + // though in a way that can be retried. ErrorPredicateRetryableNotCommitted ErrorPredicate = 50002 ) From 5793b1a55e7d93eea9ce8de36ddf3d44843e4449 Mon Sep 17 00:00:00 2001 From: Nikolas Ioannou Date: Tue, 7 May 2019 08:32:57 +0200 Subject: [PATCH 012/118] Validate cache eviction policy value after knob args have been set. --- fdbserver/fdbserver.actor.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fdbserver/fdbserver.actor.cpp b/fdbserver/fdbserver.actor.cpp index b6281d2a5b..e8148e5927 100644 --- a/fdbserver/fdbserver.actor.cpp +++ b/fdbserver/fdbserver.actor.cpp @@ -54,6 +54,7 @@ #include "fdbrpc/TLSConnection.h" #include "fdbrpc/Net2FileSystem.h" #include "fdbrpc/Platform.h" +#include "fdbrpc/AsyncFileCached.actor.h" #include "fdbserver/CoroFlow.h" #include "flow/SignalSafeUnwind.h" #if defined(CMAKE_BUILD) || !defined(WIN32) @@ -1426,6 +1427,11 @@ int main(int argc, char* argv[]) { } if (!serverKnobs->setKnob("server_mem_limit", std::to_string(memLimit))) ASSERT(false); + if (EvictablePageCache::RANDOM != EvictablePageCache::evictionPolicyStringToEnum(flowKnobs->CACHE_EVICTION_POLICY) && + EvictablePageCache::LRU != EvictablePageCache::evictionPolicyStringToEnum(flowKnobs->CACHE_EVICTION_POLICY)) { + ASSERT(false); + } + if (role == SkipListTest) { skipListTest(); flushAndExit(FDB_EXIT_SUCCESS); From c2827f4fa3e980a99ddf41b689b970ff704a83d6 Mon Sep 17 00:00:00 2001 From: Nikolas Ioannou Date: Wed, 8 May 2019 15:41:17 +0200 Subject: [PATCH 013/118] Add page cache hit, miss, and eviction stats to SystemMonitor --- fdbrpc/AsyncFileCached.actor.h | 10 +++++----- flow/SystemMonitor.cpp | 3 +++ flow/SystemMonitor.h | 6 ++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/fdbrpc/AsyncFileCached.actor.h b/fdbrpc/AsyncFileCached.actor.h index 3efa52d9fa..9c874d30c2 100644 --- a/fdbrpc/AsyncFileCached.actor.h +++ b/fdbrpc/AsyncFileCached.actor.h @@ -69,7 +69,7 @@ struct EvictablePageCache : ReferenceCounted { explicit EvictablePageCache(int pageSize, int64_t maxSize) : pageSize(pageSize), maxPages(maxSize / pageSize), cacheEvictionType(evictionPolicyStringToEnum(FLOW_KNOBS->CACHE_EVICTION_POLICY)) { cacheHits.init(LiteralStringRef("EvictablePageCache.CacheHits")); cacheMisses.init(LiteralStringRef("EvictablePageCache.CacheMisses")); - cacheEvictions.init(LiteralStringRef("EvictablePageCache.CacheEviction")); + cacheEvictions.init(LiteralStringRef("EvictablePageCache.CacheEvictions")); } void allocate(EvictablePage* page) { @@ -82,7 +82,7 @@ struct EvictablePageCache : ReferenceCounted { } else { lruPages.push_back(*page); // new page is considered the most recently used (placed at LRU tail) } - cacheMisses++; + ++cacheMisses; } void updateHit(EvictablePage* page) { @@ -91,7 +91,7 @@ struct EvictablePageCache : ReferenceCounted { lruPages.erase(List::s_iterator_to(*page)); lruPages.push_back(*page); } - cacheHits++; + ++cacheHits; } void try_evict() { @@ -100,7 +100,7 @@ struct EvictablePageCache : ReferenceCounted { for (int i = 0; i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; i++) { // If we don't manage to evict anything, just go ahead and exceed the cache limit int toEvict = g_random->randomInt(0, pages.size()); if (pages[toEvict]->evict()) { - cacheEvictions++; + ++cacheEvictions; break; } } @@ -114,7 +114,7 @@ struct EvictablePageCache : ReferenceCounted { it != lruPages.end() && i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; ++it, ++i) { // If we don't manage to evict anything, just go ahead and exceed the cache limit if (it->evict()) { - cacheEvictions++; + ++cacheEvictions; break; } } diff --git a/flow/SystemMonitor.cpp b/flow/SystemMonitor.cpp index 3e9b010a9e..08639c357a 100644 --- a/flow/SystemMonitor.cpp +++ b/flow/SystemMonitor.cpp @@ -89,6 +89,9 @@ SystemStatistics customSystemMonitor(std::string eventName, StatisticsState *sta .detail("CachePageReadsMerged", netData.countFileCachePageReadsMerged - statState->networkState.countFileCachePageReadsMerged) .detail("CacheWrites", netData.countFileCacheWrites - statState->networkState.countFileCacheWrites) .detail("CacheReads", netData.countFileCacheReads - statState->networkState.countFileCacheReads) + .detail("CacheHits", netData.countFilePageCacheHits - statState->networkState.countFilePageCacheHits) + .detail("CacheMisses", netData.countFilePageCacheMisses - statState->networkState.countFilePageCacheMisses) + .detail("CacheEvictions", netData.countFilePageCacheEvictions - statState->networkState.countFilePageCacheEvictions) .detail("ZoneID", machineState.zoneId) .detail("MachineID", machineState.machineId) .detail("AIOSubmitCount", netData.countAIOSubmit - statState->networkState.countAIOSubmit) diff --git a/flow/SystemMonitor.h b/flow/SystemMonitor.h index 8c7d41c5ba..4c0585cd69 100644 --- a/flow/SystemMonitor.h +++ b/flow/SystemMonitor.h @@ -74,6 +74,9 @@ struct NetworkData { int64_t countFileCachePageReadsMerged; int64_t countFileCacheFinds; int64_t countFileCacheReadBytes; + int64_t countFilePageCacheHits; + int64_t countFilePageCacheMisses; + int64_t countFilePageCacheEvictions; int64_t countConnEstablished; int64_t countConnClosedWithError; int64_t countConnClosedWithoutError; @@ -121,6 +124,9 @@ struct NetworkData { countFileCachePageReadsMerged = getValue(LiteralStringRef("AsyncFile.CountCachePageReadsMerged")); countFileCacheFinds = getValue(LiteralStringRef("AsyncFile.CountCacheFinds")); countFileCacheReadBytes = getValue(LiteralStringRef("AsyncFile.CountCacheReadBytes")); + countFilePageCacheHits = getValue(LiteralStringRef("EvictablePageCache.CacheHits")); + countFilePageCacheMisses = getValue(LiteralStringRef("EvictablePageCache.CacheMisses")); + countFilePageCacheEvictions = getValue(LiteralStringRef("EvictablePageCache.CacheEvictions")); } }; From b50926c79262455c4c98daaedf551cb853372ffc Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 8 May 2019 21:22:14 -1000 Subject: [PATCH 014/118] replaceFile is truncate(0) on windows --- fdbserver/DiskQueue.actor.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fdbserver/DiskQueue.actor.cpp b/fdbserver/DiskQueue.actor.cpp index 7097698710..9e5e9a4021 100644 --- a/fdbserver/DiskQueue.actor.cpp +++ b/fdbserver/DiskQueue.actor.cpp @@ -283,6 +283,13 @@ public: TraceEvent("DiskQueueReplaceTruncateEnded").detail("Filename", file->getFilename()); } +#if defined(_WIN32) + ACTOR static Future> replaceFile(Reference toReplace) { + // Windows doesn't support a rename over an open file. + wait( toReplace->truncate(0) ); + return toReplace; + } +#else ACTOR static Future> replaceFile(Reference toReplace) { incrementalTruncate( toReplace ); @@ -292,6 +299,7 @@ public: return replacement; } +#endif Future push(StringRef pageData, vector>* toSync) { return push( this, pageData, toSync ); From 0d0f54d1e6f2b68efe2c68a9f89c6cfd5c7d846b Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 8 May 2019 21:22:40 -1000 Subject: [PATCH 015/118] Fix IAsyncFileSystem::open() flags to stop a crash. OPEN_ATOMIC_WRITE_AND_CREATE was missing a required OPEN_CREATE. I'm honestly baffled how this was missed in testing. --- fdbserver/DiskQueue.actor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdbserver/DiskQueue.actor.cpp b/fdbserver/DiskQueue.actor.cpp index 9e5e9a4021..d01f304102 100644 --- a/fdbserver/DiskQueue.actor.cpp +++ b/fdbserver/DiskQueue.actor.cpp @@ -293,7 +293,7 @@ public: ACTOR static Future> replaceFile(Reference toReplace) { incrementalTruncate( toReplace ); - Reference _replacement = wait( IAsyncFileSystem::filesystem()->open( toReplace->getFilename(), IAsyncFile::OPEN_ATOMIC_WRITE_AND_CREATE | IAsyncFile::OPEN_READWRITE | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_UNBUFFERED | IAsyncFile::OPEN_LOCK, 0 ) ); + Reference _replacement = wait( IAsyncFileSystem::filesystem()->open( toReplace->getFilename(), IAsyncFile::OPEN_ATOMIC_WRITE_AND_CREATE | IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_UNBUFFERED, 0 ) ); state Reference replacement = _replacement; wait( replacement->sync() ); From c6c33a4daa83776c1508eaedc9b0c32a9964741e Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 8 May 2019 21:23:42 -1000 Subject: [PATCH 016/118] Make replaceFile more likely to be tested. --- fdbserver/DiskQueue.actor.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fdbserver/DiskQueue.actor.cpp b/fdbserver/DiskQueue.actor.cpp index d01f304102..eb5b11fe6d 100644 --- a/fdbserver/DiskQueue.actor.cpp +++ b/fdbserver/DiskQueue.actor.cpp @@ -339,8 +339,9 @@ public: if (self->files[1].size > desiredMaxFileSize) { // Either shrink self->files[1] to the size of self->files[0], or chop off fileShrinkBytes int64_t maxShrink = std::max( pageFloor(self->files[1].size - desiredMaxFileSize), self->fileShrinkBytes ); - if (maxShrink / SERVER_KNOBS->DISK_QUEUE_FILE_EXTENSION_BYTES > - SERVER_KNOBS->DISK_QUEUE_MAX_TRUNCATE_EXTENTS) { + if ((maxShrink / SERVER_KNOBS->DISK_QUEUE_FILE_EXTENSION_BYTES > + SERVER_KNOBS->DISK_QUEUE_MAX_TRUNCATE_EXTENTS) || + BUGGIFY_WITH_PROB(0.1)) { TEST(true); // Replacing DiskQueue file TraceEvent("DiskQueueReplaceFile", self->dbgid).detail("Filename", self->files[1].f->getFilename()).detail("OldFileSize", self->files[1].size).detail("ElidedTruncateSize", maxShrink); Reference newFile = wait( replaceFile(self->files[1].f) ); From 510b0b2fcd64197dfcda9295833acd419dc6569b Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 8 May 2019 23:08:25 -1000 Subject: [PATCH 017/118] Fix DiskQueue not replaceFile'ing frequently enough for the final time. --- fdbrpc/sim2.actor.cpp | 2 +- fdbserver/DiskQueue.actor.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/fdbrpc/sim2.actor.cpp b/fdbrpc/sim2.actor.cpp index 7aa8166b27..4368904a98 100644 --- a/fdbrpc/sim2.actor.cpp +++ b/fdbrpc/sim2.actor.cpp @@ -608,7 +608,7 @@ private: wait( waitUntilDiskReady( self->diskParameters, 0 ) ); if( _chsize( self->h, (long) size ) == -1 ) { - TraceEvent(SevWarn, "SimpleFileIOError").detail("Location", 6); + TraceEvent(SevWarn, "SimpleFileIOError").detail("Location", 6).detail("Filename", self->filename).detail("Error", strerror(errno)).detail("Size", size).detail("Fd", self->h); throw io_error(); } diff --git a/fdbserver/DiskQueue.actor.cpp b/fdbserver/DiskQueue.actor.cpp index eb5b11fe6d..d48e323a09 100644 --- a/fdbserver/DiskQueue.actor.cpp +++ b/fdbserver/DiskQueue.actor.cpp @@ -336,19 +336,20 @@ public: const int64_t activeDataVolume = pageCeiling(self->files[0].size - self->files[0].popped + self->fileExtensionBytes + self->fileShrinkBytes); const int64_t desiredMaxFileSize = std::max( activeDataVolume, SERVER_KNOBS->TLOG_HARD_LIMIT_BYTES * 2 ); - if (self->files[1].size > desiredMaxFileSize) { + const bool frivolouslyTruncate = BUGGIFY_WITH_PROB(0.001); + if (self->files[1].size > desiredMaxFileSize || frivolouslyTruncate) { // Either shrink self->files[1] to the size of self->files[0], or chop off fileShrinkBytes int64_t maxShrink = std::max( pageFloor(self->files[1].size - desiredMaxFileSize), self->fileShrinkBytes ); if ((maxShrink / SERVER_KNOBS->DISK_QUEUE_FILE_EXTENSION_BYTES > SERVER_KNOBS->DISK_QUEUE_MAX_TRUNCATE_EXTENTS) || - BUGGIFY_WITH_PROB(0.1)) { + (frivolouslyTruncate && g_random->random01() < 0.3)) { TEST(true); // Replacing DiskQueue file TraceEvent("DiskQueueReplaceFile", self->dbgid).detail("Filename", self->files[1].f->getFilename()).detail("OldFileSize", self->files[1].size).detail("ElidedTruncateSize", maxShrink); Reference newFile = wait( replaceFile(self->files[1].f) ); self->files[1].setFile(newFile); self->files[1].size = 0; } else { - self->files[1].size -= maxShrink; + self->files[1].size -= std::min(maxShrink, self->files[1].size); waitfor.push_back( self->files[1].f->truncate( self->files[1].size ) ); } } From fbc4e7b3510ae8225a3f65077a613b98012eaf4d Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Wed, 8 May 2019 23:10:18 -1000 Subject: [PATCH 018/118] Add a release note --- documentation/sphinx/source/release-notes.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/sphinx/source/release-notes.rst b/documentation/sphinx/source/release-notes.rst index d04214a2b7..31613f92fd 100644 --- a/documentation/sphinx/source/release-notes.rst +++ b/documentation/sphinx/source/release-notes.rst @@ -126,6 +126,7 @@ Fixes only impacting 6.1.0+ * The transaction log spill-by-reference policy could read too much data from disk. [6.1.5] `(PR #1527) `_ * Memory tracking trace events could cause the program to crash when called from inside a trace event. [6.1.5] `(PR #1541) `_ * TLogs will replace a large file with an empty file rather than doing a large truncate operation. [6.1.5] `(PR #1545) `_ +* Fix PR #1545 to work on Windows and Linux. [6.1.6] `(PR #1556) `_ Earlier release notes --------------------- From 4a794923e648513d3e4eaea24a3f51f59c9c10c2 Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Fri, 10 May 2019 10:49:32 -0700 Subject: [PATCH 019/118] Added environmental variable representing the docker version Added Java Home environmental variable Removed unnecessary packages --- build/Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 895858f7a0..b710225fb5 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,5 +1,6 @@ FROM centos:6 -LABEL version=0.1.2 +LABEL version=0.1.3 +ENV DOCKER_IMAGEVER=0.1.3 # Install dependencies for developer tools, bindings,\ # documentation, actorcompiler, and packaging tools\ @@ -8,7 +9,7 @@ RUN yum install -y yum-utils &&\ yum -y install centos-release-scl epel-release &&\ yum -y install devtoolset-7 mono-core java-1.8.0-openjdk-devel \ rh-python36-python-devel rh-ruby24 golang python27 \ - rpm-build debbuild python-pip npm ccache distcc &&\ + rpm-build debbuild python-pip npm &&\ pip install boto3==1.1.1 USER root @@ -39,4 +40,5 @@ RUN curl -L https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.8.2.tar.gz > cd /tmp/libressl-2.8.2 && scl enable devtoolset-7 -- make -j`nproc` install &&\ rm -rf /tmp/libressl-2.8.2 /tmp/libressl.tar.gz +ENV JAVA_HOME=/usr/lib/jvm/java-1.8.0 CMD scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash From c3dcbefeb21788a4918118dcfe653c5b6f137f0b Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Fri, 10 May 2019 10:56:31 -0700 Subject: [PATCH 020/118] Updated docker compose to use latest build image --- build/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/docker-compose.yaml b/build/docker-compose.yaml index a3a281ee78..b7fecb150b 100644 --- a/build/docker-compose.yaml +++ b/build/docker-compose.yaml @@ -2,7 +2,7 @@ version: "3" services: common: &common - image: foundationdb/foundationdb-build:0.1.2 + image: foundationdb/foundationdb-build:0.1.3 build-setup: &build-setup <<: *common From 1cfd94f30ba43f809ecbd29c1f5c4afa5ca8e5bb Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Fri, 10 May 2019 11:01:21 -0700 Subject: [PATCH 021/118] Modified docker compose to use make rather than cmake for bindings, packages, and docs --- build/docker-compose.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/docker-compose.yaml b/build/docker-compose.yaml index b7fecb150b..c19fa81337 100644 --- a/build/docker-compose.yaml +++ b/build/docker-compose.yaml @@ -26,16 +26,16 @@ services: build-docs: <<: *build-setup - command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'mkdir -p "$${BUILD_DIR}" && cd "$${BUILD_DIR}" && cmake /__this_is_some_very_long_name_dir_needed_to_fix_a_bug_with_debug_rpms__/foundationdb && make -j "$${MAKEJOBS}" package_html' + command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'make -j "$${MAKEJOBS}" docpackage' release-packages: &release-packages <<: *release-setup - command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'mkdir -p "$${BUILD_DIR}" && cd "$${BUILD_DIR}" && cmake -DFDB_RELEASE=1 -DVALGRIND=0 /__this_is_some_very_long_name_dir_needed_to_fix_a_bug_with_debug_rpms__/foundationdb && make -j "$${MAKEJOBS}" packages preinstall && cpack' + command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'make -j "$${MAKEJOBS}" packages' snapshot-packages: &snapshot-packages <<: *build-setup - command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'mkdir -p "$${BUILD_DIR}" && cd "$${BUILD_DIR}" && cmake -DFDB_RELEASE=0 -DVALGRIND=0 /__this_is_some_very_long_name_dir_needed_to_fix_a_bug_with_debug_rpms__/foundationdb && make -j "$${MAKEJOBS}" packages preinstall && cpack' + command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'make -j "$${MAKEJOBS}" packages' prb-packages: <<: *snapshot-packages @@ -43,11 +43,11 @@ services: release-bindings: &release-bindings <<: *release-setup - command: bash -c 'make -j "$${MAKEJOBS}" bindings' + command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'make -j "$${MAKEJOBS}" bindings' snapshot-bindings: &snapshot-bindings <<: *build-setup - command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'mkdir -p "$${BUILD_DIR}" && cd "$${BUILD_DIR}" && cmake -DFDB_RELEASE=0 /__this_is_some_very_long_name_dir_needed_to_fix_a_bug_with_debug_rpms__/foundationdb && make -j "$${MAKEJOBS}" python_binding' + command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'make -j "$${MAKEJOBS}" bindings' prb-bindings: <<: *snapshot-bindings From 1d041222ec38cc0a17d8c04b0df15ba8e356488f Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Fri, 10 May 2019 12:58:10 -0700 Subject: [PATCH 022/118] Suppress error message for missing tool binaries --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 177922a31a..78b83afe80 100644 --- a/Makefile +++ b/Makefile @@ -15,14 +15,14 @@ else $(error Not prepared to compile on $(ARCH)) endif -MONO := $(shell which mono) +MONO := $(shell which mono 2>/dev/null) ifeq ($(MONO),) MONO := /usr/bin/mono endif -MCS := $(shell which mcs) +MCS := $(shell which mcs 2>/dev/null) ifeq ($(MCS),) - MCS := $(shell which dmcs) + MCS := $(shell which dmcs 2>/dev/null) endif ifeq ($(MCS),) MCS := /usr/bin/mcs @@ -70,7 +70,7 @@ else endif BOOSTDIR ?= ${BOOST_BASEDIR}/${BOOST_BASENAME} -CCACHE := $(shell which ccache) +CCACHE := $(shell which ccache 2>/dev/null) ifneq ($(CCACHE),) CCACHE_CC := $(CCACHE) $(CC) CCACHE_CXX := $(CCACHE) $(CXX) From 93c1dced1695d795bcdfc7ab10d02e7f885b52ef Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 10 May 2019 15:18:44 -0500 Subject: [PATCH 023/118] bindings/go: add labels for futures --- bindings/go/src/fdb/transaction.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/bindings/go/src/fdb/transaction.go b/bindings/go/src/fdb/transaction.go index 2f9c270044..4cd0186404 100644 --- a/bindings/go/src/fdb/transaction.go +++ b/bindings/go/src/fdb/transaction.go @@ -184,7 +184,9 @@ func (t Transaction) Snapshot() Snapshot { // Typical code will not use OnError directly. (Database).Transact uses // OnError internally to implement a correct retry loop. func (t Transaction) OnError(e Error) FutureNil { - return &futureNil{newFuture(C.fdb_transaction_on_error(t.ptr, C.fdb_error_t(e.Code)))} + return &futureNil{ + future: newFuture(C.fdb_transaction_on_error(t.ptr, C.fdb_error_t(e.Code))), + } } // Commit attempts to commit the modifications made in the transaction to the @@ -198,7 +200,9 @@ func (t Transaction) OnError(e Error) FutureNil { // see // https://apple.github.io/foundationdb/developer-guide.html#transactions-with-unknown-results. func (t Transaction) Commit() FutureNil { - return &futureNil{newFuture(C.fdb_transaction_commit(t.ptr))} + return &futureNil{ + future: newFuture(C.fdb_transaction_commit(t.ptr)), + } } // Watch creates a watch and returns a FutureNil that will become ready when the @@ -232,12 +236,14 @@ func (t Transaction) Commit() FutureNil { // cancelled by calling (FutureNil).Cancel on its returned future. func (t Transaction) Watch(key KeyConvertible) FutureNil { kb := key.FDBKey() - return &futureNil{newFuture(C.fdb_transaction_watch(t.ptr, byteSliceToPtr(kb), C.int(len(kb))))} + return &futureNil{ + future: newFuture(C.fdb_transaction_watch(t.ptr, byteSliceToPtr(kb), C.int(len(kb)))), + } } func (t *transaction) get(key []byte, snapshot int) FutureByteSlice { return &futureByteSlice{ - newFuture(C.fdb_transaction_get( + future: newFuture(C.fdb_transaction_get( t.ptr, byteSliceToPtr(key), C.int(len(key)), @@ -261,7 +267,7 @@ func (t *transaction) doGetRange(r Range, options RangeOptions, snapshot bool, i ekey := esel.Key.FDBKey() return futureKeyValueArray{ - newFuture(C.fdb_transaction_get_range( + future: newFuture(C.fdb_transaction_get_range( t.ptr, byteSliceToPtr(bkey), C.int(len(bkey)), @@ -302,7 +308,9 @@ func (t Transaction) GetRange(r Range, options RangeOptions) RangeResult { } func (t *transaction) getReadVersion() FutureInt64 { - return &futureInt64{newFuture(C.fdb_transaction_get_read_version(t.ptr))} + return &futureInt64{ + future: newFuture(C.fdb_transaction_get_read_version(t.ptr)), + } } // (Infrequently used) GetReadVersion returns the (future) transaction read version. The read is @@ -383,7 +391,7 @@ func boolToInt(b bool) int { func (t *transaction) getKey(sel KeySelector, snapshot int) FutureKey { key := sel.Key.FDBKey() return &futureKey{ - newFuture(C.fdb_transaction_get_key( + future: newFuture(C.fdb_transaction_get_key( t.ptr, byteSliceToPtr(key), C.int(len(key)), @@ -502,7 +510,7 @@ func (t Transaction) Options() TransactionOptions { func localityGetAddressesForKey(t *transaction, key KeyConvertible) FutureStringSlice { kb := key.FDBKey() return &futureStringSlice{ - newFuture(C.fdb_transaction_get_addresses_for_key( + future: newFuture(C.fdb_transaction_get_addresses_for_key( t.ptr, byteSliceToPtr(kb), C.int(len(kb)), From 9a85c19063640b8f47ce486dd525991b5bc0390d Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 9 May 2019 01:34:07 -0500 Subject: [PATCH 024/118] bindings/go: fix generated int64ToBytes calls --- .../go/src/_util/translate_fdb_options.go | 7 +- bindings/go/src/fdb/generated.go | 87 ++++--------------- 2 files changed, 20 insertions(+), 74 deletions(-) diff --git a/bindings/go/src/_util/translate_fdb_options.go b/bindings/go/src/_util/translate_fdb_options.go index 327e716648..033d0e770e 100644 --- a/bindings/go/src/_util/translate_fdb_options.go +++ b/bindings/go/src/_util/translate_fdb_options.go @@ -66,11 +66,7 @@ func writeOptBytes(w io.Writer, receiver string, function string, opt Option) { func writeOptInt(w io.Writer, receiver string, function string, opt Option) { fmt.Fprintf(w, `func (o %s) %s(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(%d, b) + return o.setOpt(%d, int64ToBytes(param)) } `, receiver, function, opt.Code) } @@ -205,7 +201,6 @@ func main() { package fdb import ( - "bytes" "encoding/binary" ) diff --git a/bindings/go/src/fdb/generated.go b/bindings/go/src/fdb/generated.go index 9f5ea8dda9..6842072df8 100644 --- a/bindings/go/src/fdb/generated.go +++ b/bindings/go/src/fdb/generated.go @@ -30,7 +30,6 @@ package fdb import ( - "bytes" "encoding/binary" ) @@ -65,22 +64,14 @@ func (o NetworkOptions) SetTraceEnable(param string) error { // // Parameter: max size of a single trace output file func (o NetworkOptions) SetTraceRollSize(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(31, b) + return o.setOpt(31, int64ToBytes(param)) } // Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time. // // Parameter: max total size of trace files func (o NetworkOptions) SetTraceMaxLogsSize(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(32, b) + return o.setOpt(32, int64ToBytes(param)) } // Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'. @@ -160,22 +151,14 @@ func (o NetworkOptions) SetBuggifyDisable() error { // // Parameter: probability expressed as a percentage between 0 and 100 func (o NetworkOptions) SetBuggifySectionActivatedProbability(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(50, b) + return o.setOpt(50, int64ToBytes(param)) } // Set the probability of an active BUGGIFY section being fired // // Parameter: probability expressed as a percentage between 0 and 100 func (o NetworkOptions) SetBuggifySectionFiredProbability(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(51, b) + return o.setOpt(51, int64ToBytes(param)) } // Set the ca bundle @@ -242,22 +225,14 @@ func (o NetworkOptions) SetEnableSlowTaskProfiling() error { // // Parameter: Max location cache entries func (o DatabaseOptions) SetLocationCacheSize(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(10, b) + return o.setOpt(10, int64ToBytes(param)) } // Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000. // // Parameter: Max outstanding watches func (o DatabaseOptions) SetMaxWatches(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(20, b) + return o.setOpt(20, int64ToBytes(param)) } // Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing. @@ -278,33 +253,21 @@ func (o DatabaseOptions) SetDatacenterId(param string) error { // // Parameter: value in milliseconds of timeout func (o DatabaseOptions) SetTransactionTimeout(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(500, b) + return o.setOpt(500, int64ToBytes(param)) } // Set a timeout in milliseconds which, when elapsed, will cause a transaction automatically to be cancelled. This sets the ``retry_limit`` option of each transaction created by this database. See the transaction option description for more information. // // Parameter: number of times to retry func (o DatabaseOptions) SetTransactionRetryLimit(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(501, b) + return o.setOpt(501, int64ToBytes(param)) } // Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. This sets the ``max_retry_delay`` option of each transaction created by this database. See the transaction option description for more information. // // Parameter: value in milliseconds of maximum delay func (o DatabaseOptions) SetTransactionMaxRetryDelay(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(502, b) + return o.setOpt(502, int64ToBytes(param)) } // Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. @@ -415,33 +378,21 @@ func (o TransactionOptions) SetLogTransaction() error { // // Parameter: value in milliseconds of timeout func (o TransactionOptions) SetTimeout(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(500, b) + return o.setOpt(500, int64ToBytes(param)) } // Set a maximum number of retries after which additional calls to ``onError`` will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Prior to API version 610, like all other transaction options, the retry limit must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the retry limit each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. // // Parameter: number of times to retry func (o TransactionOptions) SetRetryLimit(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(501, b) + return o.setOpt(501, int64ToBytes(param)) } // Set the maximum amount of backoff delay incurred in the call to ``onError`` if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay. Prior to API version 610, like all other transaction options, the maximum retry delay must be reset after a call to ``onError``. If the API version is 610 or greater, the retry limit is not reset after an ``onError`` call. Note that at all API versions, it is safe and legal to set the maximum retry delay each time the transaction begins, so most code written assuming the older behavior can be upgraded to the newer behavior without requiring any modification, and the caller is not required to implement special logic in retry loops to only conditionally set this option. // // Parameter: value in milliseconds of maximum delay func (o TransactionOptions) SetMaxRetryDelay(param int64) error { - b, e := int64ToBytes(param) - if e != nil { - return e - } - return o.setOpt(502, b) + return o.setOpt(502, int64ToBytes(param)) } // Snapshot read operations will see the results of writes done in the same transaction. This is the default behavior. @@ -493,7 +444,7 @@ const ( // Infrequently used. The client has passed a specific row limit and wants // that many rows delivered in a single batch. Because of iterator operation // in client drivers make request batches transparent to the user, consider - // “WANT_ALL“ StreamingMode instead. A row limit must be specified if this + // ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this // mode is used. StreamingModeExact StreamingMode = 1 @@ -610,15 +561,15 @@ type ErrorPredicate int const ( - // Returns “true“ if the error indicates the operations in the transactions - // should be retried because of transient error. + // Returns ``true`` if the error indicates the operations in the + // transactions should be retried because of transient error. ErrorPredicateRetryable ErrorPredicate = 50000 - // Returns “true“ if the error indicates the transaction may have succeeded, - // though not in a way the system can verify. + // Returns ``true`` if the error indicates the transaction may have + // succeeded, though not in a way the system can verify. ErrorPredicateMaybeCommitted ErrorPredicate = 50001 - // Returns “true“ if the error indicates the transaction has not committed, - // though in a way that can be retried. + // Returns ``true`` if the error indicates the transaction has not + // committed, though in a way that can be retried. ErrorPredicateRetryableNotCommitted ErrorPredicate = 50002 ) From c328b15d366a2f867f31646b608f7c8fbeba5723 Mon Sep 17 00:00:00 2001 From: "A.J. Beamon" Date: Fri, 10 May 2019 14:51:20 -0700 Subject: [PATCH 025/118] TLS was creating trace events with invalid types (containing spaces). --- FDBLibTLS/FDBLibTLSSession.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/FDBLibTLS/FDBLibTLSSession.cpp b/FDBLibTLS/FDBLibTLSSession.cpp index 680e2aae6c..352e4ae00b 100644 --- a/FDBLibTLS/FDBLibTLSSession.cpp +++ b/FDBLibTLS/FDBLibTLSSession.cpp @@ -245,11 +245,11 @@ std::tuple FDBLibTLSSession::check_verify(Referenceroots); @@ -258,31 +258,31 @@ std::tuple FDBLibTLSSession::check_verify(Referencechain, 0); if ((subject = X509_get_subject_name(cert)) == NULL) { - reason = "FDBLibTLSCertSubjectError"; + reason = "Cert subject error"; goto err; } for (auto &pair: verify->subject_criteria) { if (!match_criteria(cert, subject, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) { - reason = "FDBLibTLSCertSubjectMatchFailure"; + reason = "Cert subject match failure"; goto err; } } // Check issuer criteria. if ((issuer = X509_get_issuer_name(cert)) == NULL) { - reason = "FDBLibTLSCertIssuerError"; + reason = "Cert issuer error"; goto err; } for (auto &pair: verify->issuer_criteria) { if (!match_criteria(cert, issuer, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) { - reason = "FDBLibTLSCertIssuerMatchFailure"; + reason = "Cert issuer match failure"; goto err; } } @@ -290,12 +290,12 @@ std::tuple FDBLibTLSSession::check_verify(Referencechain, sk_X509_num(store_ctx->chain) - 1); if ((subject = X509_get_subject_name(cert)) == NULL) { - reason = "FDBLibTLSRootSubjectError"; + reason = "Root subject error"; goto err; } for (auto &pair: verify->root_criteria) { if (!match_criteria(cert, subject, pair.first, pair.second.criteria, pair.second.match_type, pair.second.location)) { - reason = "FDBLibTLSRootSubjectMatchFailure"; + reason = "Root subject match failure"; goto err; } } @@ -345,7 +345,7 @@ bool FDBLibTLSSession::verify_peer() { if (!rc) { // log the various failure reasons for (std::string reason : verify_failure_reasons) { - TraceEvent(reason.c_str(), uid).suppressFor(1.0); + TraceEvent("FDBLibTLSVerifyFailure", uid).detail("Reason", reason).suppressFor(1.0); } } From 4d32909837fc155a91c7bb183f11cbbc8c821510 Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Fri, 10 May 2019 15:22:41 -0700 Subject: [PATCH 026/118] Moved standard libraries to LDFLAGS rather than LIBS --- bindings/flow/tester/local.mk | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/flow/tester/local.mk b/bindings/flow/tester/local.mk index 2ef4fcb753..6e59625c5f 100644 --- a/bindings/flow/tester/local.mk +++ b/bindings/flow/tester/local.mk @@ -35,8 +35,7 @@ _fdb_flow_tester_clean: @rm -rf bindings/flow/bin ifeq ($(PLATFORM),linux) - fdb_flow_tester_LIBS += -ldl -lpthread -lrt - fdb_flow_tester_LDFLAGS += -static-libstdc++ -static-libgcc + fdb_flow_tester_LDFLAGS += -static-libstdc++ -static-libgcc -ldl -lpthread -lrt else ifeq ($(PLATFORM),osx) fdb_flow_tester_LDFLAGS += -lc++ endif From ab4618c3f9c7f77bbf644f46716e11cd55f84a65 Mon Sep 17 00:00:00 2001 From: "A.J. Beamon" Date: Fri, 10 May 2019 13:15:06 -0700 Subject: [PATCH 027/118] Don't access TraceEvent::eventCounts outside the network thread. Make net2liveness an atomic counter. --- flow/Net2.actor.cpp | 4 ++-- flow/Platform.cpp | 9 +++++---- flow/Trace.cpp | 5 ++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/flow/Net2.actor.cpp b/flow/Net2.actor.cpp index 6b5bd1ac42..fe081e9131 100644 --- a/flow/Net2.actor.cpp +++ b/flow/Net2.actor.cpp @@ -65,7 +65,7 @@ static_assert(currentProtocolVersion < 0x0FDB00B100000000LL, "Unexpected protoco #if defined(__linux__) #include -volatile double net2liveness = 0; +std::atomic net2liveness(0); volatile size_t net2backtraces_max = 10000; volatile void** volatile net2backtraces = NULL; @@ -689,7 +689,7 @@ void Net2::run() { } // to keep the thread liveness check happy - net2liveness = g_nondeterministic_random->random01(); + net2liveness.fetch_add(1); } #endif diff --git a/flow/Platform.cpp b/flow/Platform.cpp index a854c30d44..d303e48d80 100644 --- a/flow/Platform.cpp +++ b/flow/Platform.cpp @@ -2742,7 +2742,7 @@ extern volatile size_t net2backtraces_offset; extern volatile size_t net2backtraces_max; extern volatile bool net2backtraces_overflow; extern volatile int net2backtraces_count; -extern volatile double net2liveness; +extern std::atomic net2liveness; extern volatile thread_local int profilingEnabled; extern void initProfiling(); @@ -2789,12 +2789,13 @@ void* checkThread(void *arg) { pthread_t mainThread = *(pthread_t*)arg; free(arg); - double lastValue = net2liveness; + int64_t lastValue = net2liveness.load(); double lastSignal = 0; double logInterval = FLOW_KNOBS->SLOWTASK_PROFILING_INTERVAL; while(true) { threadSleep(FLOW_KNOBS->SLOWTASK_PROFILING_INTERVAL); - if(lastValue == net2liveness) { + int64_t currentLiveness = net2liveness.load(); + if(lastValue == currentLiveness) { double t = timer(); if(lastSignal == 0 || t - lastSignal >= logInterval) { if(lastSignal > 0) { @@ -2806,10 +2807,10 @@ void* checkThread(void *arg) { } } else { + lastValue = currentLiveness; lastSignal = 0; logInterval = FLOW_KNOBS->SLOWTASK_PROFILING_INTERVAL; } - lastValue = net2liveness; } return NULL; #else diff --git a/flow/Trace.cpp b/flow/Trace.cpp index 60eb571c86..306885fa36 100644 --- a/flow/Trace.cpp +++ b/flow/Trace.cpp @@ -921,7 +921,10 @@ TraceEvent::~TraceEvent() { severity = SevError; } - TraceEvent::eventCounts[severity/10]++; + if(isNetworkThread()) { + TraceEvent::eventCounts[severity/10]++; + } + g_traceLog.writeEvent( fields, trackingKey, severity > SevWarnAlways ); if (g_traceLog.isOpen()) { From c502ed3d152e966affcbf0487d824a9fa46472ba Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 10 May 2019 14:46:26 -1000 Subject: [PATCH 028/118] Fix a variety of problems stemming from a wait() being added to push(). And that this code was previously insufficiently tested. --- fdbrpc/AsyncFileKAIO.actor.h | 2 +- fdbrpc/sim2.actor.cpp | 7 ++++++- fdbserver/DiskQueue.actor.cpp | 32 +++++++++++++++++++++----------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/fdbrpc/AsyncFileKAIO.actor.h b/fdbrpc/AsyncFileKAIO.actor.h index d9fb6de2f8..882e112ed7 100644 --- a/fdbrpc/AsyncFileKAIO.actor.h +++ b/fdbrpc/AsyncFileKAIO.actor.h @@ -265,7 +265,7 @@ public: result = fallocate( fd, 0, 0, size); if (result != 0) { int fallocateErrCode = errno; - TraceEvent("AsyncFileKAIOAllocateError").detail("Fd",fd).detail("Filename", filename).GetLastError(); + TraceEvent("AsyncFileKAIOAllocateError").detail("Fd",fd).detail("Filename", filename).detail("Size", size).GetLastError(); if ( fallocateErrCode == EOPNOTSUPP ) { // Mark fallocate as unsupported. Try again with truncate. ctx.fallocateSupported = false; diff --git a/fdbrpc/sim2.actor.cpp b/fdbrpc/sim2.actor.cpp index 4368904a98..5bc89266f9 100644 --- a/fdbrpc/sim2.actor.cpp +++ b/fdbrpc/sim2.actor.cpp @@ -604,11 +604,16 @@ private: if (randLog) fprintf( randLog, "SFT1 %s %s %s %lld\n", self->dbgId.shortString().c_str(), self->filename.c_str(), opId.shortString().c_str(), size ); + if (size == 0) { + // KAIO will return EINVAL, as len==0 is an error. + throw io_error(); + } + if(self->delayOnWrite) wait( waitUntilDiskReady( self->diskParameters, 0 ) ); if( _chsize( self->h, (long) size ) == -1 ) { - TraceEvent(SevWarn, "SimpleFileIOError").detail("Location", 6).detail("Filename", self->filename).detail("Error", strerror(errno)).detail("Size", size).detail("Fd", self->h); + TraceEvent(SevWarn, "SimpleFileIOError").detail("Location", 6).detail("Filename", self->filename).detail("Size", size).detail("Fd", self->h).GetLastError(); throw io_error(); } diff --git a/fdbserver/DiskQueue.actor.cpp b/fdbserver/DiskQueue.actor.cpp index d48e323a09..49e46bbf4c 100644 --- a/fdbserver/DiskQueue.actor.cpp +++ b/fdbserver/DiskQueue.actor.cpp @@ -158,13 +158,13 @@ class RawDiskQueue_TwoFiles : public Tracked { public: RawDiskQueue_TwoFiles( std::string basename, std::string fileExtension, UID dbgid, int64_t fileSizeWarningLimit ) : basename(basename), fileExtension(fileExtension), onError(delayed(error.getFuture())), onStopped(stopped.getFuture()), - readingFile(-1), readingPage(-1), writingPos(-1), dbgid(dbgid), + readingFile(-1), readingPage(-1), pushlock(1), writingPos(-1), dbgid(dbgid), dbg_file0BeginSeq(0), fileExtensionBytes(SERVER_KNOBS->DISK_QUEUE_FILE_EXTENSION_BYTES), fileShrinkBytes(SERVER_KNOBS->DISK_QUEUE_FILE_SHRINK_BYTES), readingBuffer( dbgid ), readyToPush(Void()), fileSizeWarningLimit(fileSizeWarningLimit), lastCommit(Void()), isFirstCommit(true) { if (BUGGIFY) - fileExtensionBytes = 1<<10 * g_random->randomSkewedUInt32( 1, 40<<10 ); + fileExtensionBytes = _PAGE_SIZE * g_random->randomSkewedUInt32( 1, 10<<10 ); if (BUGGIFY) fileShrinkBytes = _PAGE_SIZE * g_random->randomSkewedUInt32( 1, 10<<10 ); files[0].dbgFilename = filename(0); @@ -261,6 +261,7 @@ public: int readingFile; // i if the next page after readingBuffer should be read from files[i], 2 if recovery is complete int64_t readingPage; // Page within readingFile that is the next page after readingBuffer + FlowLock pushlock; int64_t writingPos; // Position within files[1] that will be next written int64_t fileExtensionBytes; @@ -293,7 +294,7 @@ public: ACTOR static Future> replaceFile(Reference toReplace) { incrementalTruncate( toReplace ); - Reference _replacement = wait( IAsyncFileSystem::filesystem()->open( toReplace->getFilename(), IAsyncFile::OPEN_ATOMIC_WRITE_AND_CREATE | IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_UNBUFFERED, 0 ) ); + Reference _replacement = wait( IAsyncFileSystem::filesystem()->open( toReplace->getFilename(), IAsyncFile::OPEN_ATOMIC_WRITE_AND_CREATE | IAsyncFile::OPEN_CREATE | IAsyncFile::OPEN_READWRITE | IAsyncFile::OPEN_UNCACHED | IAsyncFile::OPEN_UNBUFFERED | IAsyncFile::OPEN_LOCK, 0600 ) ); state Reference replacement = _replacement; wait( replacement->sync() ); @@ -306,6 +307,11 @@ public: } ACTOR static Future push(RawDiskQueue_TwoFiles* self, StringRef pageData, vector>* toSync) { + state TrackMe trackMe(self); + + wait( self->pushlock.take(g_network->getCurrentTask(), 1) ); + state FlowLock::Releaser releaser(self->pushlock, 1); + // Write the given data to the queue files, swapping or extending them if necessary. // Don't do any syncs, but push the modified file(s) onto toSync. ASSERT( self->readingFile == 2 ); @@ -333,23 +339,28 @@ public: std::swap(self->firstPages[0], self->firstPages[1]); self->files[1].popped = 0; self->writingPos = 0; + *self->firstPages[1] = *(const Page*)pageData.begin(); const int64_t activeDataVolume = pageCeiling(self->files[0].size - self->files[0].popped + self->fileExtensionBytes + self->fileShrinkBytes); - const int64_t desiredMaxFileSize = std::max( activeDataVolume, SERVER_KNOBS->TLOG_HARD_LIMIT_BYTES * 2 ); - const bool frivolouslyTruncate = BUGGIFY_WITH_PROB(0.001); + const int64_t desiredMaxFileSize = pageCeiling( std::max( activeDataVolume, SERVER_KNOBS->TLOG_HARD_LIMIT_BYTES * 2 ) ); + const bool frivolouslyTruncate = BUGGIFY_WITH_PROB(0.1); if (self->files[1].size > desiredMaxFileSize || frivolouslyTruncate) { // Either shrink self->files[1] to the size of self->files[0], or chop off fileShrinkBytes - int64_t maxShrink = std::max( pageFloor(self->files[1].size - desiredMaxFileSize), self->fileShrinkBytes ); + int64_t maxShrink = pageFloor( std::max( self->files[1].size - desiredMaxFileSize, self->fileShrinkBytes ) ); if ((maxShrink / SERVER_KNOBS->DISK_QUEUE_FILE_EXTENSION_BYTES > - SERVER_KNOBS->DISK_QUEUE_MAX_TRUNCATE_EXTENTS) || + SERVER_KNOBS->DISK_QUEUE_MAX_TRUNCATE_EXTENTS) || (frivolouslyTruncate && g_random->random01() < 0.3)) { TEST(true); // Replacing DiskQueue file TraceEvent("DiskQueueReplaceFile", self->dbgid).detail("Filename", self->files[1].f->getFilename()).detail("OldFileSize", self->files[1].size).detail("ElidedTruncateSize", maxShrink); Reference newFile = wait( replaceFile(self->files[1].f) ); self->files[1].setFile(newFile); - self->files[1].size = 0; + waitfor.push_back( self->files[1].f->truncate( self->fileExtensionBytes ) ); + self->files[1].size = self->fileExtensionBytes; } else { + const int64_t startingSize = self->files[1].size; self->files[1].size -= std::min(maxShrink, self->files[1].size); + self->files[1].size = std::max(self->files[1].size, self->fileExtensionBytes); + TraceEvent("DiskQueueTruncate", self->dbgid).detail("Filename", self->files[1].f->getFilename()).detail("OldFileSize", startingSize).detail("NewFileSize", self->files[1].size); waitfor.push_back( self->files[1].f->truncate( self->files[1].size ) ); } } @@ -365,9 +376,8 @@ public: TraceEvent(SevWarnAlways, "DiskQueueFileTooLarge", self->dbgid).suppressFor(1.0).detail("Filename", self->filename(1)).detail("Size", self->files[1].size); } } - } - - if (self->writingPos == 0) { + } else if (self->writingPos == 0) { + // If this is the first write to a brand new disk queue file. *self->firstPages[1] = *(const Page*)pageData.begin(); } From c95d09f9fd5afb8daf0dcadd68cf0f39004cc90c Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 10 May 2019 14:53:46 -1000 Subject: [PATCH 029/118] Convert truncate(0) to truncate(4KB) on Windows. Blindly, in case Windows doesn't like 0 length truncates too. --- fdbserver/DiskQueue.actor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdbserver/DiskQueue.actor.cpp b/fdbserver/DiskQueue.actor.cpp index 49e46bbf4c..b1a12c21dd 100644 --- a/fdbserver/DiskQueue.actor.cpp +++ b/fdbserver/DiskQueue.actor.cpp @@ -287,7 +287,7 @@ public: #if defined(_WIN32) ACTOR static Future> replaceFile(Reference toReplace) { // Windows doesn't support a rename over an open file. - wait( toReplace->truncate(0) ); + wait( toReplace->truncate(4<<10) ); return toReplace; } #else From ea12a54946930a0fb5e7284574ce3a30d04517e4 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 10 May 2019 18:26:22 -1000 Subject: [PATCH 030/118] Rename DISK_QUEUE_MAX_TRUNCATE_EXTENTS -> ..._BYTES So as to not make filesystem assumptions. This knob did technically appear in (only the) 6.1.5 release, but this feature was broken 6.1.5, so thus impossible to use anyway. --- fdbserver/DiskQueue.actor.cpp | 3 +-- fdbserver/Knobs.cpp | 2 +- fdbserver/Knobs.h | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/fdbserver/DiskQueue.actor.cpp b/fdbserver/DiskQueue.actor.cpp index b1a12c21dd..5e5dffb443 100644 --- a/fdbserver/DiskQueue.actor.cpp +++ b/fdbserver/DiskQueue.actor.cpp @@ -347,8 +347,7 @@ public: if (self->files[1].size > desiredMaxFileSize || frivolouslyTruncate) { // Either shrink self->files[1] to the size of self->files[0], or chop off fileShrinkBytes int64_t maxShrink = pageFloor( std::max( self->files[1].size - desiredMaxFileSize, self->fileShrinkBytes ) ); - if ((maxShrink / SERVER_KNOBS->DISK_QUEUE_FILE_EXTENSION_BYTES > - SERVER_KNOBS->DISK_QUEUE_MAX_TRUNCATE_EXTENTS) || + if ((maxShrink > SERVER_KNOBS->DISK_QUEUE_MAX_TRUNCATE_BYTES) || (frivolouslyTruncate && g_random->random01() < 0.3)) { TEST(true); // Replacing DiskQueue file TraceEvent("DiskQueueReplaceFile", self->dbgid).detail("Filename", self->files[1].f->getFilename()).detail("OldFileSize", self->files[1].size).detail("ElidedTruncateSize", maxShrink); diff --git a/fdbserver/Knobs.cpp b/fdbserver/Knobs.cpp index 4c551d4791..e79839af5f 100644 --- a/fdbserver/Knobs.cpp +++ b/fdbserver/Knobs.cpp @@ -75,7 +75,7 @@ ServerKnobs::ServerKnobs(bool randomize, ClientKnobs* clientKnobs) { init( TLOG_SPILL_REFERENCE_MAX_BYTES_PER_BATCH, 16<<10 ); if ( randomize && BUGGIFY ) TLOG_SPILL_REFERENCE_MAX_BYTES_PER_BATCH = 500; init( DISK_QUEUE_FILE_EXTENSION_BYTES, 10<<20 ); // BUGGIFYd per file within the DiskQueue init( DISK_QUEUE_FILE_SHRINK_BYTES, 100<<20 ); // BUGGIFYd per file within the DiskQueue - init( DISK_QUEUE_MAX_TRUNCATE_EXTENTS, 1<<10 ); if ( randomize && BUGGIFY ) DISK_QUEUE_MAX_TRUNCATE_EXTENTS = 0; + init( DISK_QUEUE_MAX_TRUNCATE_BYTES, 2<<30 ); if ( randomize && BUGGIFY ) DISK_QUEUE_MAX_TRUNCATE_BYTES = 0; init( TLOG_DEGRADED_DELAY_COUNT, 5 ); init( TLOG_DEGRADED_DURATION, 5.0 ); diff --git a/fdbserver/Knobs.h b/fdbserver/Knobs.h index ee91df4ad8..119656e3eb 100644 --- a/fdbserver/Knobs.h +++ b/fdbserver/Knobs.h @@ -79,7 +79,7 @@ public: int64_t TLOG_SPILL_REFERENCE_MAX_BYTES_PER_BATCH; int64_t DISK_QUEUE_FILE_EXTENSION_BYTES; // When we grow the disk queue, by how many bytes should it grow? int64_t DISK_QUEUE_FILE_SHRINK_BYTES; // When we shrink the disk queue, by how many bytes should it shrink? - int DISK_QUEUE_MAX_TRUNCATE_EXTENTS; + int DISK_QUEUE_MAX_TRUNCATE_BYTES; // A truncate larger than this will cause the file to be replaced instead. int TLOG_DEGRADED_DELAY_COUNT; double TLOG_DEGRADED_DURATION; From 4a7e0319c7bc102ddc292bf63cb4861f07890ba9 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Fri, 10 May 2019 20:21:37 -1000 Subject: [PATCH 031/118] Refactor away pushlock. Pushing was already a serialized, sequential operation. Instead make it explicit that there are two waits as part of a push: 1. The setup work to reserve a spot on in the file 2. The work of writing and sync'ing the data And we return a Future> to force these to be done sequentially. --- fdbserver/DiskQueue.actor.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/fdbserver/DiskQueue.actor.cpp b/fdbserver/DiskQueue.actor.cpp index 5e5dffb443..3f5e2779cf 100644 --- a/fdbserver/DiskQueue.actor.cpp +++ b/fdbserver/DiskQueue.actor.cpp @@ -158,7 +158,7 @@ class RawDiskQueue_TwoFiles : public Tracked { public: RawDiskQueue_TwoFiles( std::string basename, std::string fileExtension, UID dbgid, int64_t fileSizeWarningLimit ) : basename(basename), fileExtension(fileExtension), onError(delayed(error.getFuture())), onStopped(stopped.getFuture()), - readingFile(-1), readingPage(-1), pushlock(1), writingPos(-1), dbgid(dbgid), + readingFile(-1), readingPage(-1), writingPos(-1), dbgid(dbgid), dbg_file0BeginSeq(0), fileExtensionBytes(SERVER_KNOBS->DISK_QUEUE_FILE_EXTENSION_BYTES), fileShrinkBytes(SERVER_KNOBS->DISK_QUEUE_FILE_SHRINK_BYTES), readingBuffer( dbgid ), readyToPush(Void()), fileSizeWarningLimit(fileSizeWarningLimit), lastCommit(Void()), isFirstCommit(true) @@ -261,7 +261,6 @@ public: int readingFile; // i if the next page after readingBuffer should be read from files[i], 2 if recovery is complete int64_t readingPage; // Page within readingFile that is the next page after readingBuffer - FlowLock pushlock; int64_t writingPos; // Position within files[1] that will be next written int64_t fileExtensionBytes; @@ -302,16 +301,11 @@ public: } #endif - Future push(StringRef pageData, vector>* toSync) { + Future> push(StringRef pageData, vector>* toSync) { return push( this, pageData, toSync ); } - ACTOR static Future push(RawDiskQueue_TwoFiles* self, StringRef pageData, vector>* toSync) { - state TrackMe trackMe(self); - - wait( self->pushlock.take(g_network->getCurrentTask(), 1) ); - state FlowLock::Releaser releaser(self->pushlock, 1); - + ACTOR static Future> push(RawDiskQueue_TwoFiles* self, StringRef pageData, vector>* toSync) { // Write the given data to the queue files, swapping or extending them if necessary. // Don't do any syncs, but push the modified file(s) onto toSync. ASSERT( self->readingFile == 2 ); @@ -387,8 +381,7 @@ public: waitfor.push_back( self->files[1].f->write( pageData.begin(), pageData.size(), self->writingPos ) ); self->writingPos += pageData.size(); - wait( waitForAll(waitfor) ); - return Void(); + return waitForAll(waitfor); } ACTOR static UNCANCELLABLE Future pushAndCommit(RawDiskQueue_TwoFiles* self, StringRef pageData, StringBuffer* pageMem, uint64_t poppedPages) { @@ -415,11 +408,11 @@ public: TEST( pageData.size() > sizeof(Page) ); // push more than one page of data - Future pushed = self->push( pageData, &syncFiles ); + Future pushed = wait( self->push( pageData, &syncFiles ) ); pushing.send(Void()); - wait( pushed ); ASSERT( syncFiles.size() >= 1 && syncFiles.size() <= 2 ); TEST(2==syncFiles.size()); // push spans both files + wait( pushed ); delete pageMem; pageMem = 0; From 0840271ddca5f61f104a8b945e7d34dcbe941dd5 Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Sat, 11 May 2019 07:52:51 -0700 Subject: [PATCH 032/118] Added dos2unix package to build rpms --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index b710225fb5..9efca4b0f0 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -9,7 +9,7 @@ RUN yum install -y yum-utils &&\ yum -y install centos-release-scl epel-release &&\ yum -y install devtoolset-7 mono-core java-1.8.0-openjdk-devel \ rh-python36-python-devel rh-ruby24 golang python27 \ - rpm-build debbuild python-pip npm &&\ + rpm-build debbuild python-pip npm dos2unix &&\ pip install boto3==1.1.1 USER root From cacd66fdde00c7497c4415ce3dbce36541c97a09 Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Sat, 11 May 2019 07:53:22 -0700 Subject: [PATCH 033/118] Ensure that virtualenv is starting via python2 --- documentation/sphinx/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/sphinx/Makefile b/documentation/sphinx/Makefile index d23b136f1f..3aab160be2 100644 --- a/documentation/sphinx/Makefile +++ b/documentation/sphinx/Makefile @@ -70,7 +70,7 @@ buildsphinx: cd $(BUILDDIR); \ curl -OL $(VENV_URL); \ tar zxvf $(VENV_VERSION).tar.gz; \ - ./$(VENV_VERSION)/virtualenv.py venv; \ + python2 ./$(VENV_VERSION)/virtualenv.py venv; \ fi . $(VENVDIR)/bin/activate && \ cp .pip.conf $(VENVDIR)/pip.conf && \ From be571d3fd456ce6c2528a339c66f67a511b43136 Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Sat, 11 May 2019 08:01:32 -0700 Subject: [PATCH 034/118] Changed volume mount for building docs in order to shorten directory length --- build/docker-compose.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/docker-compose.yaml b/build/docker-compose.yaml index c19fa81337..28dea93408 100644 --- a/build/docker-compose.yaml +++ b/build/docker-compose.yaml @@ -26,6 +26,9 @@ services: build-docs: <<: *build-setup + volumes: + - ..:/foundationdb + working_dir: /foundationdb command: scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash -c 'make -j "$${MAKEJOBS}" docpackage' From 59a9030ed64a261f61a904654017445b3ed9aa80 Mon Sep 17 00:00:00 2001 From: Evan Tschannen Date: Sun, 12 May 2019 19:02:09 -0700 Subject: [PATCH 035/118] updated documentation for 6.1.6 --- documentation/sphinx/source/downloads.rst | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/documentation/sphinx/source/downloads.rst b/documentation/sphinx/source/downloads.rst index d4bb3bb93b..63a25896f6 100644 --- a/documentation/sphinx/source/downloads.rst +++ b/documentation/sphinx/source/downloads.rst @@ -10,38 +10,38 @@ macOS The macOS installation package is supported on macOS 10.7+. It includes the client and (optionally) the server. -* `FoundationDB-6.1.5.pkg `_ +* `FoundationDB-6.1.6.pkg `_ Ubuntu ------ The Ubuntu packages are supported on 64-bit Ubuntu 12.04+, but beware of the Linux kernel bug in Ubuntu 12.x. -* `foundationdb-clients-6.1.5-1_amd64.deb `_ -* `foundationdb-server-6.1.5-1_amd64.deb `_ (depends on the clients package) +* `foundationdb-clients-6.1.6-1_amd64.deb `_ +* `foundationdb-server-6.1.6-1_amd64.deb `_ (depends on the clients package) RHEL/CentOS EL6 --------------- The RHEL/CentOS EL6 packages are supported on 64-bit RHEL/CentOS 6.x. -* `foundationdb-clients-6.1.5-1.el6.x86_64.rpm `_ -* `foundationdb-server-6.1.5-1.el6.x86_64.rpm `_ (depends on the clients package) +* `foundationdb-clients-6.1.6-1.el6.x86_64.rpm `_ +* `foundationdb-server-6.1.6-1.el6.x86_64.rpm `_ (depends on the clients package) RHEL/CentOS EL7 --------------- The RHEL/CentOS EL7 packages are supported on 64-bit RHEL/CentOS 7.x. -* `foundationdb-clients-6.1.5-1.el7.x86_64.rpm `_ -* `foundationdb-server-6.1.5-1.el7.x86_64.rpm `_ (depends on the clients package) +* `foundationdb-clients-6.1.6-1.el7.x86_64.rpm `_ +* `foundationdb-server-6.1.6-1.el7.x86_64.rpm `_ (depends on the clients package) Windows ------- The Windows installer is supported on 64-bit Windows XP and later. It includes the client and (optionally) the server. -* `foundationdb-6.1.5-x64.msi `_ +* `foundationdb-6.1.6-x64.msi `_ API Language Bindings ===================== @@ -58,18 +58,18 @@ On macOS and Windows, the FoundationDB Python API bindings are installed as part If you need to use the FoundationDB Python API from other Python installations or paths, download the Python package: -* `foundationdb-6.1.5.tar.gz `_ +* `foundationdb-6.1.6.tar.gz `_ Ruby 1.9.3/2.0.0+ ----------------- -* `fdb-6.1.5.gem `_ +* `fdb-6.1.6.gem `_ Java 8+ ------- -* `fdb-java-6.1.5.jar `_ -* `fdb-java-6.1.5-javadoc.jar `_ +* `fdb-java-6.1.6.jar `_ +* `fdb-java-6.1.6-javadoc.jar `_ Go 1.1+ ------- From 84f8381905324d6082abe2fda7d3938117f72c04 Mon Sep 17 00:00:00 2001 From: Evan Tschannen Date: Sun, 12 May 2019 19:06:04 -0700 Subject: [PATCH 036/118] update installer WIX GUID following release --- packaging/msi/FDBInstaller.wxs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/msi/FDBInstaller.wxs b/packaging/msi/FDBInstaller.wxs index 9480cc2036..d29331e8c1 100644 --- a/packaging/msi/FDBInstaller.wxs +++ b/packaging/msi/FDBInstaller.wxs @@ -32,7 +32,7 @@ Date: Sun, 12 May 2019 20:08:50 -0700 Subject: [PATCH 037/118] update versions target to 6.1.7 --- versions.target | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.target b/versions.target index 20408dae1b..872c948e7b 100644 --- a/versions.target +++ b/versions.target @@ -1,7 +1,7 @@ - 6.1.6 + 6.1.7 6.1 From 966668905e54d0cb0e69d62d7d11d95b882630df Mon Sep 17 00:00:00 2001 From: Evan Tschannen Date: Sun, 12 May 2019 20:08:50 -0700 Subject: [PATCH 038/118] update installer WIX GUID following release --- packaging/msi/FDBInstaller.wxs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/msi/FDBInstaller.wxs b/packaging/msi/FDBInstaller.wxs index d29331e8c1..9947b6138a 100644 --- a/packaging/msi/FDBInstaller.wxs +++ b/packaging/msi/FDBInstaller.wxs @@ -32,7 +32,7 @@ Date: Sun, 12 May 2019 20:14:57 -0700 Subject: [PATCH 039/118] updated release notes header for 6.1.6 --- documentation/sphinx/source/release-notes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/sphinx/source/release-notes.rst b/documentation/sphinx/source/release-notes.rst index 31613f92fd..79720284ae 100644 --- a/documentation/sphinx/source/release-notes.rst +++ b/documentation/sphinx/source/release-notes.rst @@ -2,7 +2,7 @@ Release Notes ############# -6.1.5 +6.1.6 ===== Features From 067cdf9cdec57d803eece14e86ce8a235ae33585 Mon Sep 17 00:00:00 2001 From: Nikolas Ioannou Date: Mon, 13 May 2019 08:50:04 +0200 Subject: [PATCH 040/118] Simplified cache eviction policy knob arg check. --- fdbserver/fdbserver.actor.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fdbserver/fdbserver.actor.cpp b/fdbserver/fdbserver.actor.cpp index e8148e5927..44c003d4df 100644 --- a/fdbserver/fdbserver.actor.cpp +++ b/fdbserver/fdbserver.actor.cpp @@ -1427,10 +1427,8 @@ int main(int argc, char* argv[]) { } if (!serverKnobs->setKnob("server_mem_limit", std::to_string(memLimit))) ASSERT(false); - if (EvictablePageCache::RANDOM != EvictablePageCache::evictionPolicyStringToEnum(flowKnobs->CACHE_EVICTION_POLICY) && - EvictablePageCache::LRU != EvictablePageCache::evictionPolicyStringToEnum(flowKnobs->CACHE_EVICTION_POLICY)) { - ASSERT(false); - } + // evictionPolicyStringToEnum will throw an exception if the string is not recognized as a valid + EvictablePageCache::evictionPolicyStringToEnum(flowKnobs->CACHE_EVICTION_POLICY); if (role == SkipListTest) { skipListTest(); From 17a11b53f0a7ec0dea4603c668508653dc0a268a Mon Sep 17 00:00:00 2001 From: Alvin Moore Date: Mon, 13 May 2019 12:22:52 -0700 Subject: [PATCH 041/118] Upgraded to devtoolset-8 to support compilation of c++17 --- build/Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 9efca4b0f0..3a0f61908a 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -7,8 +7,8 @@ ENV DOCKER_IMAGEVER=0.1.3 RUN yum install -y yum-utils &&\ yum-config-manager --enable rhel-server-rhscl-7-rpms &&\ yum -y install centos-release-scl epel-release &&\ - yum -y install devtoolset-7 mono-core java-1.8.0-openjdk-devel \ - rh-python36-python-devel rh-ruby24 golang python27 \ + yum -y install devtoolset-8 java-1.8.0-openjdk-devel \ + rh-python36-python-devel mono-core rh-ruby24 golang python27 \ rpm-build debbuild python-pip npm dos2unix &&\ pip install boto3==1.1.1 @@ -36,9 +36,9 @@ RUN curl -L https://github.com/Kitware/CMake/releases/download/v3.13.4/cmake-3.1 RUN curl -L https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.8.2.tar.gz > /tmp/libressl.tar.gz &&\ cd /tmp && echo "b8cb31e59f1294557bfc80f2a662969bc064e83006ceef0574e2553a1c254fd5 libressl.tar.gz" > libressl-sha.txt &&\ sha256sum -c libressl-sha.txt && tar xf libressl.tar.gz &&\ - cd libressl-2.8.2 && cd /tmp/libressl-2.8.2 && scl enable devtoolset-7 -- ./configure --prefix=/usr/local/stow/libressl CFLAGS="-fPIC -O3" --prefix=/usr/local &&\ - cd /tmp/libressl-2.8.2 && scl enable devtoolset-7 -- make -j`nproc` install &&\ + cd libressl-2.8.2 && cd /tmp/libressl-2.8.2 && scl enable devtoolset-8 -- ./configure --prefix=/usr/local/stow/libressl CFLAGS="-fPIC -O3" --prefix=/usr/local &&\ + cd /tmp/libressl-2.8.2 && scl enable devtoolset-8 -- make -j`nproc` install &&\ rm -rf /tmp/libressl-2.8.2 /tmp/libressl.tar.gz ENV JAVA_HOME=/usr/lib/jvm/java-1.8.0 -CMD scl enable devtoolset-7 python27 rh-python36 rh-ruby24 -- bash +CMD scl enable devtoolset-8 python27 rh-python36 rh-ruby24 -- bash From 8e1927a5cba9f7c7317f89cba0960d9b2ff400d8 Mon Sep 17 00:00:00 2001 From: "A.J. Beamon" Date: Mon, 13 May 2019 13:25:01 -0700 Subject: [PATCH 042/118] Rename trace event field to avoid duplicating 'Time' field. --- flow/ActorCollection.actor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flow/ActorCollection.actor.cpp b/flow/ActorCollection.actor.cpp index 23eae8a997..0a013f8e01 100644 --- a/flow/ActorCollection.actor.cpp +++ b/flow/ActorCollection.actor.cpp @@ -126,7 +126,7 @@ TEST_CASE("/flow/TraceEvent") { wait(delay(0)); } TraceEvent("TraceDuration") - .detail("Time", g_network->now() - startTime); + .detail("Duration", g_network->now() - startTime); startTime = g_network->now(); for (i = 0; i < 1000000; ++i) { for (unsigned j = 0; j < 100; ++j) { @@ -142,7 +142,7 @@ TEST_CASE("/flow/TraceEvent") { wait(delay(0)); } TraceEvent("TraceDuration") - .detail("Time", g_network->now() - startTime); + .detail("Duration", g_network->now() - startTime); printf("benchmark done\n"); wait(delay(10)); return Void(); From 8bbd28315e9dc66fa14c284ac66c7bcfe2243ba8 Mon Sep 17 00:00:00 2001 From: mpilman Date: Thu, 24 Jan 2019 13:54:28 -0800 Subject: [PATCH 043/118] Added Object serializer (does not yet compile) --- cmake/ConfigureCompiler.cmake | 2 +- flow/CMakeLists.txt | 2 + flow/ObjectSerializer.h | 128 ++++ flow/flat_buffers.cpp | 513 ++++++++++++++ flow/flat_buffers.h | 1241 +++++++++++++++++++++++++++++++++ 5 files changed, 1885 insertions(+), 1 deletion(-) create mode 100644 flow/ObjectSerializer.h create mode 100644 flow/flat_buffers.cpp create mode 100644 flow/flat_buffers.h diff --git a/cmake/ConfigureCompiler.cmake b/cmake/ConfigureCompiler.cmake index 03af9c102e..e8f155a019 100644 --- a/cmake/ConfigureCompiler.cmake +++ b/cmake/ConfigureCompiler.cmake @@ -95,7 +95,7 @@ else() -mmmx -mavx -msse4.2) - add_compile_options($<$:-std=c++11>) + add_compile_options($<$:-std=c++17>) if (USE_VALGRIND) add_compile_options(-DVALGRIND -DUSE_VALGRIND) endif() diff --git a/flow/CMakeLists.txt b/flow/CMakeLists.txt index 4af896d817..4d24b168c5 100644 --- a/flow/CMakeLists.txt +++ b/flow/CMakeLists.txt @@ -63,6 +63,8 @@ set(FLOW_SRCS XmlTraceLogFormatter.cpp actorcompiler.h error_definitions.h + flat_buffers.h + flat_buffers.cpp flow.cpp flow.h genericactors.actor.cpp diff --git a/flow/ObjectSerializer.h b/flow/ObjectSerializer.h new file mode 100644 index 0000000000..03e68905a2 --- /dev/null +++ b/flow/ObjectSerializer.h @@ -0,0 +1,128 @@ +/* + * serialize.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flow/Error.h" +#include "flow/Arena.h" +#include "flow/flat_buffers.h" + +template +struct LoadContext { + Ar& ar; + std::vector> doAfter; + LoadContext(Ar& ar) : ar(ar) {} + Arena& arena() { return ar.arena(); } + + const uint8_t* tryReadZeroCopy(const uint8_t* ptr, unsigned len) { + if constexpr (Ar::ownsUnderlyingMemory) { + return ptr; + } else { + if (len == 0) return nullptr; + uint8_t* dat = new (arena()) uint8_t[len]; + std::copy(ptr, ptr + len, dat); + return dat; + } + } + + void done() const { + for (auto& f : doAfter) { + f(); + } + } + void addArena(Arena& arena) { arena = ar.arena(); } +}; + +template +class _ObjectReader { +public: + template + void deserialize(flat_buffers::FileIdentifier file_identifier, Items&... items) { + const uint8_t* data = static_cast(this)->data(); + LoadContext context(*static_cast(this)); + ASSERT(flat_buffers::read_file_identifier(data) == file_identifier); + flat_buffers::load_members(data, context, items...); + context.done(); + } + + template + void deserialize(Item& item) { + deserialize(flat_buffers::FileIdentifierFor::value, item); + } +}; + +class ObjectReader : _ObjectReader { +public: + static constexpr bool ownsUnderlyingMemory = false; + + ObjectReader(const uint8_t* data) : _data(data) {} + + const uint8_t* data() { return _data; } + + Arena& arena() { return _arena; } + +private: + const uint8_t* _data; + Arena _arena; +}; + +class ArenaObjectReader : _ObjectReader { +public: + static constexpr bool ownsUnderlyingMemory = true; + + ArenaObjectReader(Arena const& arena, const StringRef& input) : _data(input.begin()), _arena(arena) {} + + const uint8_t* data() { return _data; } + + Arena& arena() { return _arena; } + +private: + const uint8_t* _data; + Arena _arena; +}; + +class ObjectWriter { +public: + template + void serialize(flat_buffers::FileIdentifier file_identifier, Items const&... items) { + ASSERT(data = nullptr); // object serializer can only serialize one object + int allocations = 0; + auto allocator = [this, &allocations](size_t size_) { + ++allocations; + size = size_; + data = new uint8_t[size]; + return data; + }; + auto res = flat_buffers::save_members(allocator, file_identifier, items...); + ASSERT(allocations == 1); + } + + template + void serialize(Item const& item) { + serialize(flat_buffers::FileIdentifierFor::value, item); + } + +private: + uint8_t* data = nullptr; + int size = 0; +}; + +template +std::enable_if serializer(Visitor& visitor, Items&... items) { + visitor(items...); +} diff --git a/flow/flat_buffers.cpp b/flow/flat_buffers.cpp new file mode 100644 index 0000000000..29e2b6dd84 --- /dev/null +++ b/flow/flat_buffers.cpp @@ -0,0 +1,513 @@ +/* + * serialize.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flat_buffers.h" +#include "UnitTest.h" +#include "Arena.h" +#include "serialize.h" + +#include +#include +#include + +namespace flat_buffers { + +namespace detail { + +bool TraverseMessageTypes::vtableGeneratedBefore(const std::type_index& idx) { + return !f.known_types.insert(idx).second; +} + +VTable generate_vtable(size_t numMembers, const std::vector& members, + const std::vector& alignments) { + if (numMembers == 0) { + return VTable{ 4, 4 }; + } + // first is index, second is size + std::vector> indexed; + indexed.reserve(members.size()); + for (unsigned i = 0; i < members.size(); ++i) { + if (members[i] > 0) { + indexed.emplace_back(i, members[i]); + } + } + std::stable_sort(indexed.begin(), indexed.end(), + [](const std::pair& lhs, const std::pair& rhs) { + return lhs.second > rhs.second; + }); + VTable result; + result.resize(members.size() + 2); + // size of the vtable is + // - 2 bytes per member + + // - 2 bytes for the size entry + + // - 2 bytes for the size of the object + result[0] = 2 * members.size() + 4; + int offset = 0; + for (auto p : indexed) { + auto align = alignments[p.first]; + auto& res = result[p.first + 2]; + res = offset % align == 0 ? offset : ((offset / align) + 1) * align; + offset = res + p.second; + res += 4; + } + result[1] = offset + 4; + return result; +} + +} // namespace detail + +namespace unit_tests { + +TEST_CASE("flow/FlatBuffers/test") { + auto* vtable1 = detail::get_vtable(); + auto* vtable2 = detail::get_vtable(); + auto* vtable3 = detail::get_vtable(); + ASSERT(vtable1 != vtable2); + ASSERT(vtable2 == vtable3); + ASSERT(vtable1->size() == 3); + ASSERT(vtable2->size() == 7); + ASSERT((*vtable2)[0] == 14); + ASSERT((*vtable2)[1] == 22); + ASSERT(((*vtable2)[4] - 4) % 4 == 0); + ASSERT(((*vtable2)[5] - 4) % 8 == 0); + ASSERT(((*vtable2)[6] - 4) % 4 == 0); + return Void(); +} + +TEST_CASE("flow/FlatBuffers/emptyVtable") { + auto* vtable = detail::get_vtable<>(); + ASSERT((*vtable)[0] == 4); + ASSERT((*vtable)[1] == 4); + return Void(); +} + +struct Table2 { + std::string m_p = {}; + bool m_ujrnpumbfvc = {}; + int64_t m_iwgxxt = {}; + int64_t m_tjkuqo = {}; + int16_t m_ed = {}; + template + void serialize(Archiver& ar) { + return flat_buffers::serializer(ar, m_p, m_ujrnpumbfvc, m_iwgxxt, m_tjkuqo, m_ed); + } +}; + +struct Table3 { + uint16_t m_asbehdlquj = {}; + uint16_t m_k = {}; + uint16_t m_jib = {}; + int64_t m_n = {}; + template + void serialize(Archiver& ar) { + return flat_buffers::serializer(ar, m_asbehdlquj, m_k, m_jib, m_n); + } +}; + +TEST_CASE("flow/FlatBuffers/vtable2") { + const auto& vtable = + *detail::get_vtable, Table2, Table3>(); + ASSERT(!(vtable[2] <= vtable[4] && vtable[4] < vtable[2] + 8)); + return Void(); +} + +struct Nested2 { + uint8_t a; + std::vector b; + int c; + template + void serialize(Archiver& ar) { + return flat_buffers::serializer(ar, a, b, c); + } + + friend bool operator==(const Nested2& lhs, const Nested2& rhs) { + return lhs.a == rhs.a && lhs.b == rhs.b && lhs.c == rhs.c; + } +}; + +struct Nested { + uint8_t a; + std::string b; + Nested2 nested; + std::vector c; + template + void serialize(Archiver& ar) { + return flat_buffers::serializer(ar, a, b, nested, c); + } +}; + +struct Root { + uint8_t a; + std::vector b; + Nested c; + template + void serialize(Archiver& ar) { + return flat_buffers::serializer(ar, a, b, c); + } +}; + +TEST_CASE("flow/FlatBuffers/collectVTables") { + Root root; + const auto* vtables = detail::get_vtableset(root); + ASSERT(vtables == detail::get_vtableset(root)); + ASSERT(vtables->offsets.size() == 3); + const auto& root_vtable = *detail::get_vtable, Nested>(); + const auto& nested_vtable = *detail::get_vtable, int>(); + int root_offset = vtables->offsets.at(&root_vtable); + int nested_offset = vtables->offsets.at(&nested_vtable); + ASSERT(!memcmp((uint8_t*)&root_vtable[0], &vtables->packed_tables[root_offset], root_vtable.size())); + ASSERT(!memcmp((uint8_t*)&nested_vtable[0], &vtables->packed_tables[nested_offset], nested_vtable.size())); + return Void(); +} + +void print_buffer(const uint8_t* out, int len) { + std::cout << std::hex << std::setfill('0'); + for (int i = 0; i < len; ++i) { + if (i % 8 == 0) { + std::cout << std::endl; + std::cout << std::setw(4) << i << ": "; + } + std::cout << std::setw(2) << (int)out[i] << " "; + } + std::cout << std::endl << std::dec; +} + +struct Arena { + std::vector> allocated; + ~Arena() { + for (auto b : allocated) { + delete[] b.first; + } + } + + uint8_t* operator()(size_t sz) { + auto res = new uint8_t[sz]; + allocated.emplace_back(res, sz); + return res; + } + + size_t get_size(const uint8_t* ptr) const { + for (auto& p : allocated) { + if (p.first == ptr) { + return p.second; + } + } + return -1; + } +}; + +struct DummyContext { + Arena a; + Arena& arena() { return a; } +}; + +TEST_CASE("flow/FlatBuffers/serializeDeserializeRoot") { + Root root{ 1, + { { 13, { "ghi", "jkl" }, 15 }, { 16, { "mnop", "qrstuv" }, 18 } }, + { 3, "hello", { 6, { "abc", "def" }, 8 }, { 10, 11, 12 } } }; + Root root2 = root; + Arena arena; + auto out = flat_buffers::detail::save(arena, root, flat_buffers::FileIdentifier{}); + + ASSERT(root.a == root2.a); + ASSERT(root.b == root2.b); + ASSERT(root.c.a == root2.c.a); + ASSERT(root.c.b == root2.c.b); + ASSERT(root.c.nested.a == root2.c.nested.a); + ASSERT(root.c.nested.b == root2.c.nested.b); + ASSERT(root.c.nested.c == root2.c.nested.c); + ASSERT(root.c.c == root2.c.c); + + root2 = {}; + DummyContext context; + flat_buffers::detail::load(root2, out, context); + + ASSERT(root.a == root2.a); + ASSERT(root.b == root2.b); + ASSERT(root.c.a == root2.c.a); + ASSERT(root.c.b == root2.c.b); + ASSERT(root.c.nested.a == root2.c.nested.a); + ASSERT(root.c.nested.b == root2.c.nested.b); + ASSERT(root.c.nested.c == root2.c.nested.c); + ASSERT(root.c.c == root2.c.c); + return Void(); +} + +TEST_CASE("flow/FlatBuffers/serializeDeserializeMembers") { + Root root{ 1, + { { 13, { "ghi", "jkl" }, 15 }, { 16, { "mnop", "qrstuv" }, 18 } }, + { 3, "hello", { 6, { "abc", "def" }, 8 }, { 10, 11, 12 } } }; + Root root2 = root; + Arena arena; + const auto* out = save_members(arena, FileIdentifier{}, root.a, root.b, root.c); + + ASSERT(root.a == root2.a); + ASSERT(root.b == root2.b); + ASSERT(root.c.a == root2.c.a); + ASSERT(root.c.b == root2.c.b); + ASSERT(root.c.nested.a == root2.c.nested.a); + ASSERT(root.c.nested.b == root2.c.nested.b); + ASSERT(root.c.nested.c == root2.c.nested.c); + ASSERT(root.c.c == root2.c.c); + + root2 = {}; + DummyContext context; + load_members(out, context, root2.a, root2.b, root2.c); + + ASSERT(root.a == root2.a); + ASSERT(root.b == root2.b); + ASSERT(root.c.a == root2.c.a); + ASSERT(root.c.b == root2.c.b); + ASSERT(root.c.nested.a == root2.c.nested.a); + ASSERT(root.c.nested.b == root2.c.nested.b); + ASSERT(root.c.nested.c == root2.c.nested.c); + ASSERT(root.c.c == root2.c.c); + return Void(); +} + +} // namespace unit_tests + +template +struct union_like_traits> : std::true_type { + using Member = boost::variant; + using alternatives = pack; + static uint8_t index(const Member& variant) { return variant.which(); } + static bool empty(const Member& variant) { return false; } + + template + static const index_t& get(const Member& variant) { + return boost::get>(variant); + } + + template + static const void assign(Member& member, const Alternative& a) { + static_assert(std::is_same_v, Alternative>); + member = a; + } +}; + +namespace unit_tests { + +TEST_CASE("flow/FlatBuffers/variant") { + using V = boost::variant; + V v1; + V v2; + Arena arena; + DummyContext context; + const uint8_t* out; + + v1 = 1; + out = save_members(arena, FileIdentifier{}, v1); + // print_buffer(out, arena.get_size(out)); + load_members(out, context, v2); + ASSERT(boost::get(v1) == boost::get(v2)); + + v1 = 1.0; + out = save_members(arena, FileIdentifier{}, v1); + // print_buffer(out, arena.get_size(out)); + load_members(out, context, v2); + ASSERT(boost::get(v1) == boost::get(v2)); + + v1 = Nested2{ 1, { "abc", "def" }, 2 }; + out = save_members(arena, FileIdentifier{}, v1); + // print_buffer(out, arena.get_size(out)); + load_members(out, context, v2); + ASSERT(boost::get(v1).a == boost::get(v2).a); + ASSERT(boost::get(v1).b == boost::get(v2).b); + ASSERT(boost::get(v1).c == boost::get(v2).c); + return Void(); +} + +TEST_CASE("flow/FlatBuffers/vectorBool") { + std::vector x1 = { true, false, true, false, true }; + std::vector x2; + Arena arena; + DummyContext context; + const uint8_t* out; + + out = save_members(arena, FileIdentifier{}, x1); + // print_buffer(out, arena.get_size(out)); + load_members(out, context, x2); + ASSERT(x1 == x2); + return Void(); +} + +struct DynamicSizeThingy { + std::string x; + mutable int saves = 0; +}; + +} // namespace unit_tests + +template <> +struct dynamic_size_traits : std::true_type { +private: + using T = unit_tests::DynamicSizeThingy; + +public: + static WriteRawMemory save(const T& t) { + ++t.saves; + T* t2 = new T(t); + return { { ownedPtr(reinterpret_cast(t2->x.data()), [t2](auto*) { delete t2; }), + t2->x.size() } }; + } + + // Context is an arbitrary type that is plumbed by reference throughout the + // load call tree. + template + static void load(const uint8_t* p, size_t n, T& t, Context&) { + t.x.assign(reinterpret_cast(p), n); + } +}; + +namespace unit_tests { + +TEST_CASE("flow/FlatBuffers/dynamic_size_owned") { + DynamicSizeThingy x1 = { "abcdefg" }; + DynamicSizeThingy x2; + Arena arena; + DummyContext context; + const uint8_t* out; + + out = save_members(arena, FileIdentifier{}, x1); + ASSERT(x1.saves == 1); + // print_buffer(out, arena.get_size(out)); + load_members(out, context, x2); + ASSERT(x1.x == x2.x); + return Void(); +} + +struct Y1 { + int a; + + template + void serialize(Archiver& ar) { + return flat_buffers::serializer(ar, a); + } +}; + +struct Y2 { + int a; + boost::variant b; + + template + void serialize(Archiver& ar) { + return flat_buffers::serializer(ar, a, b); + } +}; + +template +struct X { + int a; + Y b; + int c; + + template + void serialize(Archiver& ar) { + return flat_buffers::serializer(ar, a, b, c); + } +}; + +TEST_CASE("flow/FlatBuffers/nestedCompat") { + X x1 = { 1, { 2 }, 3 }; + X x2; + Arena arena; + DummyContext context; + const uint8_t* out; + + out = save_members(arena, FileIdentifier{}, x1); + load_members(out, context, x2); + ASSERT(x1.a == x2.a); + ASSERT(x1.b.a == x2.b.a); + ASSERT(x1.c == x2.c); + + x1 = {}; + x2.b.b = 4; + + out = save_members(arena, FileIdentifier{}, x2); + load_members(out, context, x1); + ASSERT(x1.a == x2.a); + ASSERT(x1.b.a == x2.b.a); + ASSERT(x1.c == x2.c); + return Void(); +} + +TEST_CASE("flow/FlatBuffers/struct") { + std::vector> x1 = { { 1, true, 2 }, { 3, false, 4 } }; + decltype(x1) x2; + Arena arena; + DummyContext context; + const uint8_t* out; + + out = save_members(arena, FileIdentifier{}, x1); + // print_buffer(out, arena.get_size(out)); + load_members(out, context, x2); + ASSERT(x1 == x2); + return Void(); +} + +TEST_CASE("flow/FlatBuffers/file_identifier") { + Arena arena; + const uint8_t* out; + constexpr FileIdentifier file_identifier{ 1234 }; + out = save_members(arena, file_identifier); + // print_buffer(out, arena.get_size(out)); + ASSERT(read_file_identifier(out) == file_identifier); + return Void(); +} + +TEST_CASE("flow/FlatBuffers/VectorRef") { + // this test tests a few weird memory properties of + // serialized arenas. This is why it uses weird scoping + + // first we construct the data to serialize/deserialize + // so we can compare it afterwards + std::vector src; + src.push_back("Foo"); + src.push_back("Bar"); + ::Arena vecArena; + VectorRef outVec; + { + ::Arena readerArena; + StringRef serializedVector; + { + ::Arena arena; + VectorRef vec; + for (const auto& str : src) { + vec.push_back(arena, str); + } + BinaryWriter writer(IncludeVersion()); + ::serialize_fake_root(writer, FileIdentifierFor::value, arena, vec); + serializedVector = StringRef(readerArena, writer.toStringRef()); + } + ArenaReader reader(readerArena, serializedVector, IncludeVersion()); + ::serialize_fake_root(reader, FileIdentifierFor::value, vecArena, outVec); + } + ASSERT(src.size() == outVec.size()); + for (int i = 0; i < src.size(); ++i) { + auto str = outVec[i].toString(); + ASSERT(str == src[i]); + } + return Void(); +} + +} // namespace unit_tests + +} // namespace flat_buffers diff --git a/flow/flat_buffers.h b/flow/flat_buffers.h new file mode 100644 index 0000000000..adcbc6b8a9 --- /dev/null +++ b/flow/flat_buffers.h @@ -0,0 +1,1241 @@ +/* + * flat_buffers.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace flat_buffers { + +template +struct pack {}; + +template , class...> +struct concat { + using type = T; +}; +template +struct concat, pack, Ts...> : concat, Ts...> {}; +template +using concat_t = typename concat::type; + +template +constexpr auto pack_size(pack) { + return sizeof...(Ts); +} + +template +struct index; + +template +struct index> { + using type = typename index>::type; +}; + +template +struct index<0, pack> { + using type = T; +}; + +template +using index_t = typename index::type; + +constexpr int RightAlign(int offset, int alignment) { + return offset % alignment == 0 ? offset : ((offset / alignment) + 1) * alignment; +} + +using FileIdentifier = uint32_t; + +template +struct FileIdentifierFor { + constexpr static FileIdentifier value = T::file_identifier; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 1; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 2; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 3; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 4; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 5; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 6; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 7; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 8; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 9; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 10; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 11; +}; + +template <> +struct FileIdentifierFor { + constexpr static flat_buffers::FileIdentifier value = 7266212; +}; + +template <> +struct FileIdentifierFor { + constexpr static flat_buffers::FileIdentifier value = 9348150; +}; + +template +struct FileIdentifierFor> { + constexpr static FileIdentifier value = FileIdentifierFor>::value; +}; + +template +struct FileIdentifierFor> { + constexpr static FileIdentifier value = FileIdentifierFor::value ^ FileIdentifierFor::value; +}; + +template +struct FileIdentifierFor> { + constexpr static FileIdentifier value = (0x10 << 24) | FileIdentifierFor::value; +}; + +template +struct FileIdentifierFor> { + constexpr static FileIdentifier value = 15694229; +}; + +template +struct object_construction { + T obj; + + object_construction() : obj() {} + object_construction(const T& o) : obj(o) {} + object_construction(T&& o) : obj(std::move(o)) {} + + T& get() { return obj; } + const T& get() const { return obj; } + T move() { return std::move(obj); } +}; + +// A smart pointer that knows whether or not to delete itself. +template +using OwnershipErasedPtr = std::unique_ptr>; + +// Creates an OwnershipErasedPtr that will delete itself. +template > +OwnershipErasedPtr ownedPtr(T* t, Deleter&& d = Deleter{}) { + return OwnershipErasedPtr{ t, std::forward(d) }; +} + +// Creates an OwnershipErasedPtr that will not delete itself. +template +OwnershipErasedPtr unownedPtr(T* t) { + return OwnershipErasedPtr{ t, [](T*) {} }; +} + +template +struct scalar_traits : std::false_type { + constexpr static size_t size = 0; + static void save(uint8_t*, const T&); + + // Context is an arbitrary type that is plumbed by reference throughout the + // load call tree. + template + static void load(const uint8_t*, T&, Context&); +}; + +struct WriteRawMemory { + using Block = std::pair, size_t>; + std::vector blocks; + + WriteRawMemory() {} + WriteRawMemory(Block&& b) { blocks.emplace_back(std::move(b.first), b.second); } + WriteRawMemory(std::vector&& v) : blocks(std::move(v)) {} + + WriteRawMemory(WriteRawMemory&&) = default; + WriteRawMemory& operator=(WriteRawMemory&&) = default; + + size_t size() const { + size_t result = 0; + for (const auto& b : blocks) { + result += b.second; + } + return result; + } +}; + +template +struct dynamic_size_traits : std::false_type { + static WriteRawMemory save(const T&); + + // Context is an arbitrary type that is plumbed by reference throughout the + // load call tree. + template + static void load(const uint8_t*, size_t, T&, Context&); +}; + +template +struct serializable_traits : std::false_type { + template + static void serialize(Archiver& ar, T& v); +}; + +template +struct vector_like_traits : std::false_type { + // Write this at the beginning of the buffer + using value_type = uint8_t; + using iterator = void; + using insert_iterator = void; + + static size_t num_entries(VectorLike&); + template + static void reserve(VectorLike&, size_t, Context&); + + static insert_iterator insert(VectorLike&); + static iterator begin(const VectorLike&); + static void deserialization_done(VectorLike&); // Optional +}; + +template +struct union_like_traits : std::false_type { + using Member = UnionLike; + using alternatives = pack<>; + static uint8_t index(const Member&); + static bool empty(const Member& variant); + + template + static const index_t& get(const Member&); + + template + static const void assign(Member&, const Alternative&); + + template + static void done(Member&, Context&); +}; + +// TODO(anoyes): Implement things that are currently using scalar traits with +// struct-like traits. +template +struct struct_like_traits : std::false_type { + using Member = StructLike; + using types = pack<>; + + template + static const index_t& get(const Member&); + + template + static const void assign(Member&, const index_t&); + + template + static void done(Member&, Context&); +}; + +template +struct struct_like_traits> : std::true_type { + using Member = std::tuple; + using types = pack; + + template + static const index_t& get(const Member& m) { + return std::get(m); + } + + template + static const void assign(Member& m, const Type& t) { + std::get(m) = t; + } +}; + +template +struct scalar_traits::value || std::is_floating_point::value>> + : std::true_type { + constexpr static size_t size = sizeof(T); + static void save(uint8_t* out, const T& t) { memcpy(out, &t, size); } + template + static void load(const uint8_t* in, T& t, Context&) { + memcpy(&t, in, size); + } +}; + +template +void serializer(F& fun, Items&... items); + +template +struct serializable_traits> : std::true_type { + template + static void serialize(Archiver& ar, std::pair& p) { + flat_buffers::serializer(ar, p.first, p.second); + } +}; + +template +struct vector_like_traits> : std::true_type { + using Vec = std::vector; + using value_type = typename Vec::value_type; + using iterator = typename Vec::const_iterator; + using insert_iterator = std::back_insert_iterator; + + static size_t num_entries(const Vec& v) { return v.size(); } + template + static void reserve(Vec& v, size_t size, Context&) { + v.clear(); + v.reserve(size); + } + + static insert_iterator insert(Vec& v) { return std::back_inserter(v); } + static iterator begin(const Vec& v) { return v.begin(); } +}; + +template +struct vector_like_traits> : std::true_type { + using Vec = std::map; + using value_type = std::pair; + using iterator = typename Vec::const_iterator; + using insert_iterator = std::insert_iterator; + + static size_t num_entries(const Vec& v) { return v.size(); } + template + static void reserve(Vec& v, size_t size, Context&) {} + + static insert_iterator insert(Vec& v) { return std::inserter(v, v.end()); } + static iterator begin(const Vec& v) { return v.begin(); } +}; + +template <> +struct dynamic_size_traits : std::true_type { +private: + using T = std::string; + +public: + static WriteRawMemory save(const T& t) { + return { { unownedPtr(reinterpret_cast(t.data())), t.size() } }; + }; + + // Context is an arbitrary type that is plumbed by reference throughout the + // load call tree. + template + static void load(const uint8_t* p, size_t n, T& t, Context&) { + t.assign(reinterpret_cast(p), n); + } +}; + +namespace detail { + +template +T interpret_as(const uint8_t* current) { + T t; + memcpy(&t, current, sizeof(t)); + return t; +} + +// Used to select an overload for |MessageWriter::write| that fixes relative +// offsets. +struct RelativeOffset { + int value; +}; +static_assert(sizeof(RelativeOffset) == 4, ""); + +template +constexpr bool is_scalar = scalar_traits::value; + +template +constexpr bool is_dynamic_size = dynamic_size_traits::value; + +template +constexpr bool is_union_like = union_like_traits::value; + +template +constexpr bool is_vector_like = vector_like_traits::value; + +template +constexpr bool is_struct_like = struct_like_traits::value; + +template +constexpr bool expect_serialize_member = + !is_scalar && !is_vector_like && !is_union_like && !is_dynamic_size && !is_struct_like; + +template +constexpr bool use_indirection = !(is_scalar || is_struct_like); + +using VTable = std::vector; + +template +struct sfinae_true : std::true_type {}; + +template +auto test_deserialization_done(int) -> sfinae_true; + +template +auto test_deserialization_done(long) -> std::false_type; + +template +struct has_deserialization_done : decltype(test_deserialization_done(0)) {}; + +template +constexpr int fb_scalar_size = is_scalar ? scalar_traits::size : sizeof(RelativeOffset); + +template +struct struct_offset_impl; + +template +struct struct_offset_impl { + static_assert(index == 0); + static constexpr auto offset = o; +}; + +template +struct struct_offset_impl { +private: + static constexpr size_t offset_() { + if constexpr (index == 0) { + return RightAlign(o, fb_scalar_size); + } else { + return struct_offset_impl) + fb_scalar_size, index - 1, Ts...>::offset; + } + } + +public: + static_assert(!is_struct_like, "Nested structs not supported yet"); + static constexpr auto offset = offset_(); +}; + +constexpr size_t AlignToPowerOfTwo(size_t s) { + if (s > 4) { + return 8; + } else if (s > 2) { + return 4; + } else if (s > 1) { + return 2; + } else { + return 1; + } +} + +template +constexpr auto align_helper(pack) { + return std::max({ size_t{ 1 }, AlignToPowerOfTwo(fb_scalar_size)... }); +} + +template +constexpr auto struct_size(pack) { + return std::max(1, RightAlign(struct_offset_impl<0, sizeof...(T), T...>::offset, align_helper(pack{}))); +} + +template +constexpr auto struct_offset(pack) { + static_assert(i < sizeof...(T)); + return struct_offset_impl<0, i, T...>::offset; +} + +static_assert(struct_offset<0>(pack{}) == 0); +static_assert(struct_offset<1>(pack{}) == 4); +static_assert(struct_offset<2>(pack{}) == 8); + +static_assert(struct_size(pack<>{}) == 1); +static_assert(struct_size(pack{}) == 4); +static_assert(struct_size(pack{}) == 8); +static_assert(struct_size(pack{}) == 16); + +template +constexpr int fb_size = is_struct_like ? struct_size(typename struct_like_traits::types{}) : fb_scalar_size; + +template +constexpr int fb_align = is_struct_like ? align_helper(typename struct_like_traits::types{}) + : AlignToPowerOfTwo(fb_scalar_size); + +template +struct _SizeOf { + static constexpr int size = fb_size; + static constexpr int align = fb_align; +}; + +struct PrecomputeSize { + // |offset| is measured from the end of the buffer. Precondition: len <= + // offset. + void write(const void*, int offset, int len) { current_buffer_size = std::max(current_buffer_size, offset); } + + template + void writeRawMemory(ToRawMemory&& to_raw_memory) { + auto w = std::forward(to_raw_memory)(); + int start = RightAlign(current_buffer_size + w.size() + 4, 4); + write(nullptr, start, 4); + start -= 4; + for (auto& block : w.blocks) { + write(nullptr, start, block.second); + start -= block.second; + } + writeRawMemories.emplace_back(std::move(w)); + } + + struct Noop { + void write(const void* src, int offset, int len) {} + void writeTo(PrecomputeSize& writer, int offset) { + + writer.write(nullptr, offset, size); + writer.writeToOffsets[writeToIndex] = offset; + } + void writeTo(PrecomputeSize& writer) { writeTo(writer, writer.current_buffer_size + size); } + int size; + int writeToIndex; + }; + + Noop getMessageWriter(int size) { + int writeToIndex = writeToOffsets.size(); + writeToOffsets.push_back({}); + return Noop{ size, writeToIndex }; + } + + int current_buffer_size = 0; + + const int buffer_length = -1; // Dummy, the value of this should not affect anything. + const int vtable_start = -1; // Dummy, the value of this should not affect anything. + std::vector writeToOffsets; + std::vector writeRawMemories; +}; + +template +void load_helper(Member&, const uint8_t*, Context&); + +class VTableSet; + +template +struct is_array : std::false_type {}; + +template +struct is_array> : std::true_type {}; + +struct WriteToBuffer { + // |offset| is measured from the end of the buffer. Precondition: len <= + // offset. + void write(const void* src, int offset, int len) { + copy_memory(src, offset, len); + current_buffer_size = std::max(current_buffer_size, offset); + } + + template + void writeRawMemory(ToRawMemory&&) { + auto& w = *write_raw_memories_iter; + uint32_t size = w.size(); + int start = RightAlign(current_buffer_size + size + 4, 4); + write(&size, start, 4); + start -= 4; + for (auto& p : w.blocks) { + if (p.second > 0) { + write(reinterpret_cast(p.first.get()), start, p.second); + } + start -= p.second; + } + ++write_raw_memories_iter; + } + + WriteToBuffer(int buffer_length, int vtable_start, uint8_t* buffer, std::vector writeToOffsets, + std::vector::iterator write_raw_memories_iter) + : buffer_length(buffer_length), vtable_start(vtable_start), buffer(buffer), + writeToOffsets(std::move(writeToOffsets)), write_raw_memories_iter(write_raw_memories_iter) {} + + struct MessageWriter { + template + void write(const T* src, int offset, size_t len) { + if constexpr (std::is_same_v) { + uint32_t fixed_offset = finalLocation - offset - src->value; + writer.copy_memory(&fixed_offset, finalLocation - offset, len); + } else if constexpr (is_array::value) { + writer.copy_memory(src, finalLocation - offset, std::min(src->size(), len)); + } else { + writer.copy_memory(src, finalLocation - offset, len); + } + } + void writeTo(WriteToBuffer&) { writer.current_buffer_size += size; } + void writeTo(WriteToBuffer&, int offset) { + writer.current_buffer_size = std::max(writer.current_buffer_size, offset); + } + WriteToBuffer& writer; + int finalLocation; + int size; + }; + + MessageWriter getMessageWriter(int size) { + MessageWriter m{ *this, writeToOffsets[writeToIndex++], size }; + return m; + } + + const int buffer_length; + const int vtable_start; + int current_buffer_size = 0; + +private: + void copy_memory(const void* src, int offset, int len) { + memcpy(static_cast(&buffer[buffer_length - offset]), src, len); + } + std::vector writeToOffsets; + std::vector::iterator write_raw_memories_iter; + int writeToIndex = 0; + uint8_t* buffer; +}; + +template +constexpr auto fields_helper() { + if constexpr (_SizeOf::size == 0) { + return pack<>{}; + } else if constexpr (is_union_like) { + return pack{}; + } else { + return pack{}; + } +} + +template +using Fields = decltype(fields_helper()); + +// TODO(anoyes): Make this `template ` so we can re-use +// identical vtables even if they have different types. +// Also, it's important that get_vtable always returns the same VTable pointer +// so that we can decide equality by comparing the pointers. + +extern VTable generate_vtable(size_t numMembers, const std::vector& members, + const std::vector& alignments); + +template +VTable gen_vtable(pack p) { + return generate_vtable(sizeof...(Members), std::vector{ { _SizeOf::size... } }, + std::vector{ { _SizeOf::align... } }); +} + +template +const VTable* get_vtable() { + static VTable table = gen_vtable(concat_t...>{}); + return &table; +} + +template +void for_each(F&& f, Members&&... members) { + (std::forward(f)(std::forward(members)), ...); +} + +struct VTableSet { + std::map offsets; + std::vector packed_tables; +}; + +struct InsertVTableLambda; + +struct TraverseMessageTypes { + InsertVTableLambda& f; + + bool vtableGeneratedBefore(const std::type_index&); + + template + std::enable_if_t> operator()(const Member& member) { + if (vtableGeneratedBefore(typeid(Member))) { + return; + } + if constexpr (serializable_traits::value) { + serializable_traits::serialize(f, const_cast(member)); + } else { + const_cast(member).serialize(f); + } + }; + + template + std::enable_if_t && !is_vector_like && !is_union_like> operator()(const T&) {} + + template + std::enable_if_t> operator()(const VectorLike& members) { + using VectorTraits = vector_like_traits; + using T = typename VectorTraits::value_type; + static_assert(!is_union_like, "vector not yet supported"); + // we don't need to check for recursion here because the next call + // to operator() will do that and we don't generate a vtable for the + // vector-like type itself + object_construction t; + (*this)(t.get()); + } + + template + std::enable_if_t> operator()(const UnionLike& members) { + using UnionTraits = union_like_traits; + static_assert(pack_size(typename UnionTraits::alternatives{}) <= 254, + "Up to 254 alternatives are supported for unions"); + union_helper(typename UnionTraits::alternatives{}); + } + +private: + template + void union_helper(pack) { + object_construction t; + (*this)(t.get()); + union_helper(pack{}); + } + void union_helper(pack<>) {} +}; + +struct InsertVTableLambda { + static constexpr bool isDeserializing = true; + std::set& vtables; + std::set& known_types; + + template + void operator()(const Members&... members) { + vtables.insert(get_vtable()); + for_each(TraverseMessageTypes{ *this }, members...); + } +}; + +template +int vec_bytes(const T& begin, const T& end) { + return sizeof(typename T::value_type) * (end - begin); +} + +template +VTableSet get_vtableset_impl(const Root& root) { + std::set vtables; + std::set known_types; + InsertVTableLambda vlambda{ vtables, known_types }; + if constexpr (serializable_traits::value) { + serializable_traits::serialize(vlambda, const_cast(root)); + } else { + const_cast(root).serialize(vlambda); + } + size_t size = 0; + for (const auto* vtable : vtables) { + size += vec_bytes(vtable->begin(), vtable->end()); + } + std::vector packed_tables(size); + int i = 0; + std::map offsets; + for (const auto* vtable : vtables) { + memcpy(&packed_tables[i], reinterpret_cast(&(*vtable)[0]), + vec_bytes(vtable->begin(), vtable->end())); + offsets[vtable] = i; + i += vec_bytes(vtable->begin(), vtable->end()); + } + return VTableSet{ offsets, packed_tables }; +} + +template +const VTableSet* get_vtableset(const Root& root) { + static VTableSet result = get_vtableset_impl(root); + return &result; +} + +template +void save_with_vtables(const Root& root, const VTableSet* vtableset, Writer& writer, int* vtable_start, + FileIdentifier file_identifier) { + auto vtable_writer = writer.getMessageWriter(vtableset->packed_tables.size()); + vtable_writer.write(&vtableset->packed_tables[0], 0, vtableset->packed_tables.size()); + RelativeOffset offset = save_helper(const_cast(root), writer, vtableset); + vtable_writer.writeTo(writer); + *vtable_start = writer.current_buffer_size; + int root_writer_size = sizeof(uint32_t) + sizeof(file_identifier); + auto root_writer = writer.getMessageWriter(root_writer_size); + root_writer.write(&offset, 0, sizeof(offset)); + root_writer.write(&file_identifier, sizeof(offset), sizeof(file_identifier)); + root_writer.writeTo(writer, RightAlign(writer.current_buffer_size + root_writer_size, 8)); +} + +template +struct SaveAlternative { + Writer& writer; + const VTableSet* vtables; + + RelativeOffset save(uint8_t type_tag, const typename UnionTraits::Member& member) { + return save_<0>(type_tag, member); + } + +private: + template + RelativeOffset save_(uint8_t type_tag, const typename UnionTraits::Member& member) { + if constexpr (Alternative < pack_size(typename UnionTraits::alternatives{})) { + if (type_tag == Alternative) { + auto result = save_helper(UnionTraits::template get(member), writer, vtables); + if constexpr (use_indirection>) { + return result; + } + writer.write(&result, writer.current_buffer_size + sizeof(result), sizeof(result)); + return RelativeOffset{ writer.current_buffer_size }; + } else { + return save_(type_tag, member); + } + } + throw std::runtime_error("type_tag out of range. This should never happen."); + } +}; + +template +struct LoadAlternative { + Context& context; + const uint8_t* current; + + void load(uint8_t type_tag, typename UnionTraits::Member& member) { return load_<0>(type_tag, member); } + +private: + template + void load_(uint8_t type_tag, typename UnionTraits::Member& member) { + if constexpr (Alternative < pack_size(typename UnionTraits::alternatives{})) { + if (type_tag == Alternative) { + using AlternativeT = index_t; + object_construction alternative; + if constexpr (use_indirection) { + load_helper(alternative.get(), current, context); + } else { + uint32_t current_offset = interpret_as(current); + current += current_offset; + load_helper(alternative.get(), current, context); + } + UnionTraits::template assign(member, std::move(alternative.move())); + } else { + load_(type_tag, member); + } + } + } +}; + +template +struct SaveVisitorLambda { + static constexpr bool isDeserializing = false; + const VTableSet* vtableset; + Writer& writer; + + template + void operator()(const Members&... members) { + const auto& vtable = *get_vtable(); + auto self = writer.getMessageWriter(vtable[1] /* length */); + int i = 2; + for_each( + [&](const auto& member) { + using Member = std::decay_t; + if constexpr (is_union_like) { + using UnionTraits = union_like_traits; + uint8_t type_tag = UnionTraits::index(member); + uint8_t fb_type_tag = UnionTraits::empty(member) ? 0 : type_tag + 1; // Flatbuffers indexes from 1. + self.write(&fb_type_tag, vtable[i++], sizeof(fb_type_tag)); + if (!UnionTraits::empty(member)) { + RelativeOffset offset = + (SaveAlternative{ writer, vtableset }).save(type_tag, member); + self.write(&offset, vtable[i++], sizeof(offset)); + } else { + ++i; + } + } else if constexpr (_SizeOf::size == 0) { + save_helper(member, writer, vtableset); + } else { + auto result = save_helper(member, writer, vtableset); + self.write(&result, vtable[i++], sizeof(result)); + } + }, + members...); + int vtable_offset = writer.vtable_start - vtableset->offsets.at(&vtable); + int start = RightAlign(writer.current_buffer_size + vtable[1] - 4, std::max({ 1, fb_align... })) + 4; + int32_t relative = vtable_offset - start; + self.write(&relative, 0, sizeof(relative)); + self.writeTo(writer, start); + } +}; + +template +struct LoadMember { + static constexpr bool isDeserializing = true; + const uint16_t* const vtable; + const uint8_t* const message; + const uint16_t vtable_length; + const uint16_t table_length; + int& i; + Context& context; + template + void operator()(Member& member) { + if constexpr (is_union_like) { + if (!field_present()) { + i += 2; + return; + } + uint8_t fb_type_tag; + load_helper(fb_type_tag, &message[vtable[i]], context); + uint8_t type_tag = fb_type_tag - 1; // Flatbuffers indexes from 1. + ++i; + if (field_present() && fb_type_tag > 0) { + (LoadAlternative>{ context, &message[vtable[i]] }) + .load(type_tag, member); + } + ++i; + } else if constexpr (_SizeOf::size == 0) { + load_helper(member, nullptr, context); + } else { + if (field_present()) { + load_helper(member, &message[vtable[i]], context); + } + ++i; + } + } + +private: + bool field_present() { return i < vtable_length && vtable[i] >= 4; } +}; + +template +struct int_type { + static constexpr int value = i; +}; + +template +void for_each_i_impl(F&& f, std::index_sequence) { + for_each(std::forward(f), int_type{}...); +} + +template +void for_each_i(F&& f) { + for_each_i_impl(std::forward(f), std::make_index_sequence{}); +} + +template +struct LoadSaveHelper { + template + std::enable_if_t> load(U& member, const uint8_t* current, Context& context) { + scalar_traits::load(current, member, context); + } + + template + std::enable_if_t> load(U& member, const uint8_t* current, Context& context) { + using StructTraits = struct_like_traits; + using types = typename StructTraits::types; + constexpr auto size = struct_size(types{}); + for_each_i([&](auto i_type) { + constexpr int i = decltype(i_type)::value; + using type = index_t; + object_construction t; + load_helper(t.get(), current + struct_offset(types{}), context); + StructTraits::template assign(member, t.move()); + }); + } + + template + std::enable_if_t> load(U& member, const uint8_t* current, Context& context) { + uint32_t current_offset = interpret_as(current); + current += current_offset; + uint32_t size = interpret_as(current); + current += sizeof(size); + dynamic_size_traits::load(current, size, member, context); + } + + template + struct SerializeFun { + static constexpr bool isDeserializing = true; + + const uint16_t* vtable; + const uint8_t* current; + Context& context; + + SerializeFun(const uint16_t* vtable, const uint8_t* current, Context& context) + : vtable(vtable), current(current), context(context) {} + + template + void operator()(Args&... members) { + int i = 0; + uint16_t vtable_length = vtable[i++] / sizeof(uint16_t); + uint16_t table_length = vtable[i++]; + for_each(LoadMember{ vtable, current, vtable_length, table_length, i, context }, members...); + } + }; + + template + std::enable_if_t> load(Member& member, const uint8_t* current, Context& context) { + uint32_t current_offset = interpret_as(current); + current += current_offset; + int32_t vtable_offset = interpret_as(current); + const uint16_t* vtable = reinterpret_cast(current - vtable_offset); + SerializeFun fun(vtable, current, context); + if constexpr (serializable_traits::value) { + serializable_traits::serialize(fun, member); + } else { + member.serialize(fun); + } + } + + template + std::enable_if_t> load(VectorLike& member, const uint8_t* current, Context& context) { + using VectorTraits = vector_like_traits; + using T = typename VectorTraits::value_type; + uint32_t current_offset = interpret_as(current); + current += current_offset; + uint32_t numEntries = interpret_as(current); + current += sizeof(uint32_t); + VectorTraits::reserve(member, numEntries, context); + auto inserter = VectorTraits::insert(member); + for (int i = 0; i < numEntries; ++i) { + T value; + load_helper(value, current, context); + *inserter = std::move(value); + ++inserter; + current += fb_size; + } + if constexpr (has_deserialization_done::value) { + VectorTraits::deserialization_done(member); + } + } + + template >> + auto save(const U& message, Writer& writer, const VTableSet*) { + constexpr auto size = scalar_traits::size; + std::array result = {}; + scalar_traits::save(&result[0], message); + return result; + } + + template + auto save(const U& message, Writer& writer, const VTableSet* vtables, + std::enable_if_t, int> _ = 0) { + using StructTraits = struct_like_traits; + using types = typename StructTraits::types; + constexpr auto size = struct_size(types{}); + std::array struct_bytes = {}; + for_each_i([&](auto i_type) { + constexpr int i = decltype(i_type)::value; + auto result = save_helper(StructTraits::template get(message), writer, vtables); + memcpy(&struct_bytes[struct_offset(types{})], &result, sizeof(result)); + }); + return struct_bytes; + } + + template >> + RelativeOffset save(const U& message, Writer& writer, const VTableSet*, + std::enable_if_t, int> _ = 0) { + writer.writeRawMemory([&]() { return dynamic_size_traits::save(message); }); + return RelativeOffset{ writer.current_buffer_size }; + } + + template + RelativeOffset save(const Member& member, Writer& writer, const VTableSet* vtables, + std::enable_if_t, int> _ = 0) { + SaveVisitorLambda l{ vtables, writer }; + if constexpr (serializable_traits::value) { + serializable_traits::serialize(l, const_cast(member)); + } else { + const_cast(member).serialize(l); + } + return RelativeOffset{ writer.current_buffer_size }; + } + + template >> + RelativeOffset save(const VectorLike& members, Writer& writer, const VTableSet* vtables) { + using VectorTraits = vector_like_traits; + using T = typename VectorTraits::value_type; + constexpr auto size = fb_size; + uint32_t num_entries = VectorTraits::num_entries(members); + uint32_t len = num_entries * size; + auto self = writer.getMessageWriter(len); + auto iter = VectorTraits::begin(members); + for (int i = 0; i < num_entries; ++i) { + auto result = save_helper(*iter, writer, vtables); + self.write(&result, i * size, size); + ++iter; + } + int start = RightAlign(writer.current_buffer_size + len, std::min(4, fb_align)) + 4; + writer.write(&num_entries, start, sizeof(uint32_t)); + self.writeTo(writer, start - sizeof(uint32_t)); + return RelativeOffset{ writer.current_buffer_size }; + } +}; + +template +struct LoadSaveHelper> { + template + void load(std::vector& member, const uint8_t* current, Context& context) { + uint32_t current_offset = interpret_as(current); + current += current_offset; + uint32_t length = interpret_as(current); + current += sizeof(uint32_t); + member.clear(); + member.resize(length); + bool m; + for (int i = 0; i < length; ++i) { + load_helper(m, current, context); + member[i] = m; + current += fb_size; + } + } + + template + RelativeOffset save(const std::vector& members, Writer& writer, const VTableSet* vtables) { + uint32_t len = members.size(); + int start = RightAlign(writer.current_buffer_size + sizeof(uint32_t) + len, sizeof(uint32_t)); + writer.write(&len, start, sizeof(uint32_t)); + int i = 0; + for (bool b : members) { + writer.write(&b, start - sizeof(uint32_t) - i++, 1); + } + return RelativeOffset{ writer.current_buffer_size }; + } +}; + +template +void load_helper(Member& member, const uint8_t* current, Context& context) { + LoadSaveHelper helper; + helper.load(member, current, context); +} + +template +auto save_helper(const Member& member, Writer& writer, const VTableSet* vtables) { + LoadSaveHelper helper; + return helper.save(member, writer, vtables); +} + +} // namespace detail + +template +void serializer(F& fun, Items&... items) { + fun(items...); +} + +namespace detail { + +template +struct FakeRoot { + std::tuple members; + + FakeRoot(Members&... members) : members(members...) {} + + template + void serialize(Archive& archive) { + serialize_impl(archive, std::index_sequence_for{}); + } + +private: + template + void serialize_impl(Archive& archive, std::index_sequence) { + flat_buffers::serializer(archive, std::get(members)...); + } +}; + +template +auto fake_root(Members&... members) { + return FakeRoot(members...); +} + +template +uint8_t* save(Allocator& allocator, const Root& root, FileIdentifier file_identifier) { + const auto* vtableset = get_vtableset(root); + PrecomputeSize precompute_size; + int vtable_start; + save_with_vtables(root, vtableset, precompute_size, &vtable_start, file_identifier); + uint8_t* out = allocator(precompute_size.current_buffer_size); + memset(out, 0, precompute_size.current_buffer_size); + WriteToBuffer writeToBuffer{ precompute_size.current_buffer_size, vtable_start, out, + std::move(precompute_size.writeToOffsets), precompute_size.writeRawMemories.begin() }; + save_with_vtables(root, vtableset, writeToBuffer, &vtable_start, file_identifier); + return out; +} + +template +void load(Root& root, const uint8_t* in, Context& context) { + flat_buffers::detail::load_helper(root, in, context); +} + +} // namespace detail + +template +uint8_t* save_members(Allocator& allocator, FileIdentifier file_identifier, Members&... members) { + const auto& root = flat_buffers::detail::fake_root(members...); + return detail::save(allocator, root, file_identifier); +} + +template +void load_members(const uint8_t* in, Context& context, Members&... members) { + auto root = flat_buffers::detail::fake_root(members...); + detail::load(root, in, context); +} + +inline FileIdentifier read_file_identifier(const uint8_t* in) { + FileIdentifier result; + memcpy(&result, in + sizeof(result), sizeof(result)); + return result; +} + +// members of unions must be tables in flatbuffers, so you can use this to +// introduce the indirection only when necessary. +template +struct EnsureTable { + constexpr static flat_buffers::FileIdentifier file_identifier = FileIdentifierFor::value; + EnsureTable() = default; + EnsureTable(const object_construction& t) : t(t) {} + EnsureTable(const T& t) : t(t) {} + template + void serialize(Archive& ar) { + if constexpr (detail::expect_serialize_member) { + if constexpr (serializable_traits::value) { + serializable_traits::serialize(ar, t.get()); + } else { + t.get().serialize(ar); + } + } else { + flat_buffers::serializer(ar, t.get()); + } + } + T& asUnderlyingType() { return t.get(); } + +private: + object_construction t; +}; + +} // namespace flat_buffers From fe81454ec2d3f7dc724fce25a37c1ff78535251f Mon Sep 17 00:00:00 2001 From: mpilman Date: Fri, 25 Jan 2019 15:10:20 -0800 Subject: [PATCH 044/118] basic functionality for object serializer This commit includes: - The flatbuffers implementation - A draft on how it should be used for network messages - A serializer that can be used independently What is missing: - All root objects will need a file identifier - Many special classes can not be serialized yet as the corresponding traits are not yet implemented - Object serialization can not yet be turned on (this will need a network option) --- fdbrpc/FlowTransport.actor.cpp | 36 +++-- fdbrpc/FlowTransport.h | 4 + fdbrpc/fdbrpc.h | 11 ++ fdbrpc/genericactors.actor.cpp | 5 - fdbrpc/networksender.actor.h | 20 ++- flow/Arena.h | 43 ++++++ flow/FileIdentifier.h | 97 ++++++++++++ flow/Net2.actor.cpp | 12 ++ flow/ObjectSerializer.h | 28 ++-- flow/ObjectSerializerTraits.h | 164 +++++++++++++++++++++ flow/flat_buffers.cpp | 41 +++--- flow/flat_buffers.h | 260 +++------------------------------ flow/network.h | 3 + flow/serialize.h | 55 +++++-- 14 files changed, 473 insertions(+), 306 deletions(-) create mode 100644 flow/FileIdentifier.h create mode 100644 flow/ObjectSerializerTraits.h diff --git a/fdbrpc/FlowTransport.actor.cpp b/fdbrpc/FlowTransport.actor.cpp index 70c0bfd37a..8f77027581 100644 --- a/fdbrpc/FlowTransport.actor.cpp +++ b/fdbrpc/FlowTransport.actor.cpp @@ -25,6 +25,7 @@ #include "flow/Net2Packet.h" #include "flow/ActorCollection.h" #include "flow/TDMetric.actor.h" +#include "flow/ObjectSerializer.h" #include "fdbrpc/FailureMonitor.h" #include "fdbrpc/crc32c.h" #include "fdbrpc/simulator.h" @@ -371,7 +372,6 @@ struct Peer : NonCopyable { // Send an (ignored) packet to make sure that, if our outgoing connection died before the peer made this connection attempt, // we eventually find out that our connection is dead, close it, and then respond to the next connection reattempt from peer. - //sendPacket( self, SerializeSourceRaw(StringRef()), Endpoint(peer->address(), TOKEN_IGNORE_PACKET), false ); } } @@ -529,7 +529,8 @@ TransportData::~TransportData() { } } -ACTOR static void deliver( TransportData* self, Endpoint destination, ArenaReader reader, bool inReadSocket ) { +ACTOR static void deliver(TransportData* self, Endpoint destination, ArenaReader reader, bool inReadSocket, + bool useFlatbuffers) { int priority = self->endpoints.getPriority(destination.token); if (priority < TaskReadSocket || !inReadSocket) { wait( delay(0, priority) ); @@ -540,8 +541,13 @@ ACTOR static void deliver( TransportData* self, Endpoint destination, ArenaReade auto receiver = self->endpoints.get(destination.token); if (receiver) { try { - g_currentDeliveryPeerAddress = destination.addresses; - receiver->receive( reader ); + g_currentDeliveryPeerAddress = destination.address; + if (useFlatbuffers) { + ArenaObjectReader objReader(reader.arena(), reader.arenaReadAll()); + receiver->receive(objReader); + } else { + receiver->receive( reader ); + } g_currentDeliveryPeerAddress = {NetworkAddress()}; } catch (Error& e) { g_currentDeliveryPeerAddress = {NetworkAddress()}; @@ -561,7 +567,8 @@ ACTOR static void deliver( TransportData* self, Endpoint destination, ArenaReade g_network->setCurrentTask( TaskReadSocket ); } -static void scanPackets( TransportData* transport, uint8_t*& unprocessed_begin, uint8_t* e, Arena& arena, NetworkAddress const& peerAddress, uint64_t peerProtocolVersion ) { +static void scanPackets(TransportData* transport, uint8_t*& unprocessed_begin, uint8_t* e, Arena& arena, + NetworkAddress const& peerAddress, uint64_t peerProtocolVersion) { // Find each complete packet in the given byte range and queue a ready task to deliver it. // Remove the complete packets from the range by increasing unprocessed_begin. // There won't be more than 64K of data plus one packet, so this shouldn't take a long time. @@ -633,8 +640,9 @@ static void scanPackets( TransportData* transport, uint8_t*& unprocessed_begin, #if VALGRIND VALGRIND_CHECK_MEM_IS_DEFINED(p, packetLen); #endif - ArenaReader reader( arena, StringRef(p, packetLen), AssumeVersion(peerProtocolVersion) ); - UID token; reader >> token; + ArenaReader reader(arena, StringRef(p, packetLen), AssumeVersion(removeFlags(peerProtocolVersion))); + UID token; + reader >> token; ++transport->countPacketsReceived; @@ -649,7 +657,7 @@ static void scanPackets( TransportData* transport, uint8_t*& unprocessed_begin, transport->warnAlwaysForLargePacket = false; } - deliver( transport, Endpoint( {peerAddress}, token ), std::move(reader), true ); + deliver(transport, Endpoint({ peerAddress }, token), std::move(reader), true, hasObjectSerializerFlag(peerProtocolVersion)); unprocessed_begin = p = p + packetLen; } @@ -748,7 +756,8 @@ ACTOR static Future connectionReader( TraceEvent("ConnectionEstablished", conn->getDebugID()) .suppressFor(1.0) .detail("Peer", conn->getPeerAddress()) - .detail("ConnectionId", connectionId); + .detail("ConnectionId", connectionId) + .detail("UseObjectSerializer", false); } if(connectionId > 1) { @@ -994,13 +1003,18 @@ static PacketID sendPacket( TransportData* self, ISerializeSource const& what, c // SOMEDAY: Would it be better to avoid (de)serialization by doing this check in flow? BinaryWriter wr( AssumeVersion(currentProtocolVersion) ); + // we don't need to send using an object writer here. This is a loopback delivery + // and therefore it is guaranteed that both versions will have exactly the + // same structures - so the backwards compatability capabilities are never needed + // here. what.serializeBinaryWriter(wr); Standalone copy = wr.toValue(); #if VALGRIND VALGRIND_CHECK_MEM_IS_DEFINED(copy.begin(), copy.size()); #endif - deliver( self, destination, ArenaReader(copy.arena(), copy, AssumeVersion(currentProtocolVersion)), false ); + deliver(self, destination, ArenaReader(copy.arena(), copy, AssumeVersion(currentProtocolVersion)), false, + false); return (PacketID)NULL; } else { @@ -1039,7 +1053,7 @@ static PacketID sendPacket( TransportData* self, ISerializeSource const& what, c wr.writeAhead(packetInfoSize , &packetInfoBuffer); wr << destination.token; - what.serializePacketWriter(wr); + what.serializePacketWriter(wr, g_network->useObjectSerializer()); pb = wr.finish(); len = wr.size() - packetInfoSize; diff --git a/fdbrpc/FlowTransport.h b/fdbrpc/FlowTransport.h index 8be6fa6837..bcf9c334aa 100644 --- a/fdbrpc/FlowTransport.h +++ b/fdbrpc/FlowTransport.h @@ -85,9 +85,13 @@ public: }; #pragma pack(pop) + + +class ArenaObjectReader; class NetworkMessageReceiver { public: virtual void receive( ArenaReader& ) = 0; + virtual void receive(ArenaObjectReader&) = 0; virtual bool isStream() const { return false; } }; diff --git a/fdbrpc/fdbrpc.h b/fdbrpc/fdbrpc.h index 986fe6525d..4a94af1543 100644 --- a/fdbrpc/fdbrpc.h +++ b/fdbrpc/fdbrpc.h @@ -92,6 +92,17 @@ struct NetSAV : SAV, FlowReceiver, FastAllocated> { SAV::sendErrorAndDelPromiseRef(error); } } + virtual void receive(ArenaObjectReader& reader) { + if (!SAV::canBeSet()) return; + this->addPromiseRef(); + ErrorOr message; + reader.deserialize(message); + if (message.isError()) { + SAV::sendErrorAndDelPromiseRef(message.getError()); + } else { + SAV::sendAndDelPromiseRef(message.get()); + } + } }; diff --git a/fdbrpc/genericactors.actor.cpp b/fdbrpc/genericactors.actor.cpp index 9b727021db..d0065668e4 100644 --- a/fdbrpc/genericactors.actor.cpp +++ b/fdbrpc/genericactors.actor.cpp @@ -24,11 +24,6 @@ #include "fdbrpc/simulator.h" #include "flow/actorcompiler.h" -ACTOR void simDeliverDuplicate( Standalone data, Endpoint destination ) { - wait( delay( g_random->random01() * FLOW_KNOBS->MAX_DELIVER_DUPLICATE_DELAY ) ); - FlowTransport::transport().sendUnreliable( SerializeSourceRaw(data), destination ); -} - ACTOR Future disableConnectionFailuresAfter( double time, std::string context ) { wait( delay(time) ); diff --git a/fdbrpc/networksender.actor.h b/fdbrpc/networksender.actor.h index bc108c01d6..629360d188 100644 --- a/fdbrpc/networksender.actor.h +++ b/fdbrpc/networksender.actor.h @@ -31,14 +31,22 @@ #include "flow/actorcompiler.h" // This must be the last #include. ACTOR template -void networkSender( Future input, Endpoint endpoint ) { +void networkSender(Future input, Endpoint endpoint) { try { - T value = wait( input ); - FlowTransport::transport().sendUnreliable( SerializeBoolAnd(true, value), endpoint, false ); + T value = wait(input); + if (g_network->useObjectSerializer()) { + FlowTransport::transport().sendUnreliable(SerializeSource>(ErrorOr(value)), endpoint); + } else { + FlowTransport::transport().sendUnreliable(SerializeBoolAnd(true, value), endpoint, false); + } } catch (Error& err) { - //if (err.code() == error_code_broken_promise) return; - ASSERT( err.code() != error_code_actor_cancelled ); - FlowTransport::transport().sendUnreliable( SerializeBoolAnd(false, err), endpoint, false ); + // if (err.code() == error_code_broken_promise) return; + ASSERT(err.code() != error_code_actor_cancelled); + if (g_network->useObjectSerializer()) { + FlowTransport::transport().sendUnreliable(SerializeSource>(ErrorOr(err)), endpoint); + } else { + FlowTransport::transport().sendUnreliable(SerializeBoolAnd(false, err), endpoint, false); + } } } #include "flow/unactorcompiler.h" diff --git a/flow/Arena.h b/flow/Arena.h index d93f09dafa..4c1e60c6a0 100644 --- a/flow/Arena.h +++ b/flow/Arena.h @@ -26,6 +26,7 @@ #include "flow/FastRef.h" #include "flow/Error.h" #include "flow/Trace.h" +#include "flow/ObjectSerializerTraits.h" #include #include #include @@ -108,6 +109,18 @@ public: Reference impl; }; +template<> +struct scalar_traits : std::true_type { + constexpr static size_t size = 0; + static void save(uint8_t*, const Arena&) {} + // Context is an arbitrary type that is plumbed by reference throughout + // the load call tree. + template + static void load(const uint8_t*, Arena& arena, Context& context) { + context.addArena(arena); + } +}; + struct ArenaBlockRef { ArenaBlock* next; uint32_t nextBlockOffset; @@ -723,6 +736,17 @@ inline void save( Archive& ar, const StringRef& value ) { ar << (uint32_t)value.size(); ar.serializeBytes( value.begin(), value.size() ); } + +template<> +struct dynamic_size_traits : std::true_type { + static WriteRawMemory save(const StringRef& str) { return { { unownedPtr(str.begin()), str.size() } }; } + + template + static void load(const uint8_t* ptr, size_t sz, StringRef& str, Context& context) { + str = StringRef(context.tryReadZeroCopy(ptr, sz), sz); + } +}; + inline bool operator == (const StringRef& lhs, const StringRef& rhs ) { return lhs.size() == rhs.size() && !memcmp(lhs.begin(), rhs.begin(), lhs.size()); } @@ -753,6 +777,8 @@ struct memcpy_able : std::integral_constant {}; template class VectorRef { public: + using value_type = T; + // T must be trivially destructible (and copyable)! VectorRef() : data(0), m_size(0), m_capacity(0) {} @@ -928,6 +954,23 @@ inline void save( Archive& ar, const VectorRef& value ) { ar << value[i]; } +template +struct vector_like_traits> : std::true_type { + using Vec = VectorRef; + using value_type = typename Vec::value_type; + using iterator = const T*; + using insert_iterator = T*; + + static size_t num_entries(const VectorRef& v) { return v.size(); } + template + static void reserve(VectorRef& v, size_t s, Context& context) { + v.resize(context.arena(), s); + } + + static insert_iterator insert(Vec& v) { return v.begin(); } + static iterator begin(const Vec& v) { return v.begin(); } +}; + void ArenaBlock::destroy() { // If the stack never contains more than one item, nothing will be allocated from stackArena. // If stackArena is used, it will always be a linked list, so destroying *it* will not create another arena diff --git a/flow/FileIdentifier.h b/flow/FileIdentifier.h new file mode 100644 index 0000000000..4bbb1d9d14 --- /dev/null +++ b/flow/FileIdentifier.h @@ -0,0 +1,97 @@ +/* + * FileIdentifier.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +using FileIdentifier = uint32_t; + +template +struct FileIdentifierFor { + //constexpr static FileIdentifier value = T::file_identifier; + // TODO: use file identifiers for different types + constexpr static FileIdentifier value = 0xffffff; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 1; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 2; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 3; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 4; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 5; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 6; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 7; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 8; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 9; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 10; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 11; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 7266212; +}; + +template <> +struct FileIdentifierFor { + constexpr static FileIdentifier value = 9348150; +}; + diff --git a/flow/Net2.actor.cpp b/flow/Net2.actor.cpp index fe081e9131..5ee248a33e 100644 --- a/flow/Net2.actor.cpp +++ b/flow/Net2.actor.cpp @@ -58,6 +58,18 @@ using namespace boost::asio::ip; const uint64_t currentProtocolVersion = 0x0FDB00B061070001LL; const uint64_t compatibleProtocolVersionMask = 0xffffffffffff0000LL; const uint64_t minValidProtocolVersion = 0x0FDB00A200060001LL; +const uint64_t objectSerializerFlag = 0x1000000000000000LL; +const uint64_t versionFlagMask = 0x0FFFFFFFFFFFFFFFLL; + +uint64_t removeFlags(uint64_t version) { + return version & versionFlagMask; +} +uint64_t addObjectSerializerFlag(uint64_t version) { + return version | versionFlagMask; +} +bool hasObjectSerializerFlag(uint64_t version) { + return (version & objectSerializerFlag) > 0; +} // This assert is intended to help prevent incrementing the leftmost digits accidentally. It will probably need to change when we reach version 10. static_assert(currentProtocolVersion < 0x0FDB00B100000000LL, "Unexpected protocol version"); diff --git a/flow/ObjectSerializer.h b/flow/ObjectSerializer.h index 03e68905a2..b10be8e835 100644 --- a/flow/ObjectSerializer.h +++ b/flow/ObjectSerializer.h @@ -18,6 +18,7 @@ * limitations under the License. */ +#pragma once #include "flow/Error.h" #include "flow/Arena.h" #include "flow/flat_buffers.h" @@ -52,21 +53,21 @@ template class _ObjectReader { public: template - void deserialize(flat_buffers::FileIdentifier file_identifier, Items&... items) { + void deserialize(FileIdentifier file_identifier, Items&... items) { const uint8_t* data = static_cast(this)->data(); LoadContext context(*static_cast(this)); - ASSERT(flat_buffers::read_file_identifier(data) == file_identifier); - flat_buffers::load_members(data, context, items...); + ASSERT(read_file_identifier(data) == file_identifier); + load_members(data, context, items...); context.done(); } template void deserialize(Item& item) { - deserialize(flat_buffers::FileIdentifierFor::value, item); + deserialize(FileIdentifierFor::value, item); } }; -class ObjectReader : _ObjectReader { +class ObjectReader : public _ObjectReader { public: static constexpr bool ownsUnderlyingMemory = false; @@ -81,7 +82,7 @@ private: Arena _arena; }; -class ArenaObjectReader : _ObjectReader { +class ArenaObjectReader : public _ObjectReader { public: static constexpr bool ownsUnderlyingMemory = true; @@ -99,7 +100,7 @@ private: class ObjectWriter { public: template - void serialize(flat_buffers::FileIdentifier file_identifier, Items const&... items) { + void serialize(FileIdentifier file_identifier, Items const&... items) { ASSERT(data = nullptr); // object serializer can only serialize one object int allocations = 0; auto allocator = [this, &allocations](size_t size_) { @@ -108,21 +109,20 @@ public: data = new uint8_t[size]; return data; }; - auto res = flat_buffers::save_members(allocator, file_identifier, items...); + auto res = save_members(allocator, file_identifier, items...); ASSERT(allocations == 1); } template void serialize(Item const& item) { - serialize(flat_buffers::FileIdentifierFor::value, item); + serialize(FileIdentifierFor::value, item); + } + + StringRef toStringRef() const { + return StringRef(data, size); } private: uint8_t* data = nullptr; int size = 0; }; - -template -std::enable_if serializer(Visitor& visitor, Items&... items) { - visitor(items...); -} diff --git a/flow/ObjectSerializerTraits.h b/flow/ObjectSerializerTraits.h new file mode 100644 index 0000000000..9d1476e8e2 --- /dev/null +++ b/flow/ObjectSerializerTraits.h @@ -0,0 +1,164 @@ +/* + * ObjectSerializerTraits.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +template +struct pack {}; + +template +struct index_impl; + +template +struct index_impl> { + using type = typename index_impl>::type; +}; + +template +struct index_impl<0, pack> { + using type = T; +}; + +template +using index_t = typename index_impl::type; + +// A smart pointer that knows whether or not to delete itself. +template +using OwnershipErasedPtr = std::unique_ptr>; + +// Creates an OwnershipErasedPtr that will delete itself. +template > +OwnershipErasedPtr ownedPtr(T* t, Deleter&& d = Deleter{}) { + return OwnershipErasedPtr{ t, std::forward(d) }; +} + +// Creates an OwnershipErasedPtr that will not delete itself. +template +OwnershipErasedPtr unownedPtr(T* t) { + return OwnershipErasedPtr{ t, [](T*) {} }; +} + +struct WriteRawMemory { + using Block = std::pair, size_t>; + std::vector blocks; + + WriteRawMemory() {} + WriteRawMemory(Block&& b) { blocks.emplace_back(std::move(b.first), b.second); } + WriteRawMemory(std::vector&& v) : blocks(std::move(v)) {} + + WriteRawMemory(WriteRawMemory&&) = default; + WriteRawMemory& operator=(WriteRawMemory&&) = default; + + size_t size() const { + size_t result = 0; + for (const auto& b : blocks) { + result += b.second; + } + return result; + } +}; + + +template +struct scalar_traits : std::false_type { + constexpr static size_t size = 0; + static void save(uint8_t*, const T&); + + // Context is an arbitrary type that is plumbed by reference throughout the + // load call tree. + template + static void load(const uint8_t*, T&, Context&); +}; + + +template +struct dynamic_size_traits : std::false_type { + static WriteRawMemory save(const T&); + + // Context is an arbitrary type that is plumbed by reference throughout the + // load call tree. + template + static void load(const uint8_t*, size_t, T&, Context&); +}; + +template +struct serializable_traits : std::false_type { + template + static void serialize(Archiver& ar, T& v); +}; + +template +struct vector_like_traits : std::false_type { + // Write this at the beginning of the buffer + using value_type = uint8_t; + using iterator = void; + using insert_iterator = void; + + static size_t num_entries(VectorLike&); + template + static void reserve(VectorLike&, size_t, Context&); + + static insert_iterator insert(VectorLike&); + static iterator begin(const VectorLike&); + static void deserialization_done(VectorLike&); // Optional +}; + +template +struct union_like_traits : std::false_type { + using Member = UnionLike; + using alternatives = pack<>; + static uint8_t index(const Member&); + static bool empty(const Member& variant); + + template + static const index_t& get(const Member&); + + template + static const void assign(Member&, const Alternative&); + + template + static void done(Member&, Context&); +}; + +// TODO(anoyes): Implement things that are currently using scalar traits with +// struct-like traits. +template +struct struct_like_traits : std::false_type { + using Member = StructLike; + using types = pack<>; + + template + static const index_t& get(const Member&); + + template + static const void assign(Member&, const index_t&); + + template + static void done(Member&, Context&); +}; + + diff --git a/flow/flat_buffers.cpp b/flow/flat_buffers.cpp index 29e2b6dd84..53739e4b7b 100644 --- a/flow/flat_buffers.cpp +++ b/flow/flat_buffers.cpp @@ -18,17 +18,16 @@ * limitations under the License. */ -#include "flat_buffers.h" -#include "UnitTest.h" -#include "Arena.h" -#include "serialize.h" +#include "flow/flat_buffers.h" +#include "flow/UnitTest.h" +#include "flow/Arena.h" +#include "flow/serialize.h" +#include "flow/ObjectSerializer.h" #include #include #include -namespace flat_buffers { - namespace detail { bool TraverseMessageTypes::vtableGeneratedBefore(const std::type_index& idx) { @@ -106,7 +105,7 @@ struct Table2 { int16_t m_ed = {}; template void serialize(Archiver& ar) { - return flat_buffers::serializer(ar, m_p, m_ujrnpumbfvc, m_iwgxxt, m_tjkuqo, m_ed); + serializer(ar, m_p, m_ujrnpumbfvc, m_iwgxxt, m_tjkuqo, m_ed); } }; @@ -117,7 +116,7 @@ struct Table3 { int64_t m_n = {}; template void serialize(Archiver& ar) { - return flat_buffers::serializer(ar, m_asbehdlquj, m_k, m_jib, m_n); + serializer(ar, m_asbehdlquj, m_k, m_jib, m_n); } }; @@ -134,7 +133,7 @@ struct Nested2 { int c; template void serialize(Archiver& ar) { - return flat_buffers::serializer(ar, a, b, c); + serializer(ar, a, b, c); } friend bool operator==(const Nested2& lhs, const Nested2& rhs) { @@ -149,7 +148,7 @@ struct Nested { std::vector c; template void serialize(Archiver& ar) { - return flat_buffers::serializer(ar, a, b, nested, c); + serializer(ar, a, b, nested, c); } }; @@ -159,7 +158,7 @@ struct Root { Nested c; template void serialize(Archiver& ar) { - return flat_buffers::serializer(ar, a, b, c); + serializer(ar, a, b, c); } }; @@ -224,7 +223,7 @@ TEST_CASE("flow/FlatBuffers/serializeDeserializeRoot") { { 3, "hello", { 6, { "abc", "def" }, 8 }, { 10, 11, 12 } } }; Root root2 = root; Arena arena; - auto out = flat_buffers::detail::save(arena, root, flat_buffers::FileIdentifier{}); + auto out = detail::save(arena, root, FileIdentifier{}); ASSERT(root.a == root2.a); ASSERT(root.b == root2.b); @@ -237,7 +236,7 @@ TEST_CASE("flow/FlatBuffers/serializeDeserializeRoot") { root2 = {}; DummyContext context; - flat_buffers::detail::load(root2, out, context); + detail::load(root2, out, context); ASSERT(root.a == root2.a); ASSERT(root.b == root2.b); @@ -399,7 +398,7 @@ struct Y1 { template void serialize(Archiver& ar) { - return flat_buffers::serializer(ar, a); + serializer(ar, a); } }; @@ -409,7 +408,7 @@ struct Y2 { template void serialize(Archiver& ar) { - return flat_buffers::serializer(ar, a, b); + serializer(ar, a, b); } }; @@ -421,7 +420,7 @@ struct X { template void serialize(Archiver& ar) { - return flat_buffers::serializer(ar, a, b, c); + serializer(ar, a, b, c); } }; @@ -493,12 +492,12 @@ TEST_CASE("flow/FlatBuffers/VectorRef") { for (const auto& str : src) { vec.push_back(arena, str); } - BinaryWriter writer(IncludeVersion()); - ::serialize_fake_root(writer, FileIdentifierFor::value, arena, vec); + ObjectWriter writer; + writer.serialize(FileIdentifierFor::value, arena, vec); serializedVector = StringRef(readerArena, writer.toStringRef()); } - ArenaReader reader(readerArena, serializedVector, IncludeVersion()); - ::serialize_fake_root(reader, FileIdentifierFor::value, vecArena, outVec); + ArenaObjectReader reader(readerArena, serializedVector); + reader.deserialize(FileIdentifierFor::value, vecArena, outVec); } ASSERT(src.size() == outVec.size()); for (int i = 0; i < src.size(); ++i) { @@ -509,5 +508,3 @@ TEST_CASE("flow/FlatBuffers/VectorRef") { } } // namespace unit_tests - -} // namespace flat_buffers diff --git a/flow/flat_buffers.h b/flow/flat_buffers.h index adcbc6b8a9..13637b704b 100644 --- a/flow/flat_buffers.h +++ b/flow/flat_buffers.h @@ -35,11 +35,8 @@ #include #include #include - -namespace flat_buffers { - -template -struct pack {}; +#include "flow/FileIdentifier.h" +#include "flow/ObjectSerializerTraits.h" template , class...> struct concat { @@ -55,117 +52,23 @@ constexpr auto pack_size(pack) { return sizeof...(Ts); } -template -struct index; - -template -struct index> { - using type = typename index>::type; -}; - -template -struct index<0, pack> { - using type = T; -}; - -template -using index_t = typename index::type; - constexpr int RightAlign(int offset, int alignment) { return offset % alignment == 0 ? offset : ((offset / alignment) + 1) * alignment; } -using FileIdentifier = uint32_t; +template +struct is_fb_function_t : std::false_type {}; + +template +struct is_fb_function_t::type> : std::true_type {}; template -struct FileIdentifierFor { - constexpr static FileIdentifier value = T::file_identifier; -}; +constexpr bool is_fb_function = is_fb_function_t::value; -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 1; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 2; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 3; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 4; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 5; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 6; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 7; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 8; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 9; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 10; -}; - -template <> -struct FileIdentifierFor { - constexpr static FileIdentifier value = 11; -}; - -template <> -struct FileIdentifierFor { - constexpr static flat_buffers::FileIdentifier value = 7266212; -}; - -template <> -struct FileIdentifierFor { - constexpr static flat_buffers::FileIdentifier value = 9348150; -}; - -template -struct FileIdentifierFor> { - constexpr static FileIdentifier value = FileIdentifierFor>::value; -}; - -template -struct FileIdentifierFor> { - constexpr static FileIdentifier value = FileIdentifierFor::value ^ FileIdentifierFor::value; -}; - -template -struct FileIdentifierFor> { - constexpr static FileIdentifier value = (0x10 << 24) | FileIdentifierFor::value; -}; - -template -struct FileIdentifierFor> { - constexpr static FileIdentifier value = 15694229; -}; +template +typename std::enable_if, void>::type serializer(Visitor& visitor, Items&... items) { + visitor(items...); +} template struct object_construction { @@ -180,119 +83,6 @@ struct object_construction { T move() { return std::move(obj); } }; -// A smart pointer that knows whether or not to delete itself. -template -using OwnershipErasedPtr = std::unique_ptr>; - -// Creates an OwnershipErasedPtr that will delete itself. -template > -OwnershipErasedPtr ownedPtr(T* t, Deleter&& d = Deleter{}) { - return OwnershipErasedPtr{ t, std::forward(d) }; -} - -// Creates an OwnershipErasedPtr that will not delete itself. -template -OwnershipErasedPtr unownedPtr(T* t) { - return OwnershipErasedPtr{ t, [](T*) {} }; -} - -template -struct scalar_traits : std::false_type { - constexpr static size_t size = 0; - static void save(uint8_t*, const T&); - - // Context is an arbitrary type that is plumbed by reference throughout the - // load call tree. - template - static void load(const uint8_t*, T&, Context&); -}; - -struct WriteRawMemory { - using Block = std::pair, size_t>; - std::vector blocks; - - WriteRawMemory() {} - WriteRawMemory(Block&& b) { blocks.emplace_back(std::move(b.first), b.second); } - WriteRawMemory(std::vector&& v) : blocks(std::move(v)) {} - - WriteRawMemory(WriteRawMemory&&) = default; - WriteRawMemory& operator=(WriteRawMemory&&) = default; - - size_t size() const { - size_t result = 0; - for (const auto& b : blocks) { - result += b.second; - } - return result; - } -}; - -template -struct dynamic_size_traits : std::false_type { - static WriteRawMemory save(const T&); - - // Context is an arbitrary type that is plumbed by reference throughout the - // load call tree. - template - static void load(const uint8_t*, size_t, T&, Context&); -}; - -template -struct serializable_traits : std::false_type { - template - static void serialize(Archiver& ar, T& v); -}; - -template -struct vector_like_traits : std::false_type { - // Write this at the beginning of the buffer - using value_type = uint8_t; - using iterator = void; - using insert_iterator = void; - - static size_t num_entries(VectorLike&); - template - static void reserve(VectorLike&, size_t, Context&); - - static insert_iterator insert(VectorLike&); - static iterator begin(const VectorLike&); - static void deserialization_done(VectorLike&); // Optional -}; - -template -struct union_like_traits : std::false_type { - using Member = UnionLike; - using alternatives = pack<>; - static uint8_t index(const Member&); - static bool empty(const Member& variant); - - template - static const index_t& get(const Member&); - - template - static const void assign(Member&, const Alternative&); - - template - static void done(Member&, Context&); -}; - -// TODO(anoyes): Implement things that are currently using scalar traits with -// struct-like traits. -template -struct struct_like_traits : std::false_type { - using Member = StructLike; - using types = pack<>; - - template - static const index_t& get(const Member&); - - template - static const void assign(Member&, const index_t&); - - template - static void done(Member&, Context&); -}; - template struct struct_like_traits> : std::true_type { using Member = std::tuple; @@ -320,14 +110,11 @@ struct scalar_traits::value || std::is_f } }; -template -void serializer(F& fun, Items&... items); - template struct serializable_traits> : std::true_type { template static void serialize(Archiver& ar, std::pair& p) { - flat_buffers::serializer(ar, p.first, p.second); + serializer(ar, p.first, p.second); } }; @@ -734,6 +521,7 @@ private: struct InsertVTableLambda { static constexpr bool isDeserializing = true; + static constexpr bool is_fb_visitor = true; std::set& vtables; std::set& known_types; @@ -856,6 +644,7 @@ private: template struct SaveVisitorLambda { static constexpr bool isDeserializing = false; + static constexpr bool is_fb_visitor = true; const VTableSet* vtableset; Writer& writer; @@ -982,6 +771,7 @@ struct LoadSaveHelper { template struct SerializeFun { static constexpr bool isDeserializing = true; + static constexpr bool is_fb_visitor = true; const uint16_t* vtable; const uint8_t* current; @@ -1143,11 +933,6 @@ auto save_helper(const Member& member, Writer& writer, const VTableSet* vtables) } // namespace detail -template -void serializer(F& fun, Items&... items) { - fun(items...); -} - namespace detail { template @@ -1164,7 +949,7 @@ struct FakeRoot { private: template void serialize_impl(Archive& archive, std::index_sequence) { - flat_buffers::serializer(archive, std::get(members)...); + serializer(archive, std::get(members)...); } }; @@ -1189,20 +974,20 @@ uint8_t* save(Allocator& allocator, const Root& root, FileIdentifier file_identi template void load(Root& root, const uint8_t* in, Context& context) { - flat_buffers::detail::load_helper(root, in, context); + detail::load_helper(root, in, context); } } // namespace detail template uint8_t* save_members(Allocator& allocator, FileIdentifier file_identifier, Members&... members) { - const auto& root = flat_buffers::detail::fake_root(members...); + const auto& root = detail::fake_root(members...); return detail::save(allocator, root, file_identifier); } template void load_members(const uint8_t* in, Context& context, Members&... members) { - auto root = flat_buffers::detail::fake_root(members...); + auto root = detail::fake_root(members...); detail::load(root, in, context); } @@ -1216,7 +1001,7 @@ inline FileIdentifier read_file_identifier(const uint8_t* in) { // introduce the indirection only when necessary. template struct EnsureTable { - constexpr static flat_buffers::FileIdentifier file_identifier = FileIdentifierFor::value; + constexpr static FileIdentifier file_identifier = FileIdentifierFor::value; EnsureTable() = default; EnsureTable(const object_construction& t) : t(t) {} EnsureTable(const T& t) : t(t) {} @@ -1229,7 +1014,7 @@ struct EnsureTable { t.get().serialize(ar); } } else { - flat_buffers::serializer(ar, t.get()); + serializer(ar, t.get()); } } T& asUnderlyingType() { return t.get(); } @@ -1238,4 +1023,3 @@ private: object_construction t; }; -} // namespace flat_buffers diff --git a/flow/network.h b/flow/network.h index 2756ac963c..20124dcf66 100644 --- a/flow/network.h +++ b/flow/network.h @@ -399,6 +399,9 @@ public: virtual bool isAddressOnThisHost( NetworkAddress const& addr ) = 0; // Returns true if it is reasonably certain that a connection to the given address would be a fast loopback connection + virtual bool useObjectSerializer() { return false; } + // Whether or not the object serializer should be used when sending packets + // Shorthand for transport().getLocalAddress() static NetworkAddress getLocalAddress() { diff --git a/flow/serialize.h b/flow/serialize.h index 5e9c604e93..e9bf3ff84f 100644 --- a/flow/serialize.h +++ b/flow/serialize.h @@ -27,6 +27,8 @@ #include #include "flow/Error.h" #include "flow/Arena.h" +#include "flow/FileIdentifier.h" +#include "flow/ObjectSerializer.h" #include // Though similar, is_binary_serializable cannot be replaced by std::is_pod, as doing so would prefer @@ -99,6 +101,11 @@ inline void load( Ar& ar, T& value ) { Serializer::serialize(ar, value); } +template +struct FileIdentifierFor> { + constexpr static FileIdentifier value = 15694229; +}; + template inline void load( Archive& ar, std::string& value ) { int32_t length; @@ -123,6 +130,12 @@ public: } }; +template +struct FileIdentifierFor> { + constexpr static FileIdentifier value = FileIdentifierFor::value ^ FileIdentifierFor::value; +}; + + template class Serializer< Archive, std::pair, void > { public: @@ -131,6 +144,11 @@ public: } }; +template +struct FileIdentifierFor> { + constexpr static FileIdentifier value = (0x10 << 24) | FileIdentifierFor::value; +}; + template inline void save( Archive& ar, const std::vector& value ) { ar << (int)value.size(); @@ -229,6 +247,11 @@ static inline bool valgrindCheck( const void* data, int bytes, const char* conte extern const uint64_t currentProtocolVersion; extern const uint64_t minValidProtocolVersion; extern const uint64_t compatibleProtocolVersionMask; +extern const uint64_t objectSerializerFlag; + +extern uint64_t removeFlags(uint64_t version); +extern uint64_t addObjectSerializerFlag(uint64_t version); +extern bool hasObjectSerializerFlag(uint64_t version); struct _IncludeVersion { uint64_t v; @@ -504,6 +527,10 @@ public: return (const uint8_t*)readBytes(bytes); } + StringRef arenaReadAll() const { + return StringRef(reinterpret_cast(begin), end - begin); + } + template void serializeBinaryItem( T& t ) { t = *(T*)readBytes(sizeof(T)); @@ -686,14 +713,22 @@ private: }; struct ISerializeSource { - virtual void serializePacketWriter( PacketWriter& ) const = 0; - virtual void serializeBinaryWriter( BinaryWriter& ) const = 0; + virtual void serializePacketWriter(PacketWriter&, bool useObjectSerializer) const = 0; + virtual void serializeBinaryWriter(BinaryWriter&) const = 0; }; template struct MakeSerializeSource : ISerializeSource { - virtual void serializePacketWriter( PacketWriter& w ) const { ((T const*)this)->serialize(w); } - virtual void serializeBinaryWriter( BinaryWriter& w ) const { ((T const*)this)->serialize(w); } + virtual void serializePacketWriter(PacketWriter& w, bool useObjectSerializer) const { + if (useObjectSerializer) { + ObjectWriter writer; + writer.serialize(get()); + } else { + ((T const*)this)->serialize(w); + } + } + virtual void serializeBinaryWriter(BinaryWriter& w) const { ((T const*)this)->serialize(w); } + virtual T const& get() const = 0; }; template @@ -701,6 +736,7 @@ struct SerializeSource : MakeSerializeSource> { T const& value; SerializeSource(T const& value) : value(value) {} template void serialize(Ar& ar) const { ar << value; } + virtual T const& get() const { return value; } }; template @@ -709,12 +745,11 @@ struct SerializeBoolAnd : MakeSerializeSource> { T const& value; SerializeBoolAnd( bool b, T const& value ) : b(b), value(value) {} template void serialize(Ar& ar) const { ar << b << value; } -}; - -struct SerializeSourceRaw : MakeSerializeSource { - StringRef data; - SerializeSourceRaw(StringRef data) : data(data) {} - template void serialize(Ar& ar) const { ar.serializeBytes(data); } + virtual T const& get() const { + // This is only used for the streaming serializer + ASSERT(false); + return value; + } }; #endif From ba83c458a68e9f4cabed045c8100c5ef21d1e137 Mon Sep 17 00:00:00 2001 From: mpilman Date: Mon, 28 Jan 2019 19:38:13 -0800 Subject: [PATCH 045/118] types implemented --- fdbclient/ClientDBInfo.h | 4 +- fdbclient/ClusterInterface.h | 6 +- fdbclient/FDBTypes.h | 36 +++++ fdbclient/StorageServerInterface.h | 13 +- fdbrpc/FlowTransport.actor.cpp | 11 ++ fdbrpc/FlowTransport.h | 22 +++ fdbrpc/Locality.h | 63 ++++----- fdbrpc/ReplicationPolicy.cpp | 2 + fdbrpc/ReplicationPolicy.h | 178 +++++++++++++++++++++--- fdbrpc/fdbrpc.h | 55 ++++++++ fdbserver/ClusterRecruitmentInterface.h | 18 ++- fdbserver/LogSystemConfig.h | 22 ++- fdbserver/MasterInterface.h | 4 +- fdbserver/WorkerInterface.actor.h | 4 +- flow/Error.h | 1 + flow/FastRef.h | 1 + flow/IRandom.h | 17 +++ flow/ObjectSerializerTraits.h | 15 ++ flow/Trace.h | 5 + flow/flat_buffers.h | 17 +-- flow/serialize.h | 15 +- 21 files changed, 425 insertions(+), 84 deletions(-) diff --git a/fdbclient/ClientDBInfo.h b/fdbclient/ClientDBInfo.h index 7b6aa047f8..3468d0e2fa 100644 --- a/fdbclient/ClientDBInfo.h +++ b/fdbclient/ClientDBInfo.h @@ -38,7 +38,9 @@ struct ClientDBInfo { template void serialize(Archive& ar) { - ASSERT( ar.protocolVersion() >= 0x0FDB00A200040001LL ); + if constexpr (!is_fb_function) { + ASSERT(ar.protocolVersion() >= 0x0FDB00A200040001LL); + } serializer(ar, proxies, id, clientTxnInfoSampleRate, clientTxnInfoSizeLimit); } }; diff --git a/fdbclient/ClusterInterface.h b/fdbclient/ClusterInterface.h index 61d6754b8f..28ab92be18 100644 --- a/fdbclient/ClusterInterface.h +++ b/fdbclient/ClusterInterface.h @@ -126,8 +126,10 @@ struct OpenDatabaseRequest { template void serialize(Ar& ar) { - ASSERT( ar.protocolVersion() >= 0x0FDB00A400040001LL ); - serializer(ar, issues, supportedVersions, connectedCoordinatorsNum, traceLogGroup, knownClientInfoID, reply, arena); + if constexpr (!is_fb_function) { + ASSERT( ar.protocolVersion() >= 0x0FDB00A400040001LL ); + } + serializer(ar, issues, supportedVersions, connectedCoordinatorsNum, traceLogGroup, knownClientInfoID, reply, arena); } }; diff --git a/fdbclient/FDBTypes.h b/fdbclient/FDBTypes.h index 790c70c6b6..eb24e84814 100644 --- a/fdbclient/FDBTypes.h +++ b/fdbclient/FDBTypes.h @@ -73,6 +73,23 @@ struct Tag { template void load( Ar& ar, Tag& tag ) { tag.serialize_unversioned(ar); } template void save( Ar& ar, Tag const& tag ) { const_cast(tag).serialize_unversioned(ar); } +template<> +struct scalar_traits : std::true_type { + using locality_trait = scalar_traits().locality)>; + using id_trait = scalar_traits().id)>; + constexpr static size_t size = locality_trait::size + id_trait::size; + static void save(uint8_t* out, const Tag& tag) { + locality_trait::save(out, tag.locality); + id_trait::save(out + id_trait::size, tag.id); + } + + template + static void load(const uint8_t* in, Tag& tag, Context& context) { + locality_trait::load(in, tag.locality, context); + id_trait::load(in + locality_trait::size, tag.id, context); + } +}; + static const Tag invalidTag {tagLocalitySpecial, 0}; static const Tag txsTag {tagLocalitySpecial, 1}; @@ -727,6 +744,25 @@ struct AddressExclusion { } }; +template <> +struct scalar_traits : std::true_type { + using ip_traits = scalar_traits; + using port_traits = scalar_traits; + constexpr static size_t size = ip_traits::size + port_traits::size; + static void save(uint8_t* buf, const AddressExclusion& value) { + ip_traits::save(buf, value.ip); + port_traits::save(buf + ip_traits::size, value.port); + } + + // Context is an arbitrary type that is plumbed by reference throughout the + // load call tree. + template + static void load(const uint8_t* buf, AddressExclusion& value, Context& context) { + ip_traits::load(buf, value.ip, context); + port_traits::load(buf + ip_traits::size, value.port, context); + } +}; + static bool addressExcluded( std::set const& exclusions, NetworkAddress const& addr ) { return exclusions.count( AddressExclusion(addr.ip, addr.port) ) || exclusions.count( AddressExclusion(addr.ip) ); } diff --git a/fdbclient/StorageServerInterface.h b/fdbclient/StorageServerInterface.h index 9ea511e8dd..d535a81196 100644 --- a/fdbclient/StorageServerInterface.h +++ b/fdbclient/StorageServerInterface.h @@ -69,11 +69,16 @@ struct StorageServerInterface { void serialize( Ar& ar ) { // StorageServerInterface is persisted in the database and in the tLog's data structures, so changes here have to be // versioned carefully! - serializer(ar, uniqueID, locality, getVersion, getValue, getKey, getKeyValues, getShardState, waitMetrics, - splitMetrics, getPhysicalMetrics, waitFailure, getQueuingMetrics, getKeyValueStoreType); - if( ar.protocolVersion() >= 0x0FDB00A200090001LL ) - serializer(ar, watchValue); + if constexpr (!is_fb_function) { + serializer(ar, uniqueID, locality, getVersion, getValue, getKey, getKeyValues, getShardState, waitMetrics, + splitMetrics, getPhysicalMetrics, waitFailure, getQueuingMetrics, getKeyValueStoreType); + if (ar.protocolVersion() >= 0x0FDB00A200090001LL) serializer(ar, watchValue); + } else { + serializer(ar, uniqueID, locality, getVersion, getValue, getKey, getKeyValues, getShardState, waitMetrics, + splitMetrics, getPhysicalMetrics, waitFailure, getQueuingMetrics, getKeyValueStoreType, + watchValue); + } } bool operator == (StorageServerInterface const& s) const { return uniqueID == s.uniqueID; } bool operator < (StorageServerInterface const& s) const { return uniqueID < s.uniqueID; } diff --git a/fdbrpc/FlowTransport.actor.cpp b/fdbrpc/FlowTransport.actor.cpp index 8f77027581..60bff615dd 100644 --- a/fdbrpc/FlowTransport.actor.cpp +++ b/fdbrpc/FlowTransport.actor.cpp @@ -127,6 +127,12 @@ struct EndpointNotFoundReceiver : NetworkMessageReceiver { Endpoint e; reader >> e; IFailureMonitor::failureMonitor().endpointNotFound(e); } + + virtual void receive(ArenaObjectReader& reader) { + Endpoint e; + reader.deserialize(e); + IFailureMonitor::failureMonitor().endpointNotFound(e); + } }; struct PingReceiver : NetworkMessageReceiver { @@ -139,6 +145,11 @@ struct PingReceiver : NetworkMessageReceiver { ReplyPromise reply; reader >> reply; reply.send(Void()); } + virtual void receive(ArenaObjectReader& reader) { + ReplyPromise reply; + reader.deserialize(reply); + reply.send(Void()); + } }; class TransportData { diff --git a/fdbrpc/FlowTransport.h b/fdbrpc/FlowTransport.h index bcf9c334aa..ef1f4a186c 100644 --- a/fdbrpc/FlowTransport.h +++ b/fdbrpc/FlowTransport.h @@ -86,6 +86,26 @@ public: #pragma pack(pop) +template <> +struct scalar_traits : std::true_type { + using networkAddress = scalar_traits; + using token = scalar_traits; + + static constexpr size_t size = networkAddress::size + token::size; + + static void save(uint8_t* out, const Endpoint& endpoint) { + networkAddress::save(out, endpoint.address); + out += networkAddress::size; + token::save(out, endpoint.token); + } + + template + static void load(const uint8_t* in, Endpoint& endpoint, C& c) { + networkAddress::load(in, endpoint.address, c); + in += networkAddress::size; + token::load(in, endpoint.token, c); + } +}; class ArenaObjectReader; class NetworkMessageReceiver { @@ -163,6 +183,8 @@ public: Endpoint loadedEndpoint(const UID& token); + void loadedEndpoint(Endpoint&); + private: class TransportData* self; }; diff --git a/fdbrpc/Locality.h b/fdbrpc/Locality.h index a05913460f..1b8b17ec2f 100644 --- a/fdbrpc/Locality.h +++ b/fdbrpc/Locality.h @@ -193,39 +193,40 @@ public: void serialize(Ar& ar) { // Locality is persisted in the database inside StorageServerInterface, so changes here have to be // versioned carefully! - if (ar.protocolVersion() >= 0x0FDB00A446020001LL) { - Standalone key; - Optional> value; - uint64_t mapSize = (uint64_t)_data.size(); - serializer(ar, mapSize); - if (ar.isDeserializing) { - for (size_t i = 0; i < mapSize; i++) { - serializer(ar, key, value); - _data[key] = value; + if constexpr (is_fb_function) { + serializer(ar, _data); + } else { + if (ar.protocolVersion() >= 0x0FDB00A446020001LL) { + Standalone key; + Optional> value; + uint64_t mapSize = (uint64_t)_data.size(); + serializer(ar, mapSize); + if (ar.isDeserializing) { + for (size_t i = 0; i < mapSize; i++) { + serializer(ar, key, value); + _data[key] = value; + } + } else { + for (auto it = _data.begin(); it != _data.end(); it++) { + key = it->first; + value = it->second; + serializer(ar, key, value); + } } - } - else { - for (auto it = _data.begin(); it != _data.end(); it++) { - key = it->first; - value = it->second; - serializer(ar, key, value); - } - } - } - else { - ASSERT(ar.isDeserializing); - UID zoneId, dcId, processId; - serializer(ar, zoneId, dcId); - set(keyZoneId, Standalone(zoneId.toString())); - set(keyDcId, Standalone(dcId.toString())); + } else { + ASSERT(ar.isDeserializing); + UID zoneId, dcId, processId; + serializer(ar, zoneId, dcId); + set(keyZoneId, Standalone(zoneId.toString())); + set(keyDcId, Standalone(dcId.toString())); - if (ar.protocolVersion() >= 0x0FDB00A340000001LL) { - serializer(ar, processId); - set(keyProcessId, Standalone(processId.toString())); - } - else { - int _machineClass = ProcessClass::UnsetClass; - serializer(ar, _machineClass); + if (ar.protocolVersion() >= 0x0FDB00A340000001LL) { + serializer(ar, processId); + set(keyProcessId, Standalone(processId.toString())); + } else { + int _machineClass = ProcessClass::UnsetClass; + serializer(ar, _machineClass); + } } } } diff --git a/fdbrpc/ReplicationPolicy.cpp b/fdbrpc/ReplicationPolicy.cpp index 59b8f511d1..e31483cdd0 100644 --- a/fdbrpc/ReplicationPolicy.cpp +++ b/fdbrpc/ReplicationPolicy.cpp @@ -142,6 +142,8 @@ PolicyAcross::PolicyAcross(int count, std::string const& attribKey, Reference { serializeReplicationPolicy(ar, refThis); refThis->delref_no_destroy(); } + virtual void deserializationDone() = 0; // Utility functions bool selectReplicas( @@ -100,6 +101,7 @@ inline void save( Archive& ar, const Reference& value ) { struct PolicyOne : IReplicationPolicy, public ReferenceCounted { PolicyOne() {}; + explicit PolicyOne(const PolicyOne& o) {} virtual ~PolicyOne() {}; virtual std::string name() const { return "One"; } virtual std::string info() const { return "1"; } @@ -115,23 +117,22 @@ struct PolicyOne : IReplicationPolicy, public ReferenceCounted { template void serialize(Ar& ar) { } + virtual void deserializationDone() {} virtual void attributeKeys(std::set* set) const override { return; } }; struct PolicyAcross : IReplicationPolicy, public ReferenceCounted { - PolicyAcross(int count, std::string const& attribKey, Reference const policy); + friend struct serializable_traits; + PolicyAcross(int count, std::string const& attribKey, IRepPolicyRef const policy); + explicit PolicyAcross(); + explicit PolicyAcross(const PolicyAcross& other) : PolicyAcross(other._count, other._attribKey, other._policy) {} virtual ~PolicyAcross(); virtual std::string name() const { return "Across"; } - virtual std::string info() const - { return format("%s^%d x ", _attribKey.c_str(), _count) + _policy->info(); } + virtual std::string info() const { return format("%s^%d x ", _attribKey.c_str(), _count) + _policy->info(); } virtual int maxResults() const { return _count * _policy->maxResults(); } virtual int depth() const { return 1 + _policy->depth(); } - virtual bool validate( - std::vector const& solutionSet, - Reference const& fromServers ) const; - virtual bool selectReplicas( - Reference & fromServers, - std::vector const& alsoServers, + virtual bool validate(std::vector const& solutionSet, LocalitySetRef const& fromServers) const; + virtual bool selectReplicas(LocalitySetRef& fromServers, std::vector const& alsoServers, std::vector & results ); template @@ -140,31 +141,39 @@ struct PolicyAcross : IReplicationPolicy, public ReferenceCounted serializeReplicationPolicy(ar, _policy); } - static bool compareAddedResults(const std::pair& rhs, const std::pair& lhs) - { return (rhs.first < lhs.first) || (!(lhs.first < rhs.first) && (rhs.second < lhs.second)); } + virtual void deserializationDone() {} - virtual void attributeKeys(std::set *set) const override - { set->insert(_attribKey); _policy->attributeKeys(set); } + static bool compareAddedResults(const std::pair& rhs, const std::pair& lhs) { + return (rhs.first < lhs.first) || (!(lhs.first < rhs.first) && (rhs.second < lhs.second)); + } + + virtual void attributeKeys(std::set* set) const override { + set->insert(_attribKey); + _policy->attributeKeys(set); + } protected: int _count; std::string _attribKey; - Reference _policy; + IRepPolicyRef _policy; // Cache temporary members std::vector _usedValues; std::vector _newResults; - Reference _selected; + LocalitySetRef _selected; VectorRef> _addedResults; Arena _arena; }; struct PolicyAnd : IReplicationPolicy, public ReferenceCounted { - PolicyAnd(std::vector> policies): _policies(policies), _sortedPolicies(policies) + friend struct serializable_traits; + PolicyAnd(std::vector policies): _policies(policies), _sortedPolicies(policies) { // Sort the policy array std::sort(_sortedPolicies.begin(), _sortedPolicies.end(), PolicyAnd::comparePolicy); } + explicit PolicyAnd(const PolicyAnd& other) : _policies(other._policies), _sortedPolicies(other._sortedPolicies) {} + explicit PolicyAnd() {} virtual ~PolicyAnd() {} virtual std::string name() const { return "And"; } virtual std::string info() const { @@ -218,6 +227,11 @@ struct PolicyAnd : IReplicationPolicy, public ReferenceCounted { } } + virtual void deserializationDone() { + _sortedPolicies = _policies; + std::sort(_sortedPolicies.begin(), _sortedPolicies.end(), PolicyAnd::comparePolicy); + } + virtual void attributeKeys(std::set *set) const override { for (const Reference& r : _policies) { r->attributeKeys(set); } } @@ -228,6 +242,138 @@ protected: extern int testReplication(); +#define POLICY_CONSTRUCTION_WRAPPER(t) \ + template <> \ + struct object_construction { \ + using type = t*; \ + type obj; \ + \ + object_construction() : obj(new t()) {} \ + object_construction(object_construction&& other) : obj(other.obj) { other.obj = nullptr; } \ + object_construction(const object_construction& other) : obj() {} \ + \ + object_construction& operator=(object_construction&& other) { \ + if (obj != nullptr) { \ + delete obj; \ + } \ + obj = other.obj; \ + other.obj = nullptr; \ + return *this; \ + } \ + \ + object_construction& operator=(const object_construction& other) { \ + if (obj != nullptr) { \ + delete obj; \ + } \ + obj = new t(*other.obj); \ + return *this; \ + } \ + \ + type& get() { return obj; } \ + const type& get() const { return obj; } \ + type move() { \ + auto res = obj; \ + obj = nullptr; \ + return res; \ + } \ + }; + +template <> +struct object_construction { + using type = IRepPolicyRef; + type _impl; + + object_construction() : _impl(new PolicyOne()){}; + + type& get() { return _impl; } + const type& get() const { return _impl; } + + type move() { return std::move(_impl); } +}; + +POLICY_CONSTRUCTION_WRAPPER(PolicyOne); +POLICY_CONSTRUCTION_WRAPPER(PolicyAcross); +POLICY_CONSTRUCTION_WRAPPER(PolicyAnd); + +template <> +struct FileIdentifierFor { + static constexpr FileIdentifier value = 14695621; +}; + +template <> +struct serializable_traits : std::true_type { + template + static void serialize(Archiver& ar, PolicyOne*& p) {} +}; + +template <> +struct serializable_traits : std::true_type { + template + static void serialize(Archiver& ar, PolicyAcross*& p) { + ::serializer(ar, p->_count, p->_attribKey, p->_policy); + } +}; + +template <> +struct serializable_traits : std::true_type { + template + static void serialize(Archiver& ar, PolicyAnd*& p) { + ::serializer(ar, p->_policies); + } +}; + +template <> +struct serializable_traits : std::true_type { + template + static void serialize(Archiver& ar, IRepPolicyRef& policy) { + ::serializer(ar, policy.changePtrUnsafe()); + } +}; + +template <> +struct union_like_traits : std::true_type { + using Member = IReplicationPolicy*; + using alternatives = pack; + + static uint8_t index(const Member& policy) { + if (policy->name() == "One") { + return 0; + } else if (policy->name() == "And") { + return 2; + } else { + return 1; + } + } + + static bool empty(const Member& policy) { return policy == nullptr; } + + template + static const index_t& get(IReplicationPolicy* const& member) { + if constexpr (i == 0) { + return reinterpret_cast(member); + } else if constexpr (i == 1) { + return reinterpret_cast(member); + } else { + return reinterpret_cast(member); + } + } + + template + static const void assign(Member& policy, const Alternative& impl) { + if (policy != nullptr) { + policy->delref(); + } + policy = impl; + } + + template + static void done(Member& policy, Context&) { + if (policy != nullptr) { + policy->deserializationDone(); + } + } +}; + template void serializeReplicationPolicy(Ar& ar, Reference& policy) { diff --git a/fdbrpc/fdbrpc.h b/fdbrpc/fdbrpc.h index 4a94af1543..4d466d77ab 100644 --- a/fdbrpc/fdbrpc.h +++ b/fdbrpc/fdbrpc.h @@ -23,6 +23,7 @@ #pragma once #include "flow/flow.h" +#include "flow/serialize.h" #include "fdbrpc/FlowTransport.h" // NetworkMessageReceiver Endpoint #include "fdbrpc/FailureMonitor.h" #include "fdbrpc/networksender.actor.h" @@ -174,6 +175,30 @@ void load(Ar& ar, ReplyPromise& value) { networkSender(value.getFuture(), endpoint); } +template +struct scalar_traits> : std::true_type { + using endpoint = scalar_traits; + + constexpr static size_t size = endpoint::size; + + static void save(uint8_t* out, const ReplyPromise& reply) { + auto const& ep = reply.getEndpoint(); + endpoint::save(out, ep); + ASSERT(!ep.address.isValid() || ep.address.isPublic()); // No re-serializing non-public addresses + // (the reply connection won't be + // available to any other process) + } + + template + static void load(const uint8_t* in, ReplyPromise& reply, C& context) { + Endpoint ep; + endpoint::load(in, ep, context); + FlowTransport::transport().loadedEndpoint(ep); + reply = ReplyPromise(ep); + networkSender(reply.getFuture(), ep); + } +}; + template ReplyPromise const& getReplyPromise(ReplyPromise const& p) { return p; } @@ -221,6 +246,13 @@ struct NetNotifiedQueue : NotifiedQueue, FlowReceiver, FastAllocatedsend(std::move(message)); this->delPromiseRef(); } + virtual void receive(ArenaObjectReader& reader) { + this->addPromiseRef(); + T message; + reader.deserialize(message); + this->send(std::move(message)); + this->delPromiseRef(); + } virtual bool isStream() const { return true; } }; @@ -390,5 +422,28 @@ void load(Ar& ar, RequestStream& value) { value = RequestStream(endpoint); } +template +struct scalar_traits> : std::true_type { + using endpointTraits = scalar_traits; + + static constexpr size_t size = endpointTraits::size; + + static void save(uint8_t* out, const RequestStream& stream) { + auto const& ep = stream.getEndpoint(); + endpointTraits::save(out, ep); + UNSTOPPABLE_ASSERT(ep.address.isValid()); + } + + // Context is an arbitrary type that is plumbed by reference throughout the + // load call tree. + template + static void load(const uint8_t* in, RequestStream& stream, Context& context) { + Endpoint endpoint; + endpointTraits::load(in, endpoint, context); + FlowTransport::transport().loadedEndpoint(endpoint); + stream = RequestStream(endpoint); + } +}; + #endif #include "fdbrpc/genericactors.actor.h" diff --git a/fdbserver/ClusterRecruitmentInterface.h b/fdbserver/ClusterRecruitmentInterface.h index f66b88032d..caf088c05f 100644 --- a/fdbserver/ClusterRecruitmentInterface.h +++ b/fdbserver/ClusterRecruitmentInterface.h @@ -60,9 +60,12 @@ struct ClusterControllerFullInterface { } template - void serialize( Ar& ar ) { - ASSERT( ar.protocolVersion() >= 0x0FDB00A200040001LL ); - serializer(ar, clientInterface, recruitFromConfiguration, recruitRemoteFromConfiguration, recruitStorage, registerWorker, getWorkers, registerMaster, getServerDBInfo); + void serialize(Ar& ar) { + if constexpr (!is_fb_function) { + ASSERT(ar.protocolVersion() >= 0x0FDB00A200040001LL); + } + serializer(ar, clientInterface, recruitFromConfiguration, recruitRemoteFromConfiguration, recruitStorage, + registerWorker, getWorkers, registerMaster, getServerDBInfo); } }; @@ -216,9 +219,12 @@ struct RegisterMasterRequest { RegisterMasterRequest() {} template - void serialize( Ar& ar ) { - ASSERT( ar.protocolVersion() >= 0x0FDB00A200040001LL ); - serializer(ar, id, mi, logSystemConfig, proxies, resolvers, recoveryCount, registrationCount, configuration, priorCommittedLogServers, recoveryState, recoveryStalled, reply); + void serialize(Ar& ar) { + if constexpr (!is_fb_function) { + ASSERT(ar.protocolVersion() >= 0x0FDB00A200040001LL); + } + serializer(ar, id, mi, logSystemConfig, proxies, resolvers, recoveryCount, registrationCount, configuration, + priorCommittedLogServers, recoveryState, recoveryStalled, reply); } }; diff --git a/fdbserver/LogSystemConfig.h b/fdbserver/LogSystemConfig.h index bd06329384..2b5277e88a 100644 --- a/fdbserver/LogSystemConfig.h +++ b/fdbserver/LogSystemConfig.h @@ -28,6 +28,7 @@ template struct OptionalInterface { + friend class serializable_traits>; // Represents an interface with a known id() and possibly known actual endpoints. // For example, an OptionalInterface represents a particular tlog by id, which you might or might not presently know how to communicate with @@ -58,6 +59,25 @@ protected: class LogSet; struct OldLogData; +template +struct serializable_traits> : std::true_type { + template + static void serialize(Archiver& ar, OptionalInterface& m) { + if constexpr (!Archiver::isDeserializing) { + if (m.iface.present()) { + m.ident = m.iface.get().id(); + } + } + ::serializer(ar, m.iface, m.ident); + if constexpr (Archiver::isDeserializing) { + if (m.iface.present()) { + m.ident = m.iface.get().id(); + } + } + } +}; + + struct TLogSet { std::vector> tLogs; std::vector> logRouters; @@ -136,7 +156,7 @@ struct OldTLogConf { explicit OldTLogConf(const OldLogData&); std::string toString() const { - return format("end: %d tags: %d %s", epochEnd, logRouterTags, describe(tLogs).c_str()); + return format("end: %d tags: %d %s", epochEnd, logRouterTags, describe(tLogs).c_str()); } bool operator == ( const OldTLogConf& rhs ) const { diff --git a/fdbserver/MasterInterface.h b/fdbserver/MasterInterface.h index 61f848b8c3..ac53d8ef2e 100644 --- a/fdbserver/MasterInterface.h +++ b/fdbserver/MasterInterface.h @@ -42,7 +42,9 @@ struct MasterInterface { UID id() const { return changeCoordinators.getEndpoint().token; } template void serialize(Archive& ar) { - ASSERT( ar.protocolVersion() >= 0x0FDB00A200040001LL ); + if constexpr (!is_fb_function) { + ASSERT( ar.protocolVersion() >= 0x0FDB00A200040001LL ); + } serializer(ar, locality, waitFailure, tlogRejoin, changeCoordinators, getCommitVersion); } diff --git a/fdbserver/WorkerInterface.actor.h b/fdbserver/WorkerInterface.actor.h index 4c60fc27dd..d684109f0e 100644 --- a/fdbserver/WorkerInterface.actor.h +++ b/fdbserver/WorkerInterface.actor.h @@ -139,7 +139,9 @@ struct RecruitMasterRequest { template void serialize(Ar& ar) { - ASSERT( ar.protocolVersion() >= 0x0FDB00A200040001LL ); + if constexpr (!is_fb_function) { + ASSERT(ar.protocolVersion() >= 0x0FDB00A200040001LL); + } serializer(ar, lifetime, forceRecovery, reply, arena); } }; diff --git a/flow/Error.h b/flow/Error.h index ffaef85449..18a3a1c9e6 100644 --- a/flow/Error.h +++ b/flow/Error.h @@ -29,6 +29,7 @@ #include #include "flow/Platform.h" #include "flow/Knobs.h" +#include "flow/ObjectSerializerTraits.h" enum { invalid_error_code = 0xffff }; diff --git a/flow/FastRef.h b/flow/FastRef.h index 3ddcdd55e9..380ad0ecda 100644 --- a/flow/FastRef.h +++ b/flow/FastRef.h @@ -156,6 +156,7 @@ public: bool isValid() const { return ptr != NULL; } explicit operator bool() const { return ptr != NULL; } + P*& changePtrUnsafe() { return ptr; } private: P *ptr; diff --git a/flow/IRandom.h b/flow/IRandom.h index c5d60cb01d..dbf14ac619 100644 --- a/flow/IRandom.h +++ b/flow/IRandom.h @@ -23,6 +23,7 @@ #pragma once #include "flow/Platform.h" +#include "flow/ObjectSerializerTraits.h" #include #if (defined(__APPLE__)) #include @@ -59,6 +60,22 @@ public: template void load( Ar& ar, UID& uid ) { uid.serialize_unversioned(ar); } template void save( Ar& ar, UID const& uid ) { const_cast(uid).serialize_unversioned(ar); } +template <> +struct scalar_traits : std::true_type { + constexpr static size_t size = sizeof(uint64_t[2]); + static void save(uint8_t* out, const UID& uid) { + uint64_t* outI = reinterpret_cast(out); + outI[0] = uid.first(); + outI[1] = uid.second(); + } + + template + static void load(const uint8_t* i, UID& out, Context& context) { + const uint64_t* in = reinterpret_cast(i); + out = UID(in[0], in[1]); + } +}; + namespace std { template <> class hash { diff --git a/flow/ObjectSerializerTraits.h b/flow/ObjectSerializerTraits.h index 9d1476e8e2..069146ca9f 100644 --- a/flow/ObjectSerializerTraits.h +++ b/flow/ObjectSerializerTraits.h @@ -27,6 +27,21 @@ #include #include +template +struct is_fb_function_t : std::false_type {}; + +template +struct is_fb_function_t::type> : std::true_type {}; + +template +constexpr bool is_fb_function = is_fb_function_t::value; + +template +typename std::enable_if, void>::type serializer(Visitor& visitor, Items&... items) { + visitor(items...); +} + + template struct pack {}; diff --git a/flow/Trace.h b/flow/Trace.h index 3ac5838852..c09e29374d 100644 --- a/flow/Trace.h +++ b/flow/Trace.h @@ -80,6 +80,11 @@ public: std::string toString() const; void validateFormat() const; + template + void serialize(Archiver& ar) { + static_assert(is_fb_function, "Streaming serializer has to use load/save"); + serializer(ar, fields); + } private: FieldContainer fields; diff --git a/flow/flat_buffers.h b/flow/flat_buffers.h index 13637b704b..502997e002 100644 --- a/flow/flat_buffers.h +++ b/flow/flat_buffers.h @@ -56,20 +56,6 @@ constexpr int RightAlign(int offset, int alignment) { return offset % alignment == 0 ? offset : ((offset / alignment) + 1) * alignment; } -template -struct is_fb_function_t : std::false_type {}; - -template -struct is_fb_function_t::type> : std::true_type {}; - -template -constexpr bool is_fb_function = is_fb_function_t::value; - -template -typename std::enable_if, void>::type serializer(Visitor& visitor, Items&... items) { - visitor(items...); -} - template struct object_construction { T obj; @@ -100,7 +86,8 @@ struct struct_like_traits> : std::true_type { }; template -struct scalar_traits::value || std::is_floating_point::value>> +struct scalar_traits< + T, std::enable_if_t::value || std::is_floating_point::value || std::is_enum::value>> : std::true_type { constexpr static size_t size = sizeof(T); static void save(uint8_t* out, const T& t) { memcpy(out, &t, size); } diff --git a/flow/serialize.h b/flow/serialize.h index e9bf3ff84f..adaf56c560 100644 --- a/flow/serialize.h +++ b/flow/serialize.h @@ -717,22 +717,24 @@ struct ISerializeSource { virtual void serializeBinaryWriter(BinaryWriter&) const = 0; }; -template +template struct MakeSerializeSource : ISerializeSource { + using value_type = V; virtual void serializePacketWriter(PacketWriter& w, bool useObjectSerializer) const { if (useObjectSerializer) { ObjectWriter writer; writer.serialize(get()); } else { - ((T const*)this)->serialize(w); + static_cast(this)->serialize(w); } } - virtual void serializeBinaryWriter(BinaryWriter& w) const { ((T const*)this)->serialize(w); } - virtual T const& get() const = 0; + virtual void serializeBinaryWriter(BinaryWriter& w) const { static_cast(this)->serialize(w); } + virtual value_type const& get() const = 0; }; template -struct SerializeSource : MakeSerializeSource> { +struct SerializeSource : MakeSerializeSource, T> { + using value_type = T; T const& value; SerializeSource(T const& value) : value(value) {} template void serialize(Ar& ar) const { ar << value; } @@ -740,7 +742,8 @@ struct SerializeSource : MakeSerializeSource> { }; template -struct SerializeBoolAnd : MakeSerializeSource> { +struct SerializeBoolAnd : MakeSerializeSource, T> { + using value_type = T; bool b; T const& value; SerializeBoolAnd( bool b, T const& value ) : b(b), value(value) {} From 9eeb48c43db3eeddc69a6ec6a9701a539a5e276e Mon Sep 17 00:00:00 2001 From: mpilman Date: Tue, 29 Jan 2019 08:43:57 -0800 Subject: [PATCH 046/118] Allow to turn on object serializer This commit includes functionality to turn on the object serializer for network communication. This is done the following way: - On incoming connections, a process will detect whether the client supports the object serializer and will only serialize responses with it, if it does - On outgoing connections, the command line flag is used to determine whether the object serializer should be used to send data. This way, a cluster can run in mixed mode. To upgrade one can upgrade one process at a time and set the flag one process at a time. This is how this is tested on the simulator: - The command line flag can take three options: on, off, and random. - For off, the object serializer will never we used. - For on, the object serializer will be always used. - For random, the simulator will flip a coin for each process it starts up. --- fdbclient/NativeAPI.actor.cpp | 5 +- fdbclient/NativeAPI.actor.h | 3 +- fdbclient/vexillographer/fdb.options | 3 + fdbrpc/FlowTests.actor.cpp | 1 + fdbrpc/sim2.actor.cpp | 8 +- fdbrpc/simulator.h | 13 ++- fdbserver/SimulatedCluster.actor.cpp | 62 ++++++---- fdbserver/SimulatedCluster.h | 2 +- fdbserver/fdbserver.actor.cpp | 169 ++++++++++++++++----------- flow/Net2.actor.cpp | 11 +- flow/network.h | 4 +- 11 files changed, 172 insertions(+), 109 deletions(-) diff --git a/fdbclient/NativeAPI.actor.cpp b/fdbclient/NativeAPI.actor.cpp index 4df95c560b..3f5225be1e 100644 --- a/fdbclient/NativeAPI.actor.cpp +++ b/fdbclient/NativeAPI.actor.cpp @@ -869,6 +869,9 @@ Future Cluster::onConnected() { void setNetworkOption(FDBNetworkOptions::Option option, Optional value) { switch(option) { // SOMEDAY: If the network is already started, should these three throw an error? + case FDBNetworkOptions::USE_OBJECT_SERIALIZER: + networkOptions.useObjectSerializer = extractIntOption(value) != 0; + break; case FDBNetworkOptions::TRACE_ENABLE: networkOptions.traceDirectory = value.present() ? value.get().toString() : ""; break; @@ -1008,7 +1011,7 @@ void setupNetwork(uint64_t transportId, bool useMetrics) { if (!networkOptions.logClientInfo.present()) networkOptions.logClientInfo = true; - g_network = newNet2(false, useMetrics || networkOptions.traceDirectory.present()); + g_network = newNet2(false, useMetrics || networkOptions.traceDirectory.present(), networkOptions.useObjectSerializer); FlowTransport::createInstance(transportId); Net2FileSystem::newFileSystem(); diff --git a/fdbclient/NativeAPI.actor.h b/fdbclient/NativeAPI.actor.h index 0d1acdfceb..b93094cc71 100644 --- a/fdbclient/NativeAPI.actor.h +++ b/fdbclient/NativeAPI.actor.h @@ -56,12 +56,13 @@ struct NetworkOptions { Optional logClientInfo; Standalone> supportedVersions; bool slowTaskProfilingEnabled; + bool useObjectSerializer; // The default values, TRACE_DEFAULT_ROLL_SIZE and TRACE_DEFAULT_MAX_LOGS_SIZE are located in Trace.h. NetworkOptions() : localAddress(""), clusterFile(""), traceDirectory(Optional()), traceRollSize(TRACE_DEFAULT_ROLL_SIZE), traceMaxLogsSize(TRACE_DEFAULT_MAX_LOGS_SIZE), traceLogGroup("default"), - traceFormat("xml"), slowTaskProfilingEnabled(false) {} + traceFormat("xml"), slowTaskProfilingEnabled(false), useObjectSerializer(false) {} }; class Database { diff --git a/fdbclient/vexillographer/fdb.options b/fdbclient/vexillographer/fdb.options index cba06df111..a84ba589dd 100644 --- a/fdbclient/vexillographer/fdb.options +++ b/fdbclient/vexillographer/fdb.options @@ -33,6 +33,9 @@ description is not currently required but encouraged.