cachelib/shm/ShmManager.cpp (281 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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 "cachelib/shm/ShmManager.h" #include <folly/ScopeGuard.h> #include <folly/hash/Hash.h> #include <sys/stat.h> #include <fstream> #include <vector> #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #include <thrift/lib/cpp2/protocol/Serializer.h> #pragma GCC diagnostic pop #include "cachelib/common/Utils.h" #include "cachelib/shm/ShmManager.h" #include "cachelib/shm/gen-cpp2/shm_types.h" namespace facebook { namespace cachelib { namespace { inline std::string pathName(const std::string& dir, const std::string& file) { return dir + '/' + file; } } // namespace ShmManager::ShmManager(const std::string& dir, bool usePosix) : controlDir_(dir), dirHash_(std::to_string(folly::hash::fnv64(controlDir_))), usePosix_(usePosix) { // check that the directory exists. If it does not exist, create // one. If it exists, extract the name to key mapping from the metadata // file. const auto metaFile = pathName(controlDir_, kMetaDataFile); mode_t controlMode = 0; if (!util::getStatIfExists(controlDir_, &controlMode)) { // create directory util::makeDir(controlDir_); // create an empty meta file. If during shutdown, this does not // exist, we dont do a clean shutdown createEmptyMetadataFile(metaFile); // Lock file for exclusive access lockMetadataFile(metaFile); return; } else if (!S_ISDIR(controlMode)) { // exists and is a file util::throwSystemError(ENOTDIR); } // someone indicated by creating our special file that we need to drop the // segments. const auto dropFileName = pathName(controlDir_, kDropFile); const bool dropSegments = util::getStatIfExists(dropFileName, nullptr); SCOPE_EXIT { if (dropSegments) { // now that we have decided to drop the segments, remove the file. If file // gets removed for some reason, that is okay since we have already // reinitialized the metadata file. try { util::removePath(dropFileName); } catch (...) { } } }; mode_t metaFileMode = 0; if (!util::getStatIfExists(metaFile, &metaFileMode)) { // does not exist. create an empty one. If during shutdown, this does not // exist, we dont do a clean shutdown createEmptyMetadataFile(metaFile); // Lock file for exclusive access lockMetadataFile(metaFile); return; } else if (!S_ISREG(metaFileMode)) { // exists, but is a directory util::throwSystemError(EISDIR); } // if file exists, init from it if needed. const bool reattach = dropSegments ? false : initFromFile(); if (!reattach) { DCHECK(nameToKey_.empty()); } // Lock file for exclusive access lockMetadataFile(metaFile); // truncate the file so that unless we shutdown clean, we dont re-attach // from it. createEmptyMetadataFile(metaFile); } bool ShmManager::initFromFile() { // restore the nameToKey_ map and destroy the contents of the file. const std::string fileName = pathName(controlDir_, kMetaDataFile); std::ifstream f(fileName); SCOPE_EXIT { f.close(); }; if (!f) { util::throwSystemError(ENOENT, "File is invalid"); } std::string buf{std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>()}; if (buf.empty()) { return false; } serialization::ShmManagerObject object; try { apache::thrift::BinarySerializer::deserialize(buf, object); } catch (const std::exception& ex) { throw std::invalid_argument( folly::sformat("Dersialization has failed. Reason: {}", ex.what())); } if (static_cast<bool>(usePosix_) ^ (*object.shmVal_ref() == static_cast<int8_t>(ShmVal::SHM_POSIX))) { throw std::invalid_argument(folly::sformat( "Invalid value for attach. ShmVal: {}", *object.shmVal_ref())); } for (const auto& kv : *object.nameToKeyMap_ref()) { nameToKey_.insert({kv.first, kv.second}); } return true; } typename ShmManager::ShutDownRes ShmManager::writeActiveSegmentsToFile() { const std::string fileName = pathName(controlDir_, kMetaDataFile); const bool exists = util::getStatIfExists(fileName, nullptr); // if file was deleted in the process, then the user intended to drop all // the segments. if (!exists) { // delete all the segments that we know exist for this control directory // and exit removeAllSegments(); return ShutDownRes::kFileDeleted; } // write the shmtype, nameToKey_ map to the file. DCHECK(metadataStream_); serialization::ShmManagerObject object; object.shmVal_ref() = usePosix_ ? static_cast<int8_t>(ShmVal::SHM_POSIX) : static_cast<int8_t>(ShmVal::SHM_SYS_V); for (const auto& kv : nameToKey_) { const auto& name = kv.first; const auto& key = kv.second; const auto it = segments_.find(name); // segment exists and is active. if (it != segments_.end() && it->second->isActive()) { object.nameToKeyMap_ref()[name] = key; } } // write to file std::string buf; apache::thrift::BinarySerializer::serialize(object, &buf); metadataStream_.seekp(0); metadataStream_ << buf; metadataStream_.flush(); if (!metadataStream_) { return ShutDownRes::kFailedWrite; } return ShutDownRes::kSuccess; } typename ShmManager::ShutDownRes ShmManager::shutDown() { const ShutDownRes ret = writeActiveSegmentsToFile(); metadataStream_.close(); metaDataLockFile_.unlock(); // segments that we did not attach to, but discovered it in the metadata // file, clean them up since we will not have written them to file above. removeUnAttachedSegments(); // clear our data. segments_.clear(); nameToKey_.clear(); return ret; } namespace { bool removeSegByName(bool posix, const std::string& uniqueName) { return posix ? PosixShmSegment::removeByName(uniqueName) : SysVShmSegment::removeByName(uniqueName); } } // namespace void ShmManager::removeByName(const std::string& dir, const std::string& name, bool posix) { removeSegByName(posix, uniqueIdForName(name, dir)); } bool ShmManager::segmentExists(const std::string& cacheDir, const std::string& shmName, bool posix) { try { ShmSegment(ShmAttach, uniqueIdForName(shmName, cacheDir), posix); return true; } catch (const std::exception& e) { return false; } } std::unique_ptr<ShmSegment> ShmManager::attachShmReadOnly( const std::string& dir, const std::string& name, bool posix, void* addr) { ShmSegmentOpts opts{PageSizeT::NORMAL, true /* read only */}; auto shm = std::make_unique<ShmSegment>(ShmAttach, uniqueIdForName(name, dir), posix, opts); if (!shm->mapAddress(addr)) { throw std::invalid_argument(folly::sformat( "Error mapping shm {} under {}, addr: {}", name, dir, addr)); } return shm; } void ShmManager::cleanup(const std::string& dir, bool posix) { // instantiate a shm manager and destroy it to clear all the segments // associated with the directory. ShmManager s(dir, posix); } void ShmManager::removeAllSegments() { for (const auto& kv : nameToKey_) { removeSegByName(usePosix_, uniqueIdForName(kv.first)); } nameToKey_.clear(); } void ShmManager::removeUnAttachedSegments() { auto it = nameToKey_.begin(); while (it != nameToKey_.end()) { const auto name = it->first; // check if the segment is attached. if (segments_.find(name) == segments_.end()) { // not attached removeSegByName(usePosix_, uniqueIdForName(name)); it = nameToKey_.erase(it); } else { ++it; } } } ShmAddr ShmManager::createShm(const std::string& shmName, size_t size, void* addr, ShmSegmentOpts opts) { // we are going to create a new segment most likely after trying to attach // to an old one. detach and remove any old ones if they have already been // attached or mapped removeShm(shmName); DCHECK(segments_.find(shmName) == segments_.end()); DCHECK(nameToKey_.find(shmName) == nameToKey_.end()); std::unique_ptr<ShmSegment> newSeg; try { newSeg = std::make_unique<ShmSegment>(ShmNew, uniqueIdForName(shmName), size, usePosix_, opts); } catch (const std::system_error& e) { // if segment already exists by this key and we dont know about // it(EEXIST), its an invalid state. if (e.code().value() == EEXIST) { throw; } throw std::invalid_argument( folly::sformat("Unable to create shared memory segment: name: {}, " "size: {}, addr: {}. msg: {}", shmName, size, addr, e.what())); } DCHECK(newSeg); if (!newSeg->mapAddress(addr, opts.alignment)) { throw std::invalid_argument( folly::sformat("Unable to map shared memory segment after create: " "name: {}, size: {}, addr: {}", shmName, size, addr)); } auto ret = newSeg->getCurrentMapping(); nameToKey_.emplace(shmName, newSeg->getKeyStr()); segments_.emplace(shmName, std::move(newSeg)); return ret; } void ShmManager::attachNewShm(const std::string& shmName, ShmSegmentOpts opts) { const auto keyIt = nameToKey_.find(shmName); // if key is not known already, there is not much we can do to attach. if (keyIt == nameToKey_.end()) { throw std::invalid_argument( folly::sformat("Unable to find any segment with name {}", shmName)); } // This means the segment exists and we can try to attach it. try { segments_.emplace(shmName, std::make_unique<ShmSegment>(ShmAttach, uniqueIdForName(shmName), usePosix_, opts)); } catch (const std::system_error& e) { // we are trying to attach. nothing can get invalid if an error happens // here. throw std::invalid_argument(folly::sformat( "Unable to attach to shared memory segment: name: {}, error: {}", shmName, e.what())); } DCHECK(segments_.find(shmName) != segments_.end()); DCHECK_EQ(segments_[shmName]->getKeyStr(), keyIt->second); } ShmAddr ShmManager::attachShm(const std::string& shmName, void* addr, ShmSegmentOpts opts) { // check if segment already exists. if (segments_.find(shmName) == segments_.end()) { attachNewShm(shmName, opts); } auto shmIt = segments_.find(shmName); DCHECK(shmIt != segments_.end()); auto& shm = *shmIt->second; if (shm.isMapped() || !shm.mapAddress(addr, opts.alignment)) { throw std::invalid_argument( folly::sformat("Unable to map shared memory segment after attach:" " name: {}, addr: {}, mapped: {}", shmName, addr, shm.isMapped())); } return shm.getCurrentMapping(); } bool ShmManager::removeShm(const std::string& shmName) { try { auto& shm = getShmByName(shmName); shm.detachCurrentMapping(); // first mark it to be removed if it is active if (shm.isActive()) { shm.markForRemoval(); } DCHECK(shm.isMarkedForRemoval()); DCHECK(shm.isInvalid()); } catch (const std::invalid_argument&) { // shm by this name is not attached. const bool wasPresent = removeSegByName(usePosix_, uniqueIdForName(shmName)); if (!wasPresent) { DCHECK(segments_.end() == segments_.find(shmName)); DCHECK(nameToKey_.end() == nameToKey_.find(shmName)); return false; } } // not mapped and already removed. segments_.erase(shmName); nameToKey_.erase(shmName); return true; } ShmSegment& ShmManager::getShmByName(const std::string& shmName) { const auto it = segments_.find(shmName); if (it != segments_.end()) { DCHECK(it->second != nullptr); return *it->second.get(); } else { throw std::invalid_argument(folly::sformat( "shared memory segment does not exist: name: {}", shmName)); } } } // namespace cachelib } // namespace facebook