/************************************************************************************ * * 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 #include #include #include #include #include #include #include #include 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; /** * @brief Write ready event */ using socket_write_event = std::function; /** * @brief Error event */ using socket_error_event = std::function; /** * @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>; /** * @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 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 };