Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable multi-process FMU caching and expose it through URI resolvers #388

Merged
merged 22 commits into from
Feb 5, 2020
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
214cfd3
Add utility::lock_file
kyllingstad Sep 27, 2019
3a740f0
Synchronise access to FMU cache directory
kyllingstad Sep 27, 2019
a9bf0a3
Enable FMU caching in file_uri_sub_resolver
kyllingstad Sep 27, 2019
ae1de5e
Enable caching in default URI resolver
kyllingstad Sep 27, 2019
9a10cea
Only define NOMINMAX if it's not already defined
kyllingstad Sep 30, 2019
e3af35b
Use correct error code for non-blocking lock test
kyllingstad Sep 30, 2019
afc524f
lock_file: Disable copy and move, clean up a bit
kyllingstad Sep 30, 2019
586e73e
Merge branch 'master' into feature/cse-cli-11-persistent-fmu-cache
kyllingstad Jan 3, 2020
092225f
Augmented file_lock, now based on Boost's
kyllingstad Jan 3, 2020
d7410ac
Small fixes: typo on Windows, const, include
kyllingstad Jan 4, 2020
3ef2586
Only define NOMINMAX if not already defined
kyllingstad Jan 5, 2020
b17cc76
Windows fixes
kyllingstad Jan 6, 2020
e8d309c
Add shared_mutex, allow sharing of file_lock
kyllingstad Jan 7, 2020
616aa40
Use lock files in fmi::importer
kyllingstad Jan 7, 2020
01bf4b7
Merge branch 'master' into feature/cse-cli-11-persistent-fmu-cache
kyllingstad Jan 7, 2020
6d86d35
Merge commit 'a74e7a0' into feature/cse-cli-11-persistent-fmu-cache
kyllingstad Jan 13, 2020
85d2088
Lock files do not need to be executable
kyllingstad Jan 13, 2020
bc90056
Thread safety, documentation, improved clean_cache
kyllingstad Jan 14, 2020
a74db05
Remove unnecessary include
kyllingstad Jan 14, 2020
f84b9b5
Introduce file_cache, clean up fmi::importer
kyllingstad Jan 17, 2020
60c536d
Whitespace, comments
kyllingstad Jan 17, 2020
ca80e7b
Windows stuff
kyllingstad Jan 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions include/cse/file_cache.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#ifndef CSE_FILE_CACHE_HPP
#define CSE_FILE_CACHE_HPP

#include <boost/filesystem.hpp>

#include <memory>
#include <string_view>


namespace cse
{


/**
* An interface to a file cache.
*
* Conceptually, the cache is organised as a flat list of subdirectories,
* each associated with a name – here called a *key*, so as not to confuse
* it with the actual directory name, which may be different.
* (The actual filesystem layout of the cache is implementation specific.)
*
* Client code can request read-write or read-only access to specific
* subdirectories. Since it may be impossible for the implementation to
* restrict access on a filesystem level, it is up to client code to not
* abuse the API by modifying the contents of a subdirectory after
* requesting read-only access.
*/
class file_cache
{
public:
/// A handle that represents read/write access to a cache subdirectory.
class directory_rw
{
public:
/// The filesystem path to the subdirectory.
virtual boost::filesystem::path path() const = 0;
virtual ~directory_rw() = default;
};

/// A handle that represents read-only access to a cache subdirectory.
class directory_ro
{
public:
/// The filesystem path to the subdirectory.
virtual boost::filesystem::path path() const = 0;
virtual ~directory_ro() = default;
};

/**
* Requests read/write access to the cache subdirectory associated with
* the given key, creating one if it doesn't exist already.
*
* Access is granted for the lifetime of the returned `directory_rw` object.
* The object should therefore be kept around as long as access is needed,
* but usually no longer, since it may block others from gaining access.
* Once the object expires, the directory may be modified or deleted by
* others.
*
* If the function is unable to take ownership of the directory,
* it may block until it becomes able to do so, or it may throw.
*/
virtual std::unique_ptr<directory_rw> get_directory_rw(std::string_view key) = 0;

/**
* Requests read-only access to the cache subdirectory associated with
* the given key. The key must already exist in the cache.
*
* Access is granted for the lifetime of the returned `directory_ro` object.
* The object should therefore be kept around as long as access is needed,
* but usually no longer, since it may block others from gaining access.
* Once the object expires, the directory may be modified or deleted by
* others.
*
* If the function is unable to take shared ownership of the directory,
* it may block until it becomes able to do so, or it may throw.
*/
virtual std::unique_ptr<directory_ro> get_directory_ro(std::string_view key) = 0;

virtual ~file_cache() = default;
};


/**
* A simple implementation of `file_cache` that offers no synchronisation
* or persistence.
*
* Upon construction, a new cache will be created in a randomly-named
* temporary location, and it will be removed again on destruction.
* The class may only be safely used by one thread at a time.
*/
class temporary_file_cache : public file_cache
{
public:
temporary_file_cache();

temporary_file_cache(const temporary_file_cache&) = delete;
temporary_file_cache& operator=(const temporary_file_cache&) = delete;
temporary_file_cache(temporary_file_cache&&) noexcept;
temporary_file_cache& operator=(temporary_file_cache&&) noexcept;
~temporary_file_cache() noexcept;

std::unique_ptr<directory_rw> get_directory_rw(std::string_view key) override;
std::unique_ptr<directory_ro> get_directory_ro(std::string_view key) override;

private:
class impl;
std::unique_ptr<impl> impl_;
};


/**
* A persistent file cache which can be safely accessed by multiple
* processes, threads and fibers concurrently.
*/
class persistent_file_cache : public file_cache
{
public:
/**
* Uses `cacheRoot` as the top-level directory of the cache.
*
* It is recommended that this directory be managed in its entirety by
* `persistent_file_cache`, i.e., that no other files are stored in it.
*/
explicit persistent_file_cache(const boost::filesystem::path& cacheRoot);

persistent_file_cache(const persistent_file_cache&) = delete;
persistent_file_cache& operator=(const persistent_file_cache&) = delete;
persistent_file_cache(persistent_file_cache&&) noexcept;
persistent_file_cache& operator=(persistent_file_cache&&) noexcept;
~persistent_file_cache() noexcept;

std::unique_ptr<directory_rw> get_directory_rw(std::string_view key) override;
std::unique_ptr<directory_ro> get_directory_ro(std::string_view key) override;

/**
* Cleans up cache contents.
*
* This will delete all subdirectories that are not currently being
* used (i.e., for which there exist `directory_r[ow]` handles).
*/
void cleanup();

private:
class impl;
std::unique_ptr<impl> impl_;
};


} // namespace cse
#endif
59 changes: 9 additions & 50 deletions include/cse/fmi/importer.hpp
Original file line number Diff line number Diff line change
@@ -5,6 +5,8 @@
#ifndef CSE_FMI_IMPORTER_HPP
#define CSE_FMI_IMPORTER_HPP

#include <cse/file_cache.hpp>

#include <boost/filesystem.hpp>

#include <map>
@@ -20,13 +22,6 @@ struct jm_callbacks;

namespace cse
{

namespace utility
{
class temp_dir;
}


namespace fmi
{

@@ -39,20 +34,6 @@ class fmu;
* The main purpose of this class is to read FMU files and create
* `cse::fmi::fmu` objects to represent them. This is done with the
* `import()` function.
*
* An `importer` object uses an on-disk cache that holds the unpacked
* contents of previously imported FMUs, so that they don't need to be
* unpacked anew every time they are imported. This is a huge time-saver
* when large and/or many FMUs are loaded. The path to this cache may be
* supplied by the user, in which case it is not automatically emptied on
* destruction. Thus, if the same path is supplied each time, the cache
* becomes persistent between program runs. It may be cleared manually
* by calling `clean_cache()`.
*
* \warning
* Currently, there are no synchronisation mechanisms to protect the
* cache from concurrent use, so accessing the same cache from
* multiple instances/processes will likely cause problems.
*/
class importer : public std::enable_shared_from_this<importer>
{
@@ -63,27 +44,16 @@ class importer : public std::enable_shared_from_this<importer>
*
* The cache directory will not be removed or emptied on destruction.
*
* \param [in] cachePath
* The path to the directory which will hold the FMU cache.
* If it does not exist already, it will be created.
* \param [in] cache
* The cache to which FMUs will be unpacked.
* By default, a non-persistent cache is used.
*/
static std::shared_ptr<importer> create(
const boost::filesystem::path& cachePath);

/**
* Creates a new FMU importer that uses a temporary cache
* directory.
*
* A new cache directory will be created in a location suitable for
* temporary files under the conventions of the operating system.
* It will be completely removed again on destruction.
*/
static std::shared_ptr<importer> create();
std::shared_ptr<file_cache> cache = std::make_shared<temporary_file_cache>());

private:
// Private constructors, to force use of factory functions.
importer(const boost::filesystem::path& cachePath);
importer(utility::temp_dir&& tempDir);
explicit importer(std::shared_ptr<file_cache> cache);

public:
/**
@@ -107,7 +77,7 @@ class importer : public std::enable_shared_from_this<importer>
*
* This is more or less equivalent to `import()`, but since the FMU is
* already unpacked its contents will be read from the specified directory
* rather than the cache.
* rather than the cache. (The contents will not be copied to the cache.)
*
* \param [in] unpackedFMUPath
* The path to a directory that holds the unpacked contents of an FMU.
@@ -117,14 +87,6 @@ class importer : public std::enable_shared_from_this<importer>
std::shared_ptr<fmu> import_unpacked(
const boost::filesystem::path& unpackedFMUPath);

/**
* Removes unused files and directories from the FMU cache.
*
* This will remove all FMU contents from the cache, except the ones for
* which there currently exist FMU objects.
*/
void clean_cache();

/// Returns the last FMI Library error message.
std::string last_error_message();

@@ -135,13 +97,10 @@ class importer : public std::enable_shared_from_this<importer>
void prune_ptr_caches();

// Note: The order of these declarations is important!
std::unique_ptr<utility::temp_dir> tempCacheDir_; // Only used when no cache dir is given
std::shared_ptr<file_cache> fileCache_;
std::unique_ptr<jm_callbacks> callbacks_;
std::unique_ptr<fmi_import_context_t, void (*)(fmi_import_context_t*)> handle_;

boost::filesystem::path fmuDir_;
boost::filesystem::path workDir_;

std::map<boost::filesystem::path, std::weak_ptr<fmu>> pathCache_;
std::map<std::string, std::weak_ptr<fmu>> guidCache_;
};
5 changes: 3 additions & 2 deletions include/cse/fmi/v1/fmu.hpp
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
#ifndef CSE_FMI_V1_FMU_HPP
#define CSE_FMI_V1_FMU_HPP

#include <cse/file_cache.hpp>
#include <cse/fmi/fmu.hpp>
#include <cse/fmi/importer.hpp>
#include <cse/model.hpp>
@@ -51,7 +52,7 @@ class fmu : public fmi::fmu, public std::enable_shared_from_this<fmu>
friend class fmi::importer;
fmu(
std::shared_ptr<fmi::importer> importer,
const boost::filesystem::path& fmuDir);
std::unique_ptr<file_cache::directory_ro> fmuDir);

public:
// Disable copy and move
@@ -94,7 +95,7 @@ class fmu : public fmi::fmu, public std::enable_shared_from_this<fmu>

private:
std::shared_ptr<fmi::importer> importer_;
boost::filesystem::path dir_;
std::unique_ptr<file_cache::directory_ro> dir_;

fmi1_import_t* handle_;
cse::model_description modelDescription_;
5 changes: 3 additions & 2 deletions include/cse/fmi/v2/fmu.hpp
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
#ifndef CSE_FMI_V2_FMU_HPP
#define CSE_FMI_V2_FMU_HPP

#include <cse/file_cache.hpp>
#include <cse/fmi/fmu.hpp>
#include <cse/fmi/importer.hpp>
#include <cse/model.hpp>
@@ -51,7 +52,7 @@ class fmu : public fmi::fmu, public std::enable_shared_from_this<fmu>
friend class fmi::importer;
fmu(
std::shared_ptr<fmi::importer> importer,
const boost::filesystem::path& fmuDir);
std::unique_ptr<file_cache::directory_ro> fmuDir);

public:
// Disable copy and move
@@ -94,7 +95,7 @@ class fmu : public fmi::fmu, public std::enable_shared_from_this<fmu>

private:
std::shared_ptr<fmi::importer> importer_;
boost::filesystem::path dir_;
std::unique_ptr<file_cache::directory_ro> dir_;

fmi2_import_t* handle_;
cse::model_description modelDescription_;
14 changes: 12 additions & 2 deletions include/cse/orchestration.hpp
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
#define CSE_ORCHESTRATION_HPP

#include <cse/async_slave.hpp>
#include <cse/file_cache.hpp>
#include <cse/fmi/importer.hpp>
#include <cse/model.hpp>
#include <cse/uri.hpp>
@@ -168,15 +169,24 @@ class fmu_file_uri_sub_resolver : public model_uri_sub_resolver
public:
fmu_file_uri_sub_resolver();

explicit fmu_file_uri_sub_resolver(std::shared_ptr<file_cache> cache);

std::shared_ptr<model> lookup_model(const uri& modelUri) override;

private:
std::shared_ptr<fmi::importer> importer_;
};


/// Returns a resolver for all URI schemes supported natively by CSE.
std::shared_ptr<model_uri_resolver> default_model_uri_resolver();
/**
* Returns a resolver for all URI schemes supported natively by CSE.
*
* If `cache` is not null, it will be used for caching by the URI
* resolvers that support it (e.g. for unpacking of FMUs by the `file`
* URI resolver).
*/
std::shared_ptr<model_uri_resolver> default_model_uri_resolver(
std::shared_ptr<file_cache> cache = nullptr);


} // namespace cse
3 changes: 3 additions & 0 deletions src/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ set(publicHeaders
"exception.hpp"
"execution.hpp"
"ssp_parser.hpp"
"file_cache.hpp"
"fmi/fmu.hpp"
"fmi/importer.hpp"
"fmi/v1/fmu.hpp"
@@ -69,6 +70,7 @@ set(sources
"error.cpp"
"exception.cpp"
"execution.cpp"
"file_cache.cpp"
"fmi/glue.cpp"
"fmi/importer.cpp"
"fmi/v1/fmu.cpp"
@@ -88,6 +90,7 @@ set(sources
"ssp_parser.cpp"
"timer.cpp"
"uri.cpp"
"utility/concurrency.cpp"
"utility/filesystem.cpp"
"utility/uuid.cpp"
"utility/zip.cpp"
Loading