1
0
Fork 0
arangodb/arangod/ReadCache/GlobalRevisionCacheChunk.cpp

212 lines
7.8 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Jan Steemann
////////////////////////////////////////////////////////////////////////////////
#include "GlobalRevisionCacheChunk.h"
#include "Basics/Exceptions.h"
#include "Basics/MutexLocker.h"
#include "ReadCache/RevisionTypes.h"
#include "Logger/Logger.h"
using namespace arangodb;
// align the length value to a multiple of 8
static constexpr inline uint32_t AlignSize(uint32_t value) {
return (value + 7) - ((value + 7) & 7);
}
// create a new chunk of the specified size
GlobalRevisionCacheChunk::GlobalRevisionCacheChunk(uint32_t size)
: _memory(nullptr), _currentUsers(0), _references(0), _version(0), _writePosition(0), _size(size) {
// if this throws, no harm is done
_memory = new char[size];
//LOG(ERR) << "CREATING CHUNK OF SIZE " << _size << ", PTR: " << (void*) _memory;
}
GlobalRevisionCacheChunk::~GlobalRevisionCacheChunk() {
//LOG(ERR) << "DESTROYING CHUNK OF SIZE " << _size << ", PTR: " << (void*) _memory;
delete[] _memory;
}
// stores a revision in the cache, acquiring a lease
// the collection id is prepended to the actual data in order to quickly access
// the shard-local hash for the revision when cleaning up the chunk
RevisionReader GlobalRevisionCacheChunk::storeAndLease(uint64_t collectionId, uint8_t const* data, size_t length) {
//LOG(ERR) << "STORING AND LEASING ELEMENT OF LENGTH: " << length;
if (!addReader()) {
// chunk is being garbage-collected at the moment
THROW_ARANGO_EXCEPTION(TRI_ERROR_LOCKED);
}
try {
uint32_t const version = _version.load(std::memory_order_relaxed);
uint32_t const offset = store(collectionId, data, length);
addReference();
return RevisionReader(this, RevisionOffset(offset), RevisionVersion(version));
} catch (...) {
// decrease the reference counter in case we cannot store the data in the chunk
removeReader();
throw;
}
}
// stores a revision in the cache, without acquiring a lease
// the collection id is prepended to the actual data in order to quickly access
// the shard-local hash for the revision when cleaning up the chunk
uint32_t GlobalRevisionCacheChunk::store(uint64_t collectionId, uint8_t const* data, size_t length) {
uint32_t const offset = adjustWritePosition(physicalSize(length));
// we can copy the data into the chunk without the lock
storeAtOffset(offset, collectionId, data, length);
return offset;
}
// return the physical size for a piece of data
// this adds required padding plus the required size for the collection id
size_t GlobalRevisionCacheChunk::physicalSize(size_t dataLength) noexcept {
return AlignSize(sizeof(uint64_t) + dataLength);
}
// garbage collects a chunk
// this will prepare the chunk for reuse, but not free the chunk's underlying memory
void GlobalRevisionCacheChunk::garbageCollect(GarbageCollectionCallback const& callback) {
// invalidates the chunk by simply increasing the version number
// this will make subsequent client read requests fail
++_version;
// now add ourselves as an exclusive writer
addWriter();
TRI_DEFER(removeWriter());
MUTEX_LOCKER(locker, _writeLock);
char const* ptr = _memory;
char const* end = ptr + _writePosition;
while (ptr < end) {
uint64_t collectionId = *reinterpret_cast<uint64_t const*>(ptr);
arangodb::velocypack::Slice slice(ptr + sizeof(uint64_t));
callback(collectionId, slice);
ptr += AlignSize(sizeof(uint64_t) + slice.byteSize());
}
// done collecting. now reset the cache and reset the write position
memset(_memory, 0, _writePosition);
_writePosition = 0;
}
// add a reader for the chunk, making it uneligible for garbage collection
bool GlobalRevisionCacheChunk::addReader() noexcept {
//LOG(ERR) << "ADDING READER. VALUE IS " << _currentUsers.load();
// increase the reference count value by 2. we use the lowest bit of the
// reference count to indicate that the chunk is going to be garbage-collected.
uint32_t oldValue = _currentUsers.fetch_add(2, std::memory_order_release);
//LOG(ERR) << "ADDING READER. OLD VALUE IS " << oldValue;
if ((oldValue & 1UL) == 0UL) {
// garbage collection bit not set. now we've successfully added the reader
//LOG(ERR) << "GC BIT NOT SET IN OLDVALUE";
return true;
}
//LOG(ERR) << "GC BIT IS SET IN OLDVALUE!!";
// garbage collection bit was set. revert the operation and return failure
removeReader();
return false;
}
// remove a reader for the chunk, making it eligible for garbage collection
void GlobalRevisionCacheChunk::removeReader() noexcept {
//LOG(ERR) << "REMOVING READER";
_currentUsers -= 2;
}
// modifies the reference count so that after this method call there is only
// a single writer (ourselves), and all readers are blocked
void GlobalRevisionCacheChunk::addWriter() noexcept {
// wait until there are no more readers active
//LOG(ERR) << "ADDING WRITER!";
uint32_t expected = 0UL;
while (!_currentUsers.compare_exchange_weak(expected, 1UL, std::memory_order_release, std::memory_order_relaxed)) {
expected = 0UL;
}
}
// modifies the reference count so that the exclusive writer bit is removed
// from the reference count
void GlobalRevisionCacheChunk::removeWriter() noexcept {
//LOG(ERR) << "REMOVING WRITER!";
// wait until there are no more readers active
uint32_t expected = 1UL;
while (!_currentUsers.compare_exchange_weak(expected, 0UL, std::memory_order_release, std::memory_order_relaxed)) {
expected = 1UL;
}
}
// add an external reference to the chunk. the chunk cannot be deleted physically
// if the number of external references is greater than 0
void GlobalRevisionCacheChunk::addReference() noexcept {
++_references;
}
// remove an external reference to the chunk. the chunk cannot be deleted physically
// if the number of external references is greater than 0
void GlobalRevisionCacheChunk::removeReference() noexcept {
--_references;
}
// stores the byte range [data...data+length) at the specified offset in the chunk
// the data is prepended by the collection id passed
void GlobalRevisionCacheChunk::storeAtOffset(uint32_t offset, uint64_t collectionId, uint8_t const* data, size_t length) noexcept {
// offset should always be evenly divisible by 8
TRI_ASSERT(offset % 8 == 0);
// we can copy the data into the chunk without the lock
// copy collection id into chunk
memcpy(_memory + offset, &collectionId, sizeof(collectionId));
// copy data into chunk
memcpy(_memory + offset + sizeof(collectionId), data, length);
}
// moves the write position of the chunk forward
uint32_t GlobalRevisionCacheChunk::adjustWritePosition(uint32_t length) {
//LOG(ERR) << "ADJUSTWRITEPOSITION FOR LENGTH: " << length;
// atomically check and move write pointer
MUTEX_LOCKER(locker, _writeLock);
if (_size - _writePosition < length) {
// chunk is full
//LOG(ERR) << "CHUNK IS FULL";
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
//LOG(ERR) << "MOVING WRITE POSITION FROM " << _writePosition;
uint32_t offset = _writePosition;
_writePosition += length;
return offset;
}