gloo/common/memory.h (58 lines of code) (raw):
/**
* Copyright (c) 2019-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifndef _WIN32
#include <unistd.h>
#endif
#include <memory>
#include <thread>
namespace gloo {
// The lifecycle of the unbound buffer is controlled by the user.
// It may be constructed as value or unique_ptr and destructed when
// going out of scope or when unwinding the stack.
//
// For example:
//
// {
// int i = 0;
// auto buf = context->createUnboundBuffer(&i, sizeof(i));
// buf->send(1, 0);
// ...
// throw std::runtime_error("Something happened!");
// ...
// buf->waitSend();
// }
//
// // At this point, if `waitSend` was not yet called, the I/O
// // thread executing the send operation may still call into it.
// // The unbound buffer will have been destructed already,
// // so we need to make sure the reference gets invalidated.
//
// If upon destruction there are pending operations (e.g. a recv
// operation timed out), there will be references to this unbound
// buffer in the `transport::Pair` instances that could fulfill the
// operation. To avoid these pairs calling into the unbound buffer
// after it is destructed, we must ensure that these references are
// invalidated before the destructor returns. Also, if destruction of
// the unbound buffer races with the recv operation completing after
// all, we must block the destructor and wait for the operation to
// finish. Otherwise, we risk the device thread writing to memory it's
// not supposed to.
//
// We solve this by using a shared_ptr of the "this" pointer of the
// unbound buffer. This doesn't magically convert the unbound buffer
// to be a shared_ptr, but it allows for handing out weak_ptr
// instances to refer to it. Then, whenever the unbound buffer is
// used by another thread, it converts the weak_ptr into a shared_ptr
// and uses it for a very short period of time. The wrapper class below
// waits for all shared_ptr instances to be released before returning
// from its destructor. This will block indefinitely if the shared_ptr
// acquired from the weak_ptr stays alive.
//
// Forward definitions.
template <typename T>
class WeakNonOwningPtr;
template <typename T>
class ShareableNonOwningPtr;
// NonOwningPtr is constructed from a WeakNonOwningPtr, if and
// only if the underlying object is still alive. If it is, destruction
// of the underlying object is blocked until the NonOwningPtr
// is destructed. It boxes a shared_ptr instead of being typedef'd as
// one to prevent misuse (e.g. extending its lifetime).
template <typename T>
class NonOwningPtr final {
public:
NonOwningPtr() {}
explicit NonOwningPtr(const WeakNonOwningPtr<T>& ptr)
: ptr_(ptr.ptr_.lock()) {}
T* operator->() const noexcept {
return ptr_.get();
}
explicit operator bool() const noexcept {
return (bool)ptr_;
}
private:
std::shared_ptr<T> ptr_;
};
// WeakNonOwningPtr can be constructed from a ShareableNonOwningPtr.
// It can instantiate a NonOwningPtr if and only if the
// underlying object is still alive. It boxes a weak_ptr instead of
// being typedef'd as one because it must instantiate the
// NonOwningPtr type instead of a raw shared_ptr.
template <typename T>
class WeakNonOwningPtr final {
public:
WeakNonOwningPtr() {}
explicit WeakNonOwningPtr(const ShareableNonOwningPtr<T>& ref)
: ptr_(ref.ptr_) {}
// Returns true if the instance was initialized.
explicit operator bool() const noexcept {
// Per std::weak_ptr::owner_before, "[...] two smart pointers
// compare equivalent only if they are both empty or if they both
// own the same object [...]". Therefore, if owner_before is true
// in either direction w.r.t. an empty weak_ptr, the instance was
// initialized.
return ptr_.owner_before(std::weak_ptr<T>{}) ||
std::weak_ptr<T>{}.owner_before(ptr_);
}
protected:
std::weak_ptr<T> ptr_;
friend class NonOwningPtr<T>;
};
// ShareableNonOwningPtr is the root reference to the this pointer. It
// can instantiate WeakNonOwningPtr instances. The destructor blocks
// until all NonOwningPtr instances have been destroyed. This
// guarantees that the object it refers to has no other active
// references (it may have weak ones) and can safely be destructed.
template <typename T>
class ShareableNonOwningPtr final {
public:
// Initializes private shared_ptr with nop deallocation function.
explicit ShareableNonOwningPtr(T* t) : ptr_(t, [](void* ptr) {}) {}
// Disable copy constructors.
ShareableNonOwningPtr(const ShareableNonOwningPtr&) = delete;
ShareableNonOwningPtr& operator=(ShareableNonOwningPtr const&) = delete;
~ShareableNonOwningPtr() {
// Acquire weak_ptr to T
auto weak = std::weak_ptr<T>(ptr_);
// Release shared_ptr to T
ptr_.reset();
// Wait for all shared_ptr's to T have been released
while (!weak.expired()) {
std::this_thread::yield();
}
}
protected:
std::shared_ptr<T> ptr_;
friend class WeakNonOwningPtr<T>;
};
} // namespace gloo