Program Listing for File cache.cpp

Return to documentation for file (src/client/cache.cpp)

/*
  Copyright 2018-2025, Barcelona Supercomputing Center (BSC), Spain
  Copyright 2015-2025, Johannes Gutenberg Universitaet Mainz, Germany

  This software was partially supported by the
  EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu).

  This software was partially supported by the
  ADA-FS project under the SPPEXA project funded by the DFG.

  This software was partially supported by the
  the European Union’s Horizon 2020 JTI-EuroHPC research and
  innovation programme, by the project ADMIRE (Project ID: 956748,
  admire-eurohpc.eu)

  This project was partially promoted by the Ministry for Digital Transformation
  and the Civil Service, within the framework of the Recovery,
  Transformation and Resilience Plan - Funded by the European Union
  -NextGenerationEU.

  This file is part of GekkoFS' POSIX interface.

  GekkoFS' POSIX interface is free software: you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation, either version 3 of the License,
  or (at your option) any later version.

  GekkoFS' POSIX interface is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with GekkoFS' POSIX interface.  If not, see
  <https://www.gnu.org/licenses/>.

  SPDX-License-Identifier: LGPL-3.0-or-later
*/

#include <client/cache.hpp>
#include <client/preload.hpp>
#include <client/preload_util.hpp>
#include <client/logging.hpp>

#include <cstdint>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>

namespace gkfs::cache {

namespace dir {

uint32_t
DentryCache::gen_dir_id(const std::string& dir_path) {
    // While collisions can theoretically occur, they are extremely unlikely as
    // clients are ephemeral and thus the lifetime of a cached directory as
    // well.
    return str_hash(dir_path);
}

uint32_t
DentryCache::get_dir_id(const std::string& dir_path) {
    // check if id already exists in map and return
    if(entry_dir_id_.find(dir_path) != entry_dir_id_.end()) {
        return entry_dir_id_[dir_path];
    }
    // otherwise generate one
    auto dir_id = gen_dir_id(dir_path);
    entry_dir_id_.emplace(dir_path, dir_id);
    return dir_id;
}


void
DentryCache::insert(const std::string& parent_dir, const std::string name,
                    const cache_entry value) {
    std::lock_guard<std::mutex> const lock(mtx_);
    auto dir_id = get_dir_id(parent_dir);
    entries_[dir_id].emplace(name, value);
}

std::optional<cache_entry>
DentryCache::get(const std::string& parent_dir, const std::string& name) {
    std::lock_guard<std::mutex> const lock(mtx_);
    auto dir_id = get_dir_id(parent_dir);
    if(entries_[dir_id].find(name) != entries_[dir_id].end()) {
        return entries_[dir_id][name];
    } else {
        return {};
    }
}

void
DentryCache::clear_dir(const std::string& dir_path) {
    std::lock_guard<std::mutex> const lock(mtx_);

    auto id_it = entry_dir_id_.find(dir_path);
    if(id_it == entry_dir_id_.end()) {
        return;
    }
    auto entry_it = entries_.find(id_it->second);
    if(entry_it != entries_.end()) {
        entries_.erase(entry_it);
    }
    entry_dir_id_.erase(id_it);
}

void
DentryCache::dump_cache_to_log(const std::string& dir_path) {
    std::lock_guard<std::mutex> const lock(mtx_);
    auto id_it = entry_dir_id_.find(dir_path);
    if(id_it == entry_dir_id_.end()) {
        LOG(INFO, "{}(): Cache contents for dir path '{}' NONE", __func__,
            dir_path);
        return;
    }
    auto dir_id = id_it->second;
    for(auto& [name, entry] : entries_[dir_id]) {
        // log entry
        LOG(INFO,
            "{}(): Cache contents for dir path '{}' -> name '{}' is_dir '{}' size '{}' ctime '{}'",
            __func__, dir_path, name,
            entry.file_type == gkfs::filemap::FileType::directory, entry.size,
            entry.ctime);
    }
}

void
DentryCache::clear() {
    std::lock_guard<std::mutex> const lock(mtx_);
    entries_.clear();
    entry_dir_id_.clear();
}

} // namespace dir

namespace file {

std::pair<size_t, size_t>
WriteSizeCache::record(std::string path, size_t size) {
    std::lock_guard<std::mutex> const lock(mtx_);
    auto& pair = size_cache.try_emplace(std::move(path), std::make_pair(0, 0))
                         .first->second;
    pair.first++;
    if(pair.second < size) {
        pair.second = size;
    }
    return pair;
}

std::pair<size_t, size_t>
WriteSizeCache::reset(const std::string& path, bool evict) {
    std::lock_guard<std::mutex> const lock(mtx_);
    auto it = size_cache.find(path);
    if(it == size_cache.end()) {
        return {};
    }
    auto entry = it->second;
    if(evict) {
        // remove entry from cache and discard cached size
        size_cache.erase(it);
    } else {
        // reset counter and keep cached size
        it->second.first = 0;
    }
    return entry;
}

std::pair<int, off64_t>
WriteSizeCache::flush(const std::string& path, bool evict) {
    // mutex is set in reset(). No need to lock here
    auto [latest_entry_cnt, latest_entry_size] = reset(path, false);
    // no new updates in cache, don't return size
    if(latest_entry_cnt == 0) {
        return {};
    }
    LOG(DEBUG,
        "WriteSizeCache::{}() Flushing and updating size for path '{}' size '{}' on the server. Evict: {}",
        __func__, path, latest_entry_size, evict);
    return gkfs::utils::update_file_size(path, latest_entry_size, 0, false);
}
size_t
WriteSizeCache::flush_threshold() const {
    return flush_threshold_;
}
void
WriteSizeCache::flush_threshold(size_t flush_threshold) {
    flush_threshold_ = flush_threshold;
}

} // namespace file

} // namespace gkfs::cache