2025-08-14 15:41:56 +00:00

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
};