mirror of
https://github.com/Kitware/CMake.git
synced 2026-01-04 12:49:36 -06:00
Add RAII handles for libuv handle types
The `uv_*_t` handle types are closed by `uv_close`, but the semantics are tricky. Calling `uv_close` may not close immediately. Instead it hands ownership to the uv loop to which the handle is currently attached. When the loop decides to close it, a callback is used to allow the `uv_close` caller to free resources. Provide an abstraction layer as `cm::uv_*_ptr` types corresponding to the `uv_*_t` handle types. Each pointer is either empty (`nullptr`) or has an initialized handle attached to a loop. Use move semantics to ensure a single owner of the handle so that clients can predict when the handle is destroyed.
This commit is contained in:
@@ -1029,6 +1029,7 @@ list(APPEND _tools cmake)
|
||||
target_link_libraries(cmake CMakeLib)
|
||||
|
||||
add_library(CMakeServerLib
|
||||
cmUVHandlePtr.h cmUVHandlePtr.cxx
|
||||
cmConnection.h cmConnection.cxx
|
||||
cmFileMonitor.cxx cmFileMonitor.h
|
||||
cmPipeConnection.cxx cmPipeConnection.h
|
||||
|
||||
198
Source/cmUVHandlePtr.cxx
Normal file
198
Source/cmUVHandlePtr.cxx
Normal file
@@ -0,0 +1,198 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#define cmUVHandlePtr_cxx
|
||||
#include "cmUVHandlePtr.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cm_thread.hxx"
|
||||
#include "cm_uv.h"
|
||||
|
||||
namespace cm {
|
||||
|
||||
static void close_delete(uv_handle_t* h)
|
||||
{
|
||||
free(h);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void default_delete(T* type_handle)
|
||||
{
|
||||
auto handle = reinterpret_cast<uv_handle_t*>(type_handle);
|
||||
if (handle) {
|
||||
assert(!uv_is_closing(handle));
|
||||
if (!uv_is_closing(handle)) {
|
||||
uv_close(handle, &close_delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates delete logic for a given handle type T
|
||||
*/
|
||||
template <typename T>
|
||||
struct uv_handle_deleter
|
||||
{
|
||||
void operator()(T* type_handle) const { default_delete(type_handle); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void uv_handle_ptr_base_<T>::allocate(void* data)
|
||||
{
|
||||
reset();
|
||||
|
||||
/*
|
||||
We use calloc since we know all these types are c structs
|
||||
and we just want to 0 init them. New would do the same thing;
|
||||
but casting from uv_handle_t to certain other types -- namely
|
||||
uv_timer_t -- triggers a cast_align warning on certain systems.
|
||||
*/
|
||||
handle.reset(static_cast<T*>(calloc(1, sizeof(T))), uv_handle_deleter<T>());
|
||||
handle->data = data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void uv_handle_ptr_base_<T>::reset()
|
||||
{
|
||||
handle.reset();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
uv_handle_ptr_base_<T>::operator uv_handle_t*()
|
||||
{
|
||||
return reinterpret_cast<uv_handle_t*>(handle.get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* uv_handle_ptr_base_<T>::operator->() const noexcept
|
||||
{
|
||||
return handle.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* uv_handle_ptr_base_<T>::get() const
|
||||
{
|
||||
return handle.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
uv_handle_ptr_<T>::operator T*() const
|
||||
{
|
||||
return this->handle.get();
|
||||
}
|
||||
|
||||
template <>
|
||||
struct uv_handle_deleter<uv_async_t>
|
||||
{
|
||||
/***
|
||||
* Wile uv_async_send is itself thread-safe, there are
|
||||
* no strong guarantees that close hasn't already been
|
||||
* called on the handle; and that it might be deleted
|
||||
* as the send call goes through. This mutex guards
|
||||
* against that.
|
||||
*
|
||||
* The shared_ptr here is to allow for copy construction
|
||||
* which is mandated by the standard for Deleter on
|
||||
* shared_ptrs.
|
||||
*/
|
||||
std::shared_ptr<cm::mutex> handleMutex;
|
||||
|
||||
uv_handle_deleter()
|
||||
: handleMutex(std::make_shared<cm::mutex>())
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(uv_async_t* handle)
|
||||
{
|
||||
cm::lock_guard<cm::mutex> lock(*handleMutex);
|
||||
default_delete(handle);
|
||||
}
|
||||
};
|
||||
|
||||
void uv_async_ptr::send()
|
||||
{
|
||||
auto deleter = std::get_deleter<uv_handle_deleter<uv_async_t>>(this->handle);
|
||||
assert(deleter);
|
||||
|
||||
cm::lock_guard<cm::mutex> lock(*deleter->handleMutex);
|
||||
if (this->handle) {
|
||||
uv_async_send(*this);
|
||||
}
|
||||
}
|
||||
|
||||
int uv_async_ptr::init(uv_loop_t& loop, uv_async_cb async_cb, void* data)
|
||||
{
|
||||
allocate(data);
|
||||
return uv_async_init(&loop, handle.get(), async_cb);
|
||||
}
|
||||
|
||||
template <>
|
||||
struct uv_handle_deleter<uv_signal_t>
|
||||
{
|
||||
void operator()(uv_signal_t* handle) const
|
||||
{
|
||||
if (handle) {
|
||||
uv_signal_stop(handle);
|
||||
default_delete(handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int uv_signal_ptr::init(uv_loop_t& loop, void* data)
|
||||
{
|
||||
allocate(data);
|
||||
return uv_signal_init(&loop, handle.get());
|
||||
}
|
||||
|
||||
int uv_signal_ptr::start(uv_signal_cb cb, int signum)
|
||||
{
|
||||
assert(handle);
|
||||
return uv_signal_start(*this, cb, signum);
|
||||
}
|
||||
|
||||
void uv_signal_ptr::stop()
|
||||
{
|
||||
if (handle) {
|
||||
uv_signal_stop(*this);
|
||||
}
|
||||
}
|
||||
|
||||
int uv_pipe_ptr::init(uv_loop_t& loop, int ipc, void* data)
|
||||
{
|
||||
allocate(data);
|
||||
return uv_pipe_init(&loop, *this, ipc);
|
||||
}
|
||||
|
||||
uv_pipe_ptr::operator uv_stream_t*() const
|
||||
{
|
||||
return reinterpret_cast<uv_stream_t*>(handle.get());
|
||||
}
|
||||
|
||||
uv_tty_ptr::operator uv_stream_t*() const
|
||||
{
|
||||
return reinterpret_cast<uv_stream_t*>(handle.get());
|
||||
}
|
||||
|
||||
int uv_tty_ptr::init(uv_loop_t& loop, int fd, int readable, void* data)
|
||||
{
|
||||
allocate(data);
|
||||
return uv_tty_init(&loop, *this, fd, readable);
|
||||
}
|
||||
|
||||
template class uv_handle_ptr_base_<uv_handle_t>;
|
||||
|
||||
#define UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(NAME) \
|
||||
template class uv_handle_ptr_base_<uv_##NAME##_t>; \
|
||||
template class uv_handle_ptr_<uv_##NAME##_t>;
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async)
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(signal)
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(pipe)
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(stream)
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(tty)
|
||||
}
|
||||
200
Source/cmUVHandlePtr.h
Normal file
200
Source/cmUVHandlePtr.h
Normal file
@@ -0,0 +1,200 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include "cm_uv.h"
|
||||
|
||||
#define CM_PERFECT_FWD_CTOR(Class, FwdTo) \
|
||||
template <typename... Args> \
|
||||
Class(Args&&... args) \
|
||||
: FwdTo(std::forward<Args>(args)...) \
|
||||
{ \
|
||||
}
|
||||
|
||||
namespace cm {
|
||||
|
||||
/***
|
||||
* RAII class to simplify and insure the safe usage of uv_*_t types. This
|
||||
* includes making sure resources are properly freed and contains casting
|
||||
* operators which allow for passing into relevant uv_* functions.
|
||||
*
|
||||
*@tparam T actual uv_*_t type represented.
|
||||
*/
|
||||
template <typename T>
|
||||
class uv_handle_ptr_base_
|
||||
{
|
||||
protected:
|
||||
template <typename _T>
|
||||
friend class uv_handle_ptr_base_;
|
||||
|
||||
/**
|
||||
* This must be a pointer type since the handle can outlive this class.
|
||||
* When uv_close is eventually called on the handle, the memory the
|
||||
* handle inhabits must be valid until the close callback is called
|
||||
* which can be later on in the loop.
|
||||
*/
|
||||
std::shared_ptr<T> handle;
|
||||
|
||||
/**
|
||||
* Allocate memory for the type and optionally set it's 'data' pointer.
|
||||
* Protected since this should only be called for an appropriate 'init'
|
||||
* call.
|
||||
*
|
||||
* @param data data pointer to set
|
||||
*/
|
||||
void allocate(void* data = nullptr);
|
||||
|
||||
public:
|
||||
CM_DISABLE_COPY(uv_handle_ptr_base_)
|
||||
uv_handle_ptr_base_(uv_handle_ptr_base_&&) noexcept;
|
||||
uv_handle_ptr_base_& operator=(uv_handle_ptr_base_&&) noexcept;
|
||||
|
||||
/**
|
||||
* This move constructor allows us to move out of a more specialized
|
||||
* uv type into a less specialized one. The only constraint is that
|
||||
* the right hand side is castable to T.
|
||||
*
|
||||
* This allows you to return uv_handle_ptr or uv_stream_ptr from a function
|
||||
* that initializes something like uv_pipe_ptr or uv_tcp_ptr and interact
|
||||
* and clean up after it without caring about the exact type.
|
||||
*/
|
||||
template <typename S, typename = typename std::enable_if<
|
||||
std::is_rvalue_reference<S&&>::value>::type>
|
||||
uv_handle_ptr_base_(S&& rhs)
|
||||
{
|
||||
// This will force a compiler error if rhs doesn't have a casting
|
||||
// operator to get T*
|
||||
this->handle = std::shared_ptr<T>(rhs.handle, rhs);
|
||||
rhs.handle.reset();
|
||||
}
|
||||
|
||||
// Dtor and ctor need to be inline defined like this for default ctors and
|
||||
// dtors to work.
|
||||
uv_handle_ptr_base_() {}
|
||||
uv_handle_ptr_base_(std::nullptr_t) {}
|
||||
~uv_handle_ptr_base_() { reset(); }
|
||||
|
||||
/**
|
||||
* Properly close the handle if needed and sets the inner handle to nullptr
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Allow less verbose calling of uv_handle_* functions
|
||||
* @return reinterpreted handle
|
||||
*/
|
||||
operator uv_handle_t*();
|
||||
|
||||
T* get() const;
|
||||
T* operator->() const noexcept;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline uv_handle_ptr_base_<T>::uv_handle_ptr_base_(
|
||||
uv_handle_ptr_base_<T>&&) noexcept = default;
|
||||
template <typename T>
|
||||
inline uv_handle_ptr_base_<T>& uv_handle_ptr_base_<T>::operator=(
|
||||
uv_handle_ptr_base_<T>&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* While uv_handle_ptr_base_ only exposes uv_handle_t*, this exposes uv_T_t*
|
||||
* too. It is broken out like this so we can reuse most of the code for the
|
||||
* uv_handle_ptr class.
|
||||
*/
|
||||
template <typename T>
|
||||
class uv_handle_ptr_ : public uv_handle_ptr_base_<T>
|
||||
{
|
||||
template <typename _T>
|
||||
friend class uv_handle_ptr_;
|
||||
|
||||
public:
|
||||
CM_PERFECT_FWD_CTOR(uv_handle_ptr_, uv_handle_ptr_base_<T>);
|
||||
|
||||
/***
|
||||
* Allow less verbose calling of uv_<T> functions
|
||||
* @return reinterpreted handle
|
||||
*/
|
||||
operator T*() const;
|
||||
};
|
||||
|
||||
/***
|
||||
* This specialization is required to avoid duplicate 'operator uv_handle_t*()'
|
||||
* declarations
|
||||
*/
|
||||
template <>
|
||||
class uv_handle_ptr_<uv_handle_t> : public uv_handle_ptr_base_<uv_handle_t>
|
||||
{
|
||||
public:
|
||||
CM_PERFECT_FWD_CTOR(uv_handle_ptr_, uv_handle_ptr_base_<uv_handle_t>);
|
||||
};
|
||||
|
||||
class uv_async_ptr : public uv_handle_ptr_<uv_async_t>
|
||||
{
|
||||
public:
|
||||
CM_PERFECT_FWD_CTOR(uv_async_ptr, uv_handle_ptr_<uv_async_t>);
|
||||
|
||||
int init(uv_loop_t& loop, uv_async_cb async_cb, void* data = nullptr);
|
||||
|
||||
void send();
|
||||
};
|
||||
|
||||
struct uv_signal_ptr : public uv_handle_ptr_<uv_signal_t>
|
||||
{
|
||||
CM_PERFECT_FWD_CTOR(uv_signal_ptr, uv_handle_ptr_<uv_signal_t>);
|
||||
|
||||
int init(uv_loop_t& loop, void* data = nullptr);
|
||||
|
||||
int start(uv_signal_cb cb, int signum);
|
||||
|
||||
void stop();
|
||||
};
|
||||
|
||||
struct uv_pipe_ptr : public uv_handle_ptr_<uv_pipe_t>
|
||||
{
|
||||
CM_PERFECT_FWD_CTOR(uv_pipe_ptr, uv_handle_ptr_<uv_pipe_t>);
|
||||
|
||||
operator uv_stream_t*() const;
|
||||
|
||||
int init(uv_loop_t& loop, int ipc, void* data = nullptr);
|
||||
};
|
||||
|
||||
struct uv_tty_ptr : public uv_handle_ptr_<uv_tty_t>
|
||||
{
|
||||
CM_PERFECT_FWD_CTOR(uv_tty_ptr, uv_handle_ptr_<uv_tty_t>);
|
||||
|
||||
operator uv_stream_t*() const;
|
||||
|
||||
int init(uv_loop_t& loop, int fd, int readable, void* data = nullptr);
|
||||
};
|
||||
|
||||
typedef uv_handle_ptr_<uv_stream_t> uv_stream_ptr;
|
||||
typedef uv_handle_ptr_<uv_handle_t> uv_handle_ptr;
|
||||
|
||||
#ifndef cmUVHandlePtr_cxx
|
||||
|
||||
extern template class uv_handle_ptr_base_<uv_handle_t>;
|
||||
|
||||
#define UV_HANDLE_PTR_INSTANTIATE_EXTERN(NAME) \
|
||||
extern template class uv_handle_ptr_base_<uv_##NAME##_t>; \
|
||||
extern template class uv_handle_ptr_<uv_##NAME##_t>;
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXTERN(async)
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXTERN(signal)
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXTERN(pipe)
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXTERN(stream)
|
||||
|
||||
UV_HANDLE_PTR_INSTANTIATE_EXTERN(tty)
|
||||
|
||||
#undef UV_HANDLE_PTR_INSTANTIATE_EXTERN
|
||||
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user