333 lines
8.8 KiB
C++
333 lines
8.8 KiB
C++
/************************************************************************************
|
|
*
|
|
* D++, A Lightweight C++ library for Discord
|
|
*
|
|
* Copyright 2021 Craig Edwards and D++ contributors
|
|
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
************************************************************************************/
|
|
#pragma once
|
|
#include <dpp/export.h>
|
|
#include <dpp/socket.h>
|
|
#include <cstdint>
|
|
#include <unordered_map>
|
|
#include <memory>
|
|
#include <string_view>
|
|
#include <functional>
|
|
#include <shared_mutex>
|
|
#include <dpp/thread_pool.h>
|
|
|
|
namespace dpp {
|
|
|
|
/**
|
|
* @brief Types of IO events a socket may subscribe to.
|
|
*/
|
|
enum socket_event_flags : uint8_t {
|
|
/**
|
|
* @brief Socket wants to receive events when it can be read from.
|
|
* This is provided by the underlying implementation.
|
|
*/
|
|
WANT_READ = 1,
|
|
/**
|
|
* @brief Socket wants to receive events when it can be written to.
|
|
* This is provided by the underlying implementation, and will be
|
|
* a one-off event. If you want to receive ongoing write events you
|
|
* must re-request this event type each time.
|
|
*/
|
|
WANT_WRITE = 2,
|
|
/**
|
|
* @brief Socket wants to receive events that indicate an error condition.
|
|
* Note that EOF (graceful close) is not an error condition and is indicated
|
|
* by errno being 0 and ::read() returning 0.
|
|
*/
|
|
WANT_ERROR = 4,
|
|
/**
|
|
* @brief Socket should be removed as soon as is safe to do so. Generally, this is
|
|
* after the current iteration through the active event list.
|
|
*/
|
|
WANT_DELETION = 8,
|
|
};
|
|
|
|
/**
|
|
* @brief Read ready event
|
|
*/
|
|
using socket_read_event = std::function<void(dpp::socket fd, const struct socket_events&)>;
|
|
|
|
/**
|
|
* @brief Write ready event
|
|
*/
|
|
using socket_write_event = std::function<void(dpp::socket fd, const struct socket_events&)>;
|
|
|
|
/**
|
|
* @brief Error event
|
|
*/
|
|
using socket_error_event = std::function<void(dpp::socket fd, const struct socket_events&, int error_code)>;
|
|
|
|
/**
|
|
* @brief Contains statistics about the IO loop
|
|
*/
|
|
struct DPP_EXPORT socket_stats {
|
|
/**
|
|
* @brief Number of reads since startup
|
|
*/
|
|
uint64_t reads{0};
|
|
|
|
/**
|
|
* @brief Number of writes since startup
|
|
*/
|
|
uint64_t writes{0};
|
|
|
|
/**
|
|
* @brief Number of errors since startup
|
|
*/
|
|
uint64_t errors{0};
|
|
|
|
/**
|
|
* @brief Number of updates to file descriptors
|
|
*/
|
|
uint64_t updates{0};
|
|
|
|
/**
|
|
* @brief Number of deletions of file descriptors
|
|
*/
|
|
uint64_t deletions{0};
|
|
|
|
/**
|
|
* @brief Number of loop iterations since startup
|
|
*/
|
|
uint64_t iterations{0};
|
|
|
|
/**
|
|
* @brief Number of currently active file descriptors
|
|
*/
|
|
uint64_t active_fds{0};
|
|
|
|
/**
|
|
* @brief Socket engine type
|
|
*/
|
|
std::string_view engine_type;
|
|
};
|
|
|
|
/**
|
|
* @brief Represents an active socket event set in the socket engine.
|
|
*
|
|
* An event set contains a file descriptor, a set of event handler callbacks, and
|
|
* a set of bitmask flags which indicate which events it wants to receive.
|
|
* It is possible to quickly toggle event types on or off, as it is not always necessary
|
|
* or desired to receive all events all the time, in fact doing so can cause an event
|
|
* storm which will consume 100% CPU (e.g. if you request to receive write events all
|
|
* the time).
|
|
*/
|
|
struct DPP_EXPORT socket_events {
|
|
/**
|
|
* @brief File descriptor
|
|
*
|
|
* This should be a valid file descriptor created via ::socket().
|
|
*/
|
|
dpp::socket fd{INVALID_SOCKET};
|
|
|
|
/**
|
|
* @brief Flag bit mask of values from dpp::socket_event_flags
|
|
*/
|
|
uint8_t flags{0};
|
|
|
|
/**
|
|
* @brief Read ready event
|
|
* @note This function will be called from a different thread to that
|
|
* which adds the event set to the socket engine.
|
|
*/
|
|
socket_read_event on_read{};
|
|
|
|
/**
|
|
* @brief Write ready event
|
|
* @note This function will be called from a different thread to that
|
|
* which adds the event set to the socket engine.
|
|
*/
|
|
socket_write_event on_write{};
|
|
|
|
/**
|
|
* @brief Error event
|
|
* @note This function will be called from a different thread to that
|
|
* which adds the event set to the socket engine.
|
|
*/
|
|
socket_error_event on_error{};
|
|
|
|
/**
|
|
* @brief Construct a new socket_events
|
|
* @param socket_fd file descriptor
|
|
* @param _flags initial flags bitmask
|
|
* @param read_event read ready event
|
|
* @param write_event write ready event
|
|
* @param error_event error event
|
|
*/
|
|
socket_events(dpp::socket socket_fd, uint8_t _flags, const socket_read_event& read_event, const socket_write_event& write_event = {}, const socket_error_event& error_event = {})
|
|
: fd(socket_fd), flags(_flags), on_read(read_event), on_write(write_event), on_error(error_event) { }
|
|
|
|
/**
|
|
* @brief Default constructor
|
|
*/
|
|
socket_events() = default;
|
|
};
|
|
|
|
/**
|
|
* @brief Container of event sets keyed by socket file descriptor
|
|
*/
|
|
using socket_container = std::unordered_map<dpp::socket, std::unique_ptr<socket_events>>;
|
|
|
|
/**
|
|
* @brief This is the base class for socket engines.
|
|
* The actual implementation is OS specific and the correct implementation is detected by
|
|
* CMake. It is then compiled specifically into DPP so only one implementation can exist
|
|
* in the implementation. All implementations should behave identically to the user, abstracting
|
|
* out implementation-specific behaviours (e.g. difference between edge and level triggered
|
|
* event mechanisms etc).
|
|
*/
|
|
struct DPP_EXPORT socket_engine_base {
|
|
|
|
/**
|
|
* @brief Owning cluster
|
|
*/
|
|
class cluster* owner{nullptr};
|
|
|
|
/**
|
|
* @brief Default constructor
|
|
* @param creator Owning cluster
|
|
*/
|
|
socket_engine_base(class cluster* creator);
|
|
|
|
/**
|
|
* @brief Non-copyable
|
|
*/
|
|
socket_engine_base(const socket_engine_base&) = delete;
|
|
|
|
/**
|
|
* @brief Non-copyable
|
|
*/
|
|
socket_engine_base(socket_engine_base&&) = delete;
|
|
|
|
/**
|
|
* @brief Non-movable
|
|
*/
|
|
socket_engine_base& operator=(const socket_engine_base&) = delete;
|
|
|
|
/**
|
|
* @brief Non-movable
|
|
*/
|
|
socket_engine_base& operator=(socket_engine_base&&) = delete;
|
|
|
|
/**
|
|
* @brief Default destructor
|
|
*/
|
|
virtual ~socket_engine_base() = default;
|
|
|
|
/**
|
|
* @brief Should be called repeatedly in a loop.
|
|
* Will run for a maximum of 1 second.
|
|
*/
|
|
virtual void process_events() = 0;
|
|
|
|
/**
|
|
* @brief Register a new socket with the socket engine
|
|
* @param e Socket events
|
|
* @return true if socket was added
|
|
*/
|
|
virtual bool register_socket(const socket_events& e);
|
|
|
|
/**
|
|
* @brief Update an existing socket in the socket engine
|
|
* @param e Socket events
|
|
* @return true if socket was updated
|
|
*/
|
|
virtual bool update_socket(const socket_events& e);
|
|
|
|
/**
|
|
* @brief Delete a socket from the socket engine
|
|
* @note This will not remove the socket immediately. It will set the
|
|
* WANT_DELETION flag causing it to be removed as soon as is safe to do so
|
|
* (once all events associated with it are completed).
|
|
* @param e File descriptor
|
|
* @return true if socket was queued for deletion
|
|
*/
|
|
bool delete_socket(dpp::socket fd);
|
|
|
|
/**
|
|
* @brief Iterate through the list of sockets and remove any
|
|
* with WANT_DELETION set. This will also call implementation-specific
|
|
* remove_socket() on each entry to be removed.
|
|
*/
|
|
void prune();
|
|
|
|
/**
|
|
* @brief Merge new flags in with the given file descriptor
|
|
* @param fd file descriptor
|
|
* @param extra_flags extra flags to add
|
|
*/
|
|
void inplace_modify_fd(dpp::socket fd, uint8_t extra_flags);
|
|
|
|
/**
|
|
* @brief Get statistics for socket engine
|
|
* @return socket stats
|
|
*/
|
|
const socket_stats& get_stats() const;
|
|
|
|
protected:
|
|
|
|
/**
|
|
* @brief Mutex for fds
|
|
*/
|
|
std::shared_mutex fds_mutex;
|
|
|
|
/**
|
|
* @brief File descriptors, and their states
|
|
*/
|
|
socket_container fds;
|
|
|
|
/**
|
|
* @brief Socket engine statistics
|
|
*/
|
|
socket_stats stats{};
|
|
|
|
/**
|
|
* @brief Find a file descriptors socket events
|
|
* @param fd file descriptor
|
|
* @return file descriptor or nullptr if doesn't exist
|
|
*/
|
|
socket_events* get_fd(dpp::socket fd);
|
|
|
|
/**
|
|
* @brief Called by the prune() function to remove sockets when safe to do so.
|
|
* This is normally at the end or before an iteration of the event loop.
|
|
* @param fd File descriptor to remove
|
|
*/
|
|
virtual bool remove_socket(dpp::socket fd);
|
|
};
|
|
|
|
/**
|
|
* @brief This is implemented by whatever derived form socket_engine takes
|
|
* @param creator Creating cluster
|
|
*/
|
|
DPP_EXPORT std::unique_ptr<socket_engine_base> create_socket_engine(class cluster *creator);
|
|
|
|
#ifndef _WIN32
|
|
/**
|
|
* @brief Set up a signal handler to be ignored
|
|
* @param signal Signal to set. If the signal is already set up with a handler,
|
|
* this will do nothing.
|
|
*/
|
|
void set_signal_handler(int signal);
|
|
#endif
|
|
|
|
};
|