summaryrefslogtreecommitdiffstats
path: root/raul/Maid.hpp
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2017-02-12 13:08:15 +0100
committerDavid Robillard <d@drobilla.net>2017-02-12 15:08:37 +0100
commit4825daa42f5667516fee65660ef1a6cccf8bb947 (patch)
tree63d982b6701386bfd3e2739eb0bb602fc2e783f9 /raul/Maid.hpp
parentf8bf77d3c3b77830aedafb9ebb5cdadfea7ed07a (diff)
downloadraul-4825daa42f5667516fee65660ef1a6cccf8bb947.tar.gz
raul-4825daa42f5667516fee65660ef1a6cccf8bb947.tar.bz2
raul-4825daa42f5667516fee65660ef1a6cccf8bb947.zip
Add managed_ptr for automatic real-time safe garbage collection
Diffstat (limited to 'raul/Maid.hpp')
-rw-r--r--raul/Maid.hpp119
1 files changed, 60 insertions, 59 deletions
diff --git a/raul/Maid.hpp b/raul/Maid.hpp
index edefabe..7f5a299 100644
--- a/raul/Maid.hpp
+++ b/raul/Maid.hpp
@@ -1,6 +1,6 @@
/*
This file is part of Raul.
- Copyright 2007-2014 David Robillard <http://drobilla.net>
+ Copyright 2007-2017 David Robillard <http://drobilla.net>
Raul is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -25,76 +25,77 @@
namespace Raul {
-/** Explicit quasi-garbage-collector.
+/** Explicit garbage-collector.
*
* This allows objects to be disposed of in a real-time thread, but actually
- * deleted later by another thread which calls cleanup().
+ * deleted later by another thread which calls cleanup(). Disposable objects
+ * may be explicitly disposed by calling dispose(), or automatically managed
+ * with a managed_ptr which can safely be dropped in any thread, including
+ * real-time threads.
*
* \ingroup raul
*/
class Maid : public Noncopyable
{
public:
- /** An object that can be managed by the maid using shared_ptr. */
- class Manageable : public Deletable {
+ /** An object that can be disposed via Maid::dispose(). */
+ class Disposable : public Deletable {
public:
- Manageable() {}
+ Disposable() : _maid_next(nullptr) {}
private:
friend class Maid;
- std::shared_ptr<Manageable> _maid_next;
+ Disposable* _maid_next;
};
- /** An object that can be disposed via Maid::dispose(). */
- class Disposable : public Deletable {
+ /** Deleter for Disposable objects. */
+ template<typename T>
+ class Disposer {
public:
- Disposable() : _maid_next(NULL) {}
+ Disposer(Maid& maid) : _maid(&maid) {}
+ Disposer() : _maid(nullptr) {}
- private:
- friend class Maid;
- Disposable* _maid_next;
+ void operator()(T* obj) {
+ if (_maid) { _maid->dispose(obj); }
+ }
+
+ Maid* _maid;
};
- Maid() : _disposed(NULL) {}
+ /** A managed pointer that automatically disposes of its contents.
+ *
+ * This is a unique_ptr so that it is possible to statically verify that
+ * code is real-time safe.
+ */
+ template<typename T> using managed_ptr = std::unique_ptr<T, Disposer<T>>;
+
+ Maid() : _disposed(nullptr) {}
- inline ~Maid() {
- cleanup();
+ inline ~Maid() { cleanup(); }
+
+ /** Return false iff there is currently no garbage. */
+ inline bool empty() const {
+ return !(bool)_disposed.load(std::memory_order_relaxed);
}
- /** Dispose of an object when cleanup() is called next.
+ /** Enqueue an object for deletion when cleanup() is called next.
*
- * This is thread safe, and real-time safe assuming reasonably low
- * contention. If real-time threads need to push, do not call this very
- * rapidly from many threads.
+ * This is thread-safe, and real-time safe assuming reasonably low
+ * contention.
*/
inline void dispose(Disposable* obj) {
if (obj) {
- while (true) {
- obj->_maid_next = _disposed.load();
- if (_disposed.compare_exchange_weak(obj->_maid_next, obj)) {
- return;
- }
- }
+ // Atomically add obj to the head of the disposed list
+ do {
+ obj->_maid_next = _disposed.load(std::memory_order_relaxed);
+ } while (!_disposed.compare_exchange_weak(
+ obj->_maid_next, obj,
+ std::memory_order_release,
+ std::memory_order_relaxed));
}
}
- /** Manage an object held by a shared pointer.
- *
- * This will hold a reference to `ptr` ensuring it will not be deleted
- * except by cleanup(). This is mainly useful to allow dropping references
- * in real-time threads without causing a deletion.
- *
- * This is not thread-safe.
- *
- * Note this mechanism scales linearly. If a very large number of objects
- * are managed cleanup() will become very expensive.
- */
- inline void manage(std::shared_ptr<Manageable> ptr) {
- ptr->_maid_next = _managed;
- _managed = ptr;
- }
-
- /** Free all dead and managed objects immediately.
+ /** Delete all disposed objects immediately.
*
* Obviously not real-time safe, but may be called while other threads are
* calling dispose().
@@ -102,12 +103,12 @@ public:
inline void cleanup() {
// Atomically get the head of the disposed list
Disposable* disposed;
- while (true) {
- disposed = _disposed.load();
- if (_disposed.compare_exchange_weak(disposed, NULL)) {
- break;
- }
- }
+ do {
+ disposed = _disposed.load(std::memory_order_relaxed);
+ } while (!_disposed.compare_exchange_weak(
+ disposed, nullptr,
+ std::memory_order_acquire,
+ std::memory_order_relaxed));
// Free the disposed list
for (Disposable* obj = disposed; obj;) {
@@ -115,22 +116,22 @@ public:
delete obj;
obj = next;
}
+ }
- // Free the managed list
- std::shared_ptr<Manageable> managed = _managed;
- _managed.reset();
- for (std::shared_ptr<Manageable> obj = managed; obj;) {
- const std::shared_ptr<Manageable> next = obj->_maid_next;
- obj->_maid_next.reset();
- obj = next;
- }
+ /** Make a unique_ptr that will dispose its object when dropped. */
+ template<class T, class... Args>
+ managed_ptr<T> make_managed(Args&&... args) {
+ T* obj = new T(args...);
+ return std::unique_ptr<T, Disposer<T> >(obj, Disposer<T>(*this));
}
private:
- std::atomic<Disposable*> _disposed;
- std::shared_ptr<Manageable> _managed;
+ std::atomic<Disposable*> _disposed;
};
+template<typename T>
+using managed_ptr = Maid::managed_ptr<T>;
+
} // namespace Raul
#endif // RAUL_MAID_HPP