As discussed in https://github.com/nodejs/node/pull/45888, using a global `BuiltinLoader` instance is probably undesirable in a world in which embedders are able to create Node.js Environments with different sources and therefore mutually incompatible code caching properties. This PR makes it so that `BuiltinLoader` is no longer a global singleton and instead only shared between `Environment`s that have a direct relation to each other, and addresses a few thread safety issues along with that. PR-URL: https://github.com/nodejs/node/pull/45942 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
105 lines
2.8 KiB
C++
105 lines
2.8 KiB
C++
#ifndef SRC_NODE_THREADSAFE_COW_H_
|
|
#define SRC_NODE_THREADSAFE_COW_H_
|
|
|
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
|
|
|
#include "util.h"
|
|
#include "uv.h"
|
|
|
|
#include <memory> // std::shared_ptr<T>
|
|
#include <utility> // std::forward<T>
|
|
|
|
namespace node {
|
|
|
|
// Copy-on-write utility. Not threadsafe, i.e. there is no synchronization
|
|
// of the copy operation with other operations.
|
|
template <typename T>
|
|
class CopyOnWrite final {
|
|
public:
|
|
template <typename... Args>
|
|
explicit CopyOnWrite(Args&&... args)
|
|
: data_(std::make_shared<T>(std::forward<Args>(args)...)) {}
|
|
|
|
CopyOnWrite(const CopyOnWrite<T>& other) = default;
|
|
CopyOnWrite& operator=(const CopyOnWrite<T>& other) = default;
|
|
CopyOnWrite(CopyOnWrite<T>&& other) = default;
|
|
CopyOnWrite& operator=(CopyOnWrite<T>&& other) = default;
|
|
|
|
const T* read() const { return data_.get(); }
|
|
T* write();
|
|
|
|
const T& operator*() const { return *read(); }
|
|
const T* operator->() const { return read(); }
|
|
|
|
private:
|
|
std::shared_ptr<T> data_;
|
|
};
|
|
|
|
// Threadsafe copy-on-write utility. Consumers need to use the Read and
|
|
// Write helpers to access the target data structure.
|
|
template <typename T>
|
|
class ThreadsafeCopyOnWrite final {
|
|
private:
|
|
// Define this early since some of the public members depend on it
|
|
// and some compilers need it to be defined first in that case.
|
|
struct Impl {
|
|
explicit Impl(const T& data) : data(data) {}
|
|
explicit Impl(T&& data) : data(std::move(data)) {}
|
|
|
|
Impl(const Impl& other);
|
|
Impl& operator=(const Impl& other) = delete;
|
|
Impl(Impl&& other) = delete;
|
|
Impl& operator=(Impl&& other) = delete;
|
|
|
|
RwLock mutex;
|
|
T data;
|
|
};
|
|
|
|
public:
|
|
template <typename... Args>
|
|
ThreadsafeCopyOnWrite(Args&&... args)
|
|
: impl_(T(std::forward<Args>(args)...)) {}
|
|
|
|
ThreadsafeCopyOnWrite(const ThreadsafeCopyOnWrite<T>& other) = default;
|
|
ThreadsafeCopyOnWrite& operator=(const ThreadsafeCopyOnWrite<T>& other) =
|
|
default;
|
|
ThreadsafeCopyOnWrite(ThreadsafeCopyOnWrite<T>&& other) = default;
|
|
ThreadsafeCopyOnWrite& operator=(ThreadsafeCopyOnWrite<T>&& other) = default;
|
|
|
|
class Read {
|
|
public:
|
|
explicit Read(const ThreadsafeCopyOnWrite<T>* cow);
|
|
|
|
const T& operator*() const;
|
|
const T* operator->() const;
|
|
|
|
private:
|
|
const ThreadsafeCopyOnWrite<T>* cow_;
|
|
RwLock::ScopedReadLock lock_;
|
|
};
|
|
|
|
class Write {
|
|
public:
|
|
explicit Write(ThreadsafeCopyOnWrite<T>* cow);
|
|
|
|
T& operator*();
|
|
T* operator->();
|
|
|
|
private:
|
|
ThreadsafeCopyOnWrite<T>* cow_;
|
|
typename ThreadsafeCopyOnWrite<T>::Impl* impl_;
|
|
RwLock::ScopedLock lock_;
|
|
};
|
|
|
|
Read read() const { return Read(this); }
|
|
Write write() { return Write(this); }
|
|
|
|
private:
|
|
CopyOnWrite<Impl> impl_;
|
|
};
|
|
|
|
} // namespace node
|
|
|
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
|
|
|
#endif // SRC_NODE_THREADSAFE_COW_H_
|