polybar-dwm/include/components/eventloop.hpp
Patrick Ziegler c2f087225c
Eventloop cleanup (#2577)
* eventloop: Use eventloop namespace in cpp files

* changelog: Add missing deprecated hook message

* Make eventloop and ipc classes non-copyable and non-movable

* Remove functional.hpp

* eventloop: Don't close handles in error cases

Client should be responsible for closing handles.

* eventloop: Address invalidation of handle references
2022-01-22 22:00:26 +01:00

417 lines
11 KiB
C++

#pragma once
#include <uv.h>
#include <stdexcept>
#include "common.hpp"
#include "components/logger.hpp"
#include "utils/mixins.hpp"
POLYBAR_NS
namespace eventloop {
/**
* Runs any libuv function with an integer error code return value and throws an
* exception on error.
*/
#define UV(fun, ...) \
do { \
int res = fun(__VA_ARGS__); \
if (res < 0) { \
throw std::runtime_error(__FILE__ ":"s + std::to_string(__LINE__) + \
": libuv error for '" #fun "(" #__VA_ARGS__ ")': "s + uv_strerror(res)); \
} \
} while (0);
using cb_void = function<void(void)>;
template <typename Event>
using cb_event = std::function<void(const Event&)>;
template <typename Self, typename H>
class Handle : public non_copyable_mixin, public non_movable_mixin {
public:
Handle(uv_loop_t* l) : uv_loop(l) {
get()->data = this;
}
Self& leak(std::unique_ptr<Self> h) {
lifetime_extender = std::move(h);
return *lifetime_extender;
}
void unleak() {
lifetime_extender.reset();
}
H* raw() {
return get();
}
const H* raw() const {
return get();
}
/**
* Close this handle and free associated memory.
*
* After this function returns, any reference to this object should be considered invalid.
*/
void close() {
if (!is_closing()) {
uv_close((uv_handle_t*)get(), [](uv_handle_t* handle) { close_callback(*static_cast<Self*>(handle->data)); });
}
}
bool is_closing() const {
return uv_is_closing(this->template get<uv_handle_t>());
}
bool is_active() {
return uv_is_active(this->template get<uv_handle_t>()) != 0;
}
protected:
/**
* Generic callback function that can be used for all uv handle callbacks.
*
* \tparam Event Event class/struct. Must have a constructor that takes all arguments passed to the uv callback,
* except for the handle argument.
* \tparam Member Pointer to class member where callback function is stored
* \tparam Args Additional arguments in the uv callback. Inferred by the compiler
*/
template <typename Event, cb_event<Event> Self::*Member, typename... Args>
static void event_cb(H* handle, Args... args) {
Self& This = *static_cast<Self*>(handle->data);
(This.*Member)(Event{std::forward<Args>(args)...});
}
/**
* Same as event_cb except that no event is constructed.
*/
template <cb_void Self::*Member>
static void void_event_cb(H* handle) {
Self& This = *static_cast<Self*>(handle->data);
(This.*Member)();
}
static Self& cast(H* handle) {
return *static_cast<Self*>(handle->data);
}
template <typename T = H>
T* get() {
return reinterpret_cast<T*>(&uv_handle);
}
template <typename T = H>
const T* get() const {
return reinterpret_cast<const T*>(&uv_handle);
}
uv_loop_t* loop() const {
return uv_loop;
}
static void close_callback(Self& self) {
self.unleak();
}
static void alloc_callback(uv_handle_t*, size_t, uv_buf_t* buf) {
buf->base = new char[BUFSIZ];
buf->len = BUFSIZ;
}
private:
H uv_handle;
uv_loop_t* uv_loop;
/**
* The handle stores the unique_ptr to itself so that it effectively leaks memory.
*
* This saves us from having to guarantee that the handle's lifetime extends to at least after it is closed.
*
* Once the handle is closed, either explicitly or by walking all handles when the loop shuts down, this reference
* is reset and the object is explicitly destroyed.
*/
std::unique_ptr<Self> lifetime_extender;
};
struct ErrorEvent {
int status;
};
using cb_error = cb_event<ErrorEvent>;
class WriteRequest : public non_copyable_mixin {
public:
using cb_write = cb_void;
WriteRequest(cb_write user_cb, cb_error err_cb) : write_callback(user_cb), write_err_cb(err_cb) {
get()->data = this;
};
static WriteRequest& create(cb_write user_cb, cb_error err_cb) {
auto r = std::make_unique<WriteRequest>(user_cb, err_cb);
return r->leak(std::move(r));
};
uv_write_t* get() {
return &req;
}
/**
* Trigger the write callback.
*
* After that, this object is destroyed.
*/
void trigger(int status) {
if (status < 0) {
if (write_err_cb) {
write_err_cb(ErrorEvent{status});
}
} else {
if (write_callback) {
write_callback();
}
}
unleak();
}
protected:
WriteRequest& leak(std::unique_ptr<WriteRequest> h) {
lifetime_extender = std::move(h);
return *lifetime_extender;
}
void unleak() {
lifetime_extender.reset();
}
private:
uv_write_t req;
cb_write write_callback;
cb_error write_err_cb;
/**
* The handle stores the unique_ptr to itself so that it effectively leaks memory.
*
* This means that each instance manages its own lifetime.
*/
std::unique_ptr<WriteRequest> lifetime_extender;
};
struct SignalEvent {
int signum;
};
class SignalHandle : public Handle<SignalHandle, uv_signal_t> {
public:
using Handle::Handle;
using cb = cb_event<SignalEvent>;
void init();
void start(int signum, cb user_cb);
private:
cb callback;
};
struct PollEvent {
uv_poll_event event;
};
class PollHandle : public Handle<PollHandle, uv_poll_t> {
public:
using Handle::Handle;
using cb = cb_event<PollEvent>;
void init(int fd);
void start(int events, cb user_cb, cb_error err_cb);
static void poll_callback(uv_poll_t*, int status, int events);
private:
cb callback;
cb_error err_cb;
};
struct FSEvent {
const char* path;
uv_fs_event event;
};
class FSEventHandle : public Handle<FSEventHandle, uv_fs_event_t> {
public:
using Handle::Handle;
using cb = cb_event<FSEvent>;
void init();
void start(const string& path, int flags, cb user_cb, cb_error err_cb);
static void fs_event_callback(uv_fs_event_t*, const char* path, int events, int status);
private:
cb callback;
cb_error err_cb;
};
class TimerHandle : public Handle<TimerHandle, uv_timer_t> {
public:
using Handle::Handle;
using cb = cb_void;
void init();
void start(uint64_t timeout, uint64_t repeat, cb user_cb);
void stop();
private:
cb callback;
};
class AsyncHandle : public Handle<AsyncHandle, uv_async_t> {
public:
using Handle::Handle;
using cb = cb_void;
void init(cb user_cb);
void send();
private:
cb callback;
};
struct ReadEvent {
const char* data;
size_t len;
};
template <typename Self, typename H>
class StreamHandle : public Handle<Self, H> {
public:
using Handle<Self, H>::Handle;
using cb_read = cb_event<ReadEvent>;
using cb_read_eof = cb_void;
using cb_connection = cb_void;
void listen(int backlog, cb_connection user_cb, cb_error err_cb) {
this->connection_callback = user_cb;
this->connection_err_cb = err_cb;
UV(uv_listen, this->template get<uv_stream_t>(), backlog, connection_cb);
};
static void connection_cb(uv_stream_t* server, int status) {
auto& self = Self::cast((H*)server);
if (status == 0) {
self.connection_callback();
} else {
self.connection_err_cb(ErrorEvent{status});
}
}
template <typename ClientSelf, typename ClientH>
void accept(StreamHandle<ClientSelf, ClientH>& client) {
UV(uv_accept, this->template get<uv_stream_t>(), client.template get<uv_stream_t>());
}
void read_start(cb_read fun, cb_void eof_cb, cb_error err_cb) {
this->read_callback = fun;
this->read_eof_cb = eof_cb;
this->read_err_cb = err_cb;
UV(uv_read_start, this->template get<uv_stream_t>(), &this->alloc_callback, read_cb);
};
static void read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) {
auto& self = Self::cast((H*)handle);
/*
* Wrap pointer so that it gets automatically freed once the function returns (even with exceptions)
*/
auto buf_ptr = unique_ptr<char[]>(buf->base);
if (nread > 0) {
self.read_callback(ReadEvent{buf->base, (size_t)nread});
} else if (nread < 0) {
if (nread != UV_EOF) {
self.read_err_cb(ErrorEvent{(int)nread});
} else {
self.read_eof_cb();
}
}
};
void write(const vector<uint8_t>& data, WriteRequest::cb_write user_cb = {}, cb_error err_cb = {}) {
WriteRequest& req = WriteRequest::create(user_cb, err_cb);
uv_buf_t buf{(char*)data.data(), data.size()};
UV(uv_write, req.get(), this->template get<uv_stream_t>(), &buf, 1,
[](uv_write_t* r, int status) { static_cast<WriteRequest*>(r->data)->trigger(status); });
}
private:
/**
* Callback for receiving data
*/
cb_read read_callback;
/**
* Callback for receiving EOF.
*
* Called after the associated handle has been closed.
*/
cb_read_eof read_eof_cb;
/**
* Called if an error occurs.
*/
cb_error read_err_cb;
cb_connection connection_callback;
cb_error connection_err_cb;
};
class PipeHandle : public StreamHandle<PipeHandle, uv_pipe_t> {
public:
using StreamHandle::StreamHandle;
using cb_connect = cb_void;
void init(bool ipc = false);
void open(int fd);
void bind(const string& path);
void connect(const string& name, cb_connect user_cb, cb_error err_cb);
private:
static void connect_cb(uv_connect_t* req, int status);
cb_error connect_err_cb;
cb_connect connect_callback;
};
class loop : public non_copyable_mixin, public non_movable_mixin {
public:
loop();
~loop();
void run();
void stop();
template <typename H, typename... Args>
H& handle(Args... args) {
auto ptr = make_unique<H>(get());
ptr->init(std::forward<Args>(args)...);
return ptr->leak(std::move(ptr));
}
protected:
uv_loop_t* get() const;
private:
std::unique_ptr<uv_loop_t> m_loop{nullptr};
};
} // namespace eventloop
POLYBAR_NS_END