From eb5d05b169596ad955f1d5c2bf72408e4d3f0a71 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 3 Apr 2024 17:56:11 +0000 Subject: [PATCH 001/112] socket engine basics --- include/dpp/socketengine.h | 60 ++++++++++++++++ src/dpp/socketengine.cpp | 67 ++++++++++++++++++ src/dpp/socketengines/epoll.cpp | 103 ++++++++++++++++++++++++++++ src/dpp/socketengines/poll.cpp | 117 ++++++++++++++++++++++++++++++++ 4 files changed, 347 insertions(+) create mode 100644 include/dpp/socketengine.h create mode 100644 src/dpp/socketengine.cpp create mode 100644 src/dpp/socketengines/epoll.cpp create mode 100644 src/dpp/socketengines/poll.cpp diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h new file mode 100644 index 0000000000..12ba096743 --- /dev/null +++ b/include/dpp/socketengine.h @@ -0,0 +1,60 @@ +/************************************************************************************ + * + * 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 + +enum socket_event_flags : uint8_t { + WANT_READ = 1, + WANT_WRITE = 2, + WANT_ERROR = 4, +}; + +using socket_read_event = auto (*)(const struct socket_event&) -> void; +using socket_write_event = auto (*)(const struct socket_event&) -> void; +using socket_error_event = auto (*)(const struct socket_event&, int error_code) -> void; + +struct socket_events { + uint8_t flags{0}; + socket_read_event on_read{}; + socket_write_event on_write{}; + socket_error_event on_error{}; +}; + +using socket_container = std::unordered_map>; + +struct socket_engine_base { + socket_container fds; + + socket_engine_base(); + virtual ~socket_engine_base() = default; + + virtual void run() = 0; + virtual bool register_socket(dpp::socket fd, const socket_events& e); + virtual bool update_socket(dpp::socket fd, const socket_events& e); + virtual bool remove_socket(dpp::socket fd); +}; + +/* This is implemented by whatever derived form socket_engine takes */ +std::unique_ptr create_socket_engine(); diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp new file mode 100644 index 0000000000..6d16ae2328 --- /dev/null +++ b/src/dpp/socketengine.cpp @@ -0,0 +1,67 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ + +#include +#include +#include + +bool socket_engine_base::register_socket(dpp::socket fd, const socket_events &e) { + if (fd > INVALID_SOCKET && fds.find(fd) == fds.end()) { + auto se = std::make_unique(); + *se = e; + return true; + } + return false; +} + +bool socket_engine_base::update_socket(dpp::socket fd, const socket_events &e) { + if (fd > INVALID_SOCKET && fds.find(fd) != fds.end()) { + auto iter = fds.find(fd); + *(iter->second) = e; + return true; + } + return false; +} + +bool socket_engine_base::remove_socket(dpp::socket fd) { + auto iter = fds.find(fd); + if (iter != fds.end()) { + fds.erase(iter); + return true; + } + return false; +} + +socket_engine_base::socket_engine_base() { + #ifndef WIN32 + signal(SIGALRM, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + signal(SIGXFSZ, SIG_IGN); + #else + // Set up winsock. + WSADATA wsadata; + if (WSAStartup(MAKEWORD(2, 2), &wsadata)) { + throw dpp::connection_exception(err_connect_failure, "WSAStartup failure"); + } + #endif +} diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp new file mode 100644 index 0000000000..e81026cb66 --- /dev/null +++ b/src/dpp/socketengines/epoll.cpp @@ -0,0 +1,103 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#include + +struct socket_engine_epoll : public socket_engine_base { + + int epoll_handle{INVALID_SOCKET}; + const int epoll_hint = 128; + + socket_engine_epoll() : epoll_handle(epoll_create(epoll_hint)) { + + if (epoll_handle == -1) { + throw dpp::connection_exception("Failed to initialise epoll()"); + } + } + + ~socket_engine_epoll() { + if (epoll_handle != INVALID_SOCKET) { + close(epoll_handle); + } + } + + void run() override { + // TODO: event routing loop for epoll() goes here + } + + bool register_socket(dpp::socket fd, const socket_events& e) final { + bool r = socket_engine_base::register_socket(fd, e); + if (r) { + struct epoll_event ev{}; + if ((e.flags & WANT_READ) != 0) { + ev.events |= EPOLLIN; + } + if ((e.flags & WANT_WRITE) != 0) { + ev.events |= EPOLLOUT; + } + ev.data.ptr = fds.find(fd)->second.get(); + int i = epoll_ctl(epoll_handle, EPOLL_CTL_ADD, fd, &ev); + if (i < 0) { + throw dpp::connection_exception("Failed to register socket to epoll_ctl()"); + } + } + return r; + } + + bool update_socket(dpp::socket fd, const socket_events& e) final { + bool r = socket_engine_base::update_socket(fd, e); + if (r) { + struct epoll_event ev{}; + if ((e.flags & WANT_READ) != 0) { + ev.events |= EPOLLIN; + } + if ((e.flags & WANT_WRITE) != 0) { + ev.events |= EPOLLOUT; + } + ev.data.ptr = fds.find(fd)->second.get(); + int i = epoll_ctl(epoll_handle, EPOLL_CTL_MOD, fd, &ev); + if (i < 0) { + throw dpp::connection_exception("Failed to modify socket with epoll_ctl()"); + } + } + return r; + } + + bool remove_socket(dpp::socket fd) final { + bool r = socket_engine_base::remove_socket(fd); + if (r) { + struct epoll_event ev{}; + int i = epoll_ctl(epoll_handle, EPOLL_CTL_DEL, fd, &ev); + if (i < 0) { + throw dpp::connection_exception("Failed to deregister socket with epoll_ctl()"); + } + + } + return r; + } +}; + +std::unique_ptr create_socket_engine() { + return std::make_unique(); +} \ No newline at end of file diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp new file mode 100644 index 0000000000..7a23e49d17 --- /dev/null +++ b/src/dpp/socketengines/poll.cpp @@ -0,0 +1,117 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ + +#include +#include +#ifdef _WIN32 +/* Windows-specific sockets includes */ + #include + #include + #include + /* Windows doesn't have standard poll(), it has WSAPoll. + * It's the same thing with different symbol names. + * Microsoft gotta be different. + */ + #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) + #define pollfd WSAPOLLFD + /* Windows sockets library */ + #pragma comment(lib, "ws2_32") +#else +/* Anything other than Windows (e.g. sane OSes) */ + #include +#endif +#include + +struct socket_engine_poll : public socket_engine_base { + + /* We store the pollfds as a vector. This means that insertion, deletion and updating + * are comparatively slow O(n), but these operations don't happen too often. Obtaining the + * list to pass to poll, which can happen several times a second, is as simple as + * calling poll_set.data() and is O(1). We don't expect mind-blowing performance with poll() + * anyway. + */ + std::vector poll_set; + + void run() override { + // TODO: event routing loop for poll() goes here + } + + bool register_socket(dpp::socket fd, const socket_events& e) final { + bool r = socket_engine_base::register_socket(fd, e); + if (r) { + pollfd fd_info{}; + fd_info.fd = fd; + fd_info.events = 0; + if ((e.flags & WANT_READ) != 0) { + fd_info.events |= POLLIN; + } + if ((e.flags & WANT_WRITE) != 0) { + fd_info.events |= POLLOUT; + } + if ((e.flags & WANT_ERROR) != 0) { + fd_info.events |= POLLERR; + } + poll_set.push_back(fd_info); + } + return r; + } + + bool update_socket(dpp::socket fd, const socket_events& e) final { + bool r = socket_engine_base::update_socket(fd, e); + if (r) { + /* We know this will succeed */ + for (pollfd& fd_info : poll_set) { + if (fd_info.fd != fd) { + continue; + } + fd_info.events = 0; + if ((e.flags & WANT_READ) != 0) { + fd_info.events |= POLLIN; + } + if ((e.flags & WANT_WRITE) != 0) { + fd_info.events |= POLLOUT; + } + if ((e.flags & WANT_ERROR) != 0) { + fd_info.events |= POLLERR; + } + break; + } + } + return r; + } + + bool remove_socket(dpp::socket fd) final { + bool r = socket_engine_base::remove_socket(fd); + if (r) { + for (auto i = poll_set.begin(); i != poll_set.end(); ++i) { + if (i->fd == fd) { + poll_set.erase(i); + return true; + } + } + } + return false; + } +}; + +std::unique_ptr create_socket_engine() { + return std::make_unique(); +} \ No newline at end of file From 9566009e68002c0aeb7e80e21da235e751526a60 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 00:17:43 +0000 Subject: [PATCH 002/112] epoll and kqueue stuff --- cmake/epoll.cmake | 17 ++++ cmake/kqueue.cmake | 17 ++++ include/dpp/socketengine.h | 13 ++- library-vcpkg/CMakeLists.txt | 16 +++ library/CMakeLists.txt | 15 +++ src/dpp/socketengine.cpp | 3 +- src/dpp/socketengines/epoll.cpp | 72 ++++++++++++- src/dpp/socketengines/kqueue.cpp | 167 +++++++++++++++++++++++++++++++ src/dpp/socketengines/poll.cpp | 2 +- 9 files changed, 310 insertions(+), 12 deletions(-) create mode 100644 cmake/epoll.cmake create mode 100644 cmake/kqueue.cmake create mode 100644 src/dpp/socketengines/kqueue.cpp diff --git a/cmake/epoll.cmake b/cmake/epoll.cmake new file mode 100644 index 0000000000..63070933d7 --- /dev/null +++ b/cmake/epoll.cmake @@ -0,0 +1,17 @@ +macro(CHECK_EPOLL VARIABLE) + if(UNIX) + if("${VARIABLE}" MATCHES "^${VARIABLE}$") + message(STATUS "Check if the system supports epoll") + include(CheckSymbolExists) + check_symbol_exists(epoll_create "sys/epoll.h" EPOLL_PROTOTYPE_EXISTS) + + if(EPOLL_PROTOTYPE_EXISTS) + message(STATUS "Check if the system supports epoll - yes") + set(${VARIABLE} 1 CACHE INTERNAL "Result of CHECK_EPOLL" FORCE) + else(EPOLL_PROTOTYPE_EXISTS) + message(STATUS "Check if the system supports epoll - no") + set(${VARIABLE} "" CACHE INTERNAL "Result of CHECK_EPOLL" FORCE) + endif(EPOLL_PROTOTYPE_EXISTS) + endif("${VARIABLE}" MATCHES "^${VARIABLE}$") + endif(UNIX) +endmacro(CHECK_EPOLL) \ No newline at end of file diff --git a/cmake/kqueue.cmake b/cmake/kqueue.cmake new file mode 100644 index 0000000000..955e356d71 --- /dev/null +++ b/cmake/kqueue.cmake @@ -0,0 +1,17 @@ +macro(CHECK_KQUEUE VARIABLE) + if(UNIX) + if("${VARIABLE}" MATCHES "^${VARIABLE}$") + message(STATUS "Check if the system supports kqueue") + include(CheckSymbolExists) + check_symbol_exists(kqueue "sys/event.h" KQUEUE_PROTOTYPE_EXISTS) + + if(KQUEUE_PROTOTYPE_EXISTS) + message(STATUS "Check if the system supports kqueue - yes") + set(${VARIABLE} 1 CACHE INTERNAL "Result of CHECK_KQUEUE" FORCE) + else(KQUEUE_PROTOTYPE_EXISTS) + message(STATUS "Check if the system supports kqueue - no") + set(${VARIABLE} "" CACHE INTERNAL "Result of CHECK_KQUEUE" FORCE) + endif(KQUEUE_PROTOTYPE_EXISTS) + endif("${VARIABLE}" MATCHES "^${VARIABLE}$") + endif(UNIX) +endmacro(CHECK_KQUEUE) \ No newline at end of file diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index 12ba096743..c8871c508a 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -31,9 +31,9 @@ enum socket_event_flags : uint8_t { WANT_ERROR = 4, }; -using socket_read_event = auto (*)(const struct socket_event&) -> void; -using socket_write_event = auto (*)(const struct socket_event&) -> void; -using socket_error_event = auto (*)(const struct socket_event&, int error_code) -> void; +using socket_read_event = auto (*)(dpp::socket fd, const struct socket_events&) -> void; +using socket_write_event = auto (*)(dpp::socket fd, const struct socket_events&) -> void; +using socket_error_event = auto (*)(dpp::socket fd, const struct socket_events&, int error_code) -> void; struct socket_events { uint8_t flags{0}; @@ -48,9 +48,14 @@ struct socket_engine_base { socket_container fds; socket_engine_base(); + socket_engine_base(const socket_engine_base&) = default; + socket_engine_base(socket_engine_base&&) = default; + socket_engine_base& operator=(const socket_engine_base&) = default; + socket_engine_base& operator=(socket_engine_base&&) = default; + virtual ~socket_engine_base() = default; - virtual void run() = 0; + virtual void process_events() = 0; virtual bool register_socket(dpp::socket fd, const socket_events& e); virtual bool update_socket(dpp::socket fd, const socket_events& e); virtual bool remove_socket(dpp::socket fd); diff --git a/library-vcpkg/CMakeLists.txt b/library-vcpkg/CMakeLists.txt index dded1b52a5..ea382ea835 100644 --- a/library-vcpkg/CMakeLists.txt +++ b/library-vcpkg/CMakeLists.txt @@ -9,6 +9,22 @@ else() find_package(Threads REQUIRED) endif() +include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/epoll.cmake") +include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/kqueue.cmake") +check_epoll(HAS_EPOLL) +check_kqueue(HAS_KQUEUE) +if (HAS_EPOLL) + message("-- Building with ${Green}epoll socket engine${ColourReset} -- good!") + target_sources("${LIB_NAME}" PRIVATE "${DPP_ROOT_PATH}/src/dpp/socketengines/epoll.cpp") +elseif (HAS_KQUEUE) + message("-- Building with ${Green}kqueue socket engine${ColourReset} -- good!") + target_sources("${LIB_NAME}" PRIVATE "${DPP_ROOT_PATH}/src/dpp/socketengines/kqueue.cpp") +else() + message("-- Building with ${Green}poll socket engine${ColourReset} -- meh!") + target_sources("${LIB_NAME}" PRIVATE "${DPP_ROOT_PATH}/src/dpp/socketengines/poll.cpp") +endif() + + add_library("${PROJECT_NAME}::${LIB_NAME}" ALIAS "${LIB_NAME}") if(${AVX_TYPE} STREQUAL "OFF") diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index ba47b77cf7..8ce90495ad 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -271,6 +271,21 @@ foreach (fullmodname ${subdirlist}) endif() endforeach() +include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/epoll.cmake") +include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/kqueue.cmake") +check_epoll(HAS_EPOLL) +check_kqueue(HAS_KQUEUE) +if (HAS_EPOLL) + message("-- Building with ${Green}epoll socket engine${ColourReset} -- good!") + target_sources("dpp" PRIVATE "${modules_dir}/dpp/socketengines/epoll.cpp") +elseif (HAS_KQUEUE) + message("-- Building with ${Green}kqueue socket engine${ColourReset} -- good!") + target_sources("dpp" PRIVATE "${modules_dir}/dpp/socketengines/kqueue.cpp") +else() + message("-- Building with ${Green}poll socket engine${ColourReset} -- meh!") + target_sources("dpp" PRIVATE "${modules_dir}/dpp/socketengines/poll.cpp") +endif() + target_compile_features(dpp PUBLIC cxx_std_17) target_compile_features(dpp PRIVATE cxx_constexpr) target_compile_features(dpp PRIVATE cxx_auto_type) diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index 6d16ae2328..f48611deb3 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -25,8 +25,7 @@ bool socket_engine_base::register_socket(dpp::socket fd, const socket_events &e) { if (fd > INVALID_SOCKET && fds.find(fd) == fds.end()) { - auto se = std::make_unique(); - *se = e; + fds.emplace(fd, std::make_unique(e)); return true; } return false; diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index e81026cb66..c2ab498d8d 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -23,27 +23,80 @@ #include #include #include +#include +#include +#include struct socket_engine_epoll : public socket_engine_base { int epoll_handle{INVALID_SOCKET}; - const int epoll_hint = 128; + static const int epoll_hint = 128; + std::vector events; - socket_engine_epoll() : epoll_handle(epoll_create(epoll_hint)) { + socket_engine_epoll(const socket_engine_epoll&) = default; + socket_engine_epoll(socket_engine_epoll&&) = default; + socket_engine_epoll& operator=(const socket_engine_epoll&) = default; + socket_engine_epoll& operator=(socket_engine_epoll&&) = default; + socket_engine_epoll() : epoll_handle(epoll_create(socket_engine_epoll::epoll_hint)) { + events.resize(16); if (epoll_handle == -1) { throw dpp::connection_exception("Failed to initialise epoll()"); } } - ~socket_engine_epoll() { + ~socket_engine_epoll() override { if (epoll_handle != INVALID_SOCKET) { close(epoll_handle); } } - void run() override { - // TODO: event routing loop for epoll() goes here + void process_events() final { + const int sleep_length = 1000; + int i = epoll_wait(epoll_handle, events.data(), static_cast(events.size()), sleep_length); + + for (int j = 0; j < i; j++) { + epoll_event ev = events[j]; + + auto* const eh = static_cast(ev.data.ptr); + const int fd = ev.data.fd; + if (fd == INVALID_SOCKET) { + continue; + } + + if ((ev.events & EPOLLHUP) != 0U) { + eh->on_error(fd, *eh, 0); + continue; + } + + if ((ev.events & EPOLLERR) != 0U) { + /* Get error number */ + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { + errcode = errno; + } + eh->on_error(fd, *eh, errcode); + continue; + } + + if ((ev.events & EPOLLOUT) != 0U) { + int new_events = eh->flags & ~WANT_WRITE; + if (new_events != eh->flags) { + ev.events = new_events; + ev.data.ptr = static_cast(eh); + epoll_ctl(epoll_handle, EPOLL_CTL_MOD, fd, &ev); + } + eh->flags = new_events; + } + if (ev.events & EPOLLIN) { + eh->on_read(fd, *eh); + } + if (ev.events & EPOLLOUT) { + eh->on_write(fd, *eh); + } + } + } bool register_socket(dpp::socket fd, const socket_events& e) final { @@ -56,11 +109,17 @@ struct socket_engine_epoll : public socket_engine_base { if ((e.flags & WANT_WRITE) != 0) { ev.events |= EPOLLOUT; } + if ((e.flags & WANT_ERROR) != 0) { + ev.events |= EPOLLERR; + } ev.data.ptr = fds.find(fd)->second.get(); int i = epoll_ctl(epoll_handle, EPOLL_CTL_ADD, fd, &ev); if (i < 0) { throw dpp::connection_exception("Failed to register socket to epoll_ctl()"); } + if (fds.size() * 2 > events.size()) { + events.resize(fds.size() * 2); + } } return r; } @@ -75,6 +134,9 @@ struct socket_engine_epoll : public socket_engine_base { if ((e.flags & WANT_WRITE) != 0) { ev.events |= EPOLLOUT; } + if ((e.flags & WANT_ERROR) != 0) { + ev.events |= EPOLLERR; + } ev.data.ptr = fds.find(fd)->second.get(); int i = epoll_ctl(epoll_handle, EPOLL_CTL_MOD, fd, &ev); if (i < 0) { diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp new file mode 100644 index 0000000000..cbf3350e2b --- /dev/null +++ b/src/dpp/socketengines/kqueue.cpp @@ -0,0 +1,167 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#if defined __NetBSD__ && __NetBSD_Version__ <= 999001400 + #define CAST_TYPE intptr_t +#else + #define CAST_TYPE void* +#endif + + +struct socket_engine_kqueue : public socket_engine_base { + + int kqueue_handle{INVALID_SOCKET}; + unsigned int change_pos = 0; + std::vector change_list; + std::vector ke_list; + + socket_engine_kqueue(const socket_engine_kqueue&) = default; + socket_engine_kqueue(socket_engine_kqueue&&) = default; + socket_engine_kqueue& operator=(const socket_engine_kqueue&) = default; + socket_engine_kqueue& operator=(socket_engine_kqueue&&) = default; + + socket_engine_kqueue() : kqueue_handle(kqueue()) { + change_list.resize(8); + ke_list.resize(16); + if (kqueue_handle == -1) { + throw dpp::connection_exception("Failed to initialise kqueue()"); + } + } + + ~socket_engine_kqueue() override { + if (kqueue_handle != INVALID_SOCKET) { + close(kqueue_handle); + } + } + + struct kevent* get_change_kevent() + { + if (change_pos >= change_list.size()) { + change_list.resize(change_list.size() * 2); + } + return &change_list[change_pos++]; + } + + void process_events() final { + struct timespec ts{}; + ts.tv_sec = 1; + + int i = kevent(kqueue_handle, &change_list.front(), change_pos, &ke_list.front(), static_cast(ke_list.size()), &ts); + change_pos = 0; + + if (i < 0) { + return; + } + + for (int j = 0; j < i; j++) { + const struct kevent& kev = ke_list[j]; + auto* eh = reinterpret_cast(kev.udata); + if (eh == nullptr) { + continue; + } + + const short filter = kev.filter; + if (kev.flags & EV_EOF) { + eh->on_error(kev.ident, kev.fflags); + continue; + } + if (filter == EVFILT_WRITE) { + const int bits_to_clr = FD_WANT_WRITE; + eh->flags &= ~bits_to_clr; + eh->on_write(kev.ident, eh); + } + else if (filter == EVFILT_READ) { + eh->on_read(kev.ident, eh); + } + } + } + + void set_event_write_flags(dpp::socket fd, socket_events* eh, uint8_t old_mask, uint8_t new_mask) + { + if (((new_mask & WANT_WRITE) != 0) && ((old_mask & WANT_WRITE) == 0)) + { + struct kevent* ke = get_change_kevent(); + EV_SET(ke, fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(eh)); + } + else if (((old_mask & WANT_WRITE) != 0) && ((new_mask & WANT_WRITE) == 0)) + { + struct kevent* ke = get_change_kevent(); + EV_SET(ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + } + } + + bool register_socket(dpp::socket fd, const socket_events& e) final { + bool r = socket_engine_base::register_socket(fd, e); + if (r) { + struct kevent* ke = get_change_kevent(); + socket_events* se = fds.find(fd)->second.get(); + if ((se->flags & WANT_READ) != 0) { + EV_SET(ke, fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); + } + set_event_write_flags(fd, se, 0, e.flags); + if (fds.size() * 2 > ke_list.size()) { + ke_list.resize(fds.size() * 2); + } + } + return r; + } + + bool update_socket(dpp::socket fd, const socket_events& e) final { + bool r = socket_engine_base::update_socket(fd, e); + if (r) { + struct kevent* ke = get_change_kevent(); + socket_events* se = fds.find(fd)->second.get(); + if ((se->flags & WANT_READ) != 0) { + EV_SET(ke, fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); + } + set_event_write_flags(fd, se, 0, e.flags); + if (fds.size() * 2 > ke_list.size()) { + ke_list.resize(fds.size() * 2); + } + } + return r; + } + + bool remove_socket(dpp::socket fd) final { + bool r = socket_engine_base::remove_socket(fd); + if (r) { + struct kevent* ke = get_change_kevent(); + EV_SET(ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + + // Then remove the read filter. + ke = get_change_kevent(); + EV_SET(ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + } + return r; + } +}; + +std::unique_ptr create_socket_engine() { + return std::make_unique(); +} \ No newline at end of file diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 7a23e49d17..a89a83c741 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -50,7 +50,7 @@ struct socket_engine_poll : public socket_engine_base { */ std::vector poll_set; - void run() override { + void process_events() final { // TODO: event routing loop for poll() goes here } From 0162754dc6886ea861b88313b37531b1cf4dc0cb Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 00:31:37 +0000 Subject: [PATCH 003/112] poll class --- src/dpp/socketengines/epoll.cpp | 1 - src/dpp/socketengines/poll.cpp | 47 ++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index c2ab498d8d..981c0803d8 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index a89a83c741..2c27bf2cd6 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -37,6 +37,7 @@ #else /* Anything other than Windows (e.g. sane OSes) */ #include + #include #endif #include @@ -51,7 +52,51 @@ struct socket_engine_poll : public socket_engine_base { std::vector poll_set; void process_events() final { - // TODO: event routing loop for poll() goes here + const int poll_delay = 1000; + int i = poll(poll_set.data(), static_cast(poll_set.size()), poll_delay); + int processed = 0; + + for (size_t index = 0; index < poll_set.size() && processed < i; index++) { + struct pollfd& pfd = poll_set[index]; + const int fd = pfd.fd; + const short revents = pfd.revents; + + if (revents > 0) { + processed++; + } + + auto iter = fds.find(fd); + if (iter == fds.end()) { + continue; + } + socket_events* eh = iter->second.get(); + + if ((revents & POLLHUP) != 0) { + eh->on_error(fd, *eh, 0); + continue; + } + + if ((revents & POLLERR) != 0) { + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { + errcode = errno; + } + eh->on_error(fd, *eh, errcode); + continue; + } + + if ((revents & POLLIN) != 0) { + eh->on_read(fd, *eh); + } + + if ((revents & POLLOUT) != 0) { + int mask = eh->flags; + mask &= ~WANT_WRITE; + eh->flags = mask; + eh->on_write(fd, *eh); + } + } } bool register_socket(dpp::socket fd, const socket_events& e) final { From d1fcd83fede18d1b11a5a888d34349502952c082 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 07:17:47 +0000 Subject: [PATCH 004/112] make this default to the hint size not 16 --- src/dpp/socketengines/epoll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 981c0803d8..a3270e070b 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -38,7 +38,7 @@ struct socket_engine_epoll : public socket_engine_base { socket_engine_epoll& operator=(socket_engine_epoll&&) = default; socket_engine_epoll() : epoll_handle(epoll_create(socket_engine_epoll::epoll_hint)) { - events.resize(16); + events.resize(socket_engine_epoll::epoll_hint); if (epoll_handle == -1) { throw dpp::connection_exception("Failed to initialise epoll()"); } From 3f216b3432133a0b3c71a84a2a4bf1108b76fc2d Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 13:03:46 +0000 Subject: [PATCH 005/112] socktest --- include/dpp/dpp.h | 1 + src/sockettest/socket.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/sockettest/socket.cpp diff --git a/include/dpp/dpp.h b/include/dpp/dpp.h index 9e60093b47..b5e21866c3 100644 --- a/include/dpp/dpp.h +++ b/include/dpp/dpp.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp new file mode 100644 index 0000000000..2554f54424 --- /dev/null +++ b/src/sockettest/socket.cpp @@ -0,0 +1,34 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * 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. + * + ************************************************************************************/ + +#include +#include +#include +#include + +int main() { + auto se = create_socket_engine(); + do { + se->process_events(); + std::cout << "Loop...\n"; + } while (true); +} From 4a7bcebd5d177acd437a2e52a1f0b68adf420729 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 13:08:33 +0000 Subject: [PATCH 006/112] poll --- src/dpp/socketengines/poll.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 2c27bf2cd6..5b9453ba23 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -57,9 +57,8 @@ struct socket_engine_poll : public socket_engine_base { int processed = 0; for (size_t index = 0; index < poll_set.size() && processed < i; index++) { - struct pollfd& pfd = poll_set[index]; - const int fd = pfd.fd; - const short revents = pfd.revents; + const int fd = poll_set[index].fd; + const short revents = poll_set[index].revents; if (revents > 0) { processed++; @@ -159,4 +158,4 @@ struct socket_engine_poll : public socket_engine_base { std::unique_ptr create_socket_engine() { return std::make_unique(); -} \ No newline at end of file +} From 1b918241c0e47e9cb95b3e393d159f659b735742 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 13:10:15 +0000 Subject: [PATCH 007/112] includes --- src/dpp/socketengines/epoll.cpp | 3 ++- src/dpp/socketengines/kqueue.cpp | 3 ++- src/dpp/socketengines/poll.cpp | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index a3270e070b..10e8ff8aec 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include struct socket_engine_epoll : public socket_engine_base { @@ -161,4 +162,4 @@ struct socket_engine_epoll : public socket_engine_base { std::unique_ptr create_socket_engine() { return std::make_unique(); -} \ No newline at end of file +} diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index cbf3350e2b..024175aabb 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #if defined __NetBSD__ && __NetBSD_Version__ <= 999001400 #define CAST_TYPE intptr_t @@ -164,4 +165,4 @@ struct socket_engine_kqueue : public socket_engine_base { std::unique_ptr create_socket_engine() { return std::make_unique(); -} \ No newline at end of file +} diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 5b9453ba23..f7bf0fdba3 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -38,6 +38,7 @@ /* Anything other than Windows (e.g. sane OSes) */ #include #include + #include #endif #include From a1e96f18c6a3606b6b7148109279fe9cfc8a9934 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 13:14:39 +0000 Subject: [PATCH 008/112] getsockopt --- src/dpp/socketengines/poll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index f7bf0fdba3..8233632f7e 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -79,7 +79,7 @@ struct socket_engine_poll : public socket_engine_base { if ((revents & POLLERR) != 0) { socklen_t codesize = sizeof(int); int errcode{}; - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&errcode, &codesize) < 0) { errcode = errno; } eh->on_error(fd, *eh, errcode); From 7c4ee4616f4f279803245ebaf46a089c0907c7ea Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 13:18:47 +0000 Subject: [PATCH 009/112] kqueue fixes --- src/dpp/socketengines/kqueue.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 024175aabb..b9ebe7aca0 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -89,16 +89,16 @@ struct socket_engine_kqueue : public socket_engine_base { const short filter = kev.filter; if (kev.flags & EV_EOF) { - eh->on_error(kev.ident, kev.fflags); + eh->on_error(kev.ident, *eh, kev.fflags); continue; } if (filter == EVFILT_WRITE) { const int bits_to_clr = FD_WANT_WRITE; eh->flags &= ~bits_to_clr; - eh->on_write(kev.ident, eh); + eh->on_write(kev.ident, *eh); } else if (filter == EVFILT_READ) { - eh->on_read(kev.ident, eh); + eh->on_read(kev.ident, *eh); } } } From a0cc1588c936be940814c286b1f381e2d59f73ee Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 13:22:31 +0000 Subject: [PATCH 010/112] kqueue fixes --- src/dpp/socketengines/kqueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index b9ebe7aca0..fbbcd68ec1 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -93,7 +93,7 @@ struct socket_engine_kqueue : public socket_engine_base { continue; } if (filter == EVFILT_WRITE) { - const int bits_to_clr = FD_WANT_WRITE; + const int bits_to_clr = WANT_WRITE; eh->flags &= ~bits_to_clr; eh->on_write(kev.ident, *eh); } From 62306af6045cb0c021bdcca190c0b0154bfac573 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 17:58:52 +0000 Subject: [PATCH 011/112] move se stuff into dpp namespace --- include/dpp/socketengine.h | 4 ++++ src/dpp/socketengine.cpp | 5 +++++ src/dpp/socketengines/epoll.cpp | 4 ++++ src/dpp/socketengines/kqueue.cpp | 4 ++++ src/sockettest/socket.cpp | 2 +- 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index c8871c508a..4ab8c3566b 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -25,6 +25,8 @@ #include #include +namespace dpp { + enum socket_event_flags : uint8_t { WANT_READ = 1, WANT_WRITE = 2, @@ -63,3 +65,5 @@ struct socket_engine_base { /* This is implemented by whatever derived form socket_engine takes */ std::unique_ptr create_socket_engine(); + +}; diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index f48611deb3..e0fb7610c2 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -20,9 +20,12 @@ ************************************************************************************/ #include +#include #include #include +namespace dpp { + bool socket_engine_base::register_socket(dpp::socket fd, const socket_events &e) { if (fd > INVALID_SOCKET && fds.find(fd) == fds.end()) { fds.emplace(fd, std::make_unique(e)); @@ -64,3 +67,5 @@ socket_engine_base::socket_engine_base() { } #endif } + +}; diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 10e8ff8aec..b1f9b42ecc 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -27,6 +27,8 @@ #include #include +namespace dpp { + struct socket_engine_epoll : public socket_engine_base { int epoll_handle{INVALID_SOCKET}; @@ -163,3 +165,5 @@ struct socket_engine_epoll : public socket_engine_base { std::unique_ptr create_socket_engine() { return std::make_unique(); } + +}; diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index fbbcd68ec1..6416d73de2 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -34,6 +34,7 @@ #define CAST_TYPE void* #endif +namespace dpp { struct socket_engine_kqueue : public socket_engine_base { @@ -166,3 +167,6 @@ struct socket_engine_kqueue : public socket_engine_base { std::unique_ptr create_socket_engine() { return std::make_unique(); } + +}; + diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp index 2554f54424..fc1693b9af 100644 --- a/src/sockettest/socket.cpp +++ b/src/sockettest/socket.cpp @@ -26,7 +26,7 @@ #include int main() { - auto se = create_socket_engine(); + auto se = dpp::create_socket_engine(); do { se->process_events(); std::cout << "Loop...\n"; From 7571319eaa0bcbf0f99109ad43bc70ab0d272006 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 4 Apr 2024 18:05:36 +0000 Subject: [PATCH 012/112] missing name --- src/dpp/socketengines/poll.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 8233632f7e..05825528dd 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -42,6 +42,8 @@ #endif #include +namespace dpp { + struct socket_engine_poll : public socket_engine_base { /* We store the pollfds as a vector. This means that insertion, deletion and updating @@ -160,3 +162,6 @@ struct socket_engine_poll : public socket_engine_base { std::unique_ptr create_socket_engine() { return std::make_unique(); } + +}; + From b63492fe02308be23bb42e136c059a056533d61c Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 24 Sep 2024 09:46:38 +0000 Subject: [PATCH 013/112] feat: thread pool object --- include/dpp/dpp.h | 1 + include/dpp/thread_pool.h | 50 ++++++++++++++++++++++++++ src/dpp/thread_pool.cpp | 74 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 include/dpp/thread_pool.h create mode 100644 src/dpp/thread_pool.cpp diff --git a/include/dpp/dpp.h b/include/dpp/dpp.h index 88a5f41370..bbafecf9dd 100644 --- a/include/dpp/dpp.h +++ b/include/dpp/dpp.h @@ -76,3 +76,4 @@ #include #include #include +#include diff --git a/include/dpp/thread_pool.h b/include/dpp/thread_pool.h new file mode 100644 index 0000000000..65c7b9fc71 --- /dev/null +++ b/include/dpp/thread_pool.h @@ -0,0 +1,50 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * 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 + +/** + * A task within a thread pool. A simple lambda that accepts no parameters and returns void. + */ +using thread_pool_task = std::function; + +/** + * @brief A thread pool contains 1 or more worker threads which accept thread_pool_task lambadas + * into a queue, which is processed in-order by whichever thread is free. + */ +struct thread_pool { + std::vector threads; + std::queue tasks; + std::mutex queue_mutex; + std::condition_variable cv; + bool stop{false}; + + explicit thread_pool(size_t num_threads = std::thread::hardware_concurrency()); + ~thread_pool(); + void enqueue(thread_pool_task task); +}; diff --git a/src/dpp/thread_pool.cpp b/src/dpp/thread_pool.cpp new file mode 100644 index 0000000000..b05f3a1df3 --- /dev/null +++ b/src/dpp/thread_pool.cpp @@ -0,0 +1,74 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * 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. + * + ************************************************************************************/ + +#include +#include +#include + +thread_pool::thread_pool(size_t num_threads) { + for (size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([this, i]() { + dpp::utility::set_thread_name("pool/exec/" + std::to_string(i)); + while (true) { + thread_pool_task task; + { + std::unique_lock lock(queue_mutex); + + cv.wait(lock, [this] { + return !tasks.empty() || stop; + }); + + if (stop && tasks.empty()) { + return; + } + + task = std::move(tasks.front()); + tasks.pop(); + } + + task(); + } + }); + } +} + +thread_pool::~thread_pool() +{ + { + std::unique_lock lock(queue_mutex); + stop = true; + } + + cv.notify_all(); + for (auto& thread : threads) { + thread.join(); + } +} + +void thread_pool::enqueue(thread_pool_task task) +{ + { + std::unique_lock lock(queue_mutex); + tasks.emplace(std::move(task)); + } + cv.notify_one(); +} From 7ba4f78a55a0c7706d73c91230a4a93034b6cec1 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 24 Sep 2024 11:09:47 +0000 Subject: [PATCH 014/112] fix: forgot to change this include when moving source over --- src/dpp/thread_pool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dpp/thread_pool.cpp b/src/dpp/thread_pool.cpp index b05f3a1df3..95469a784c 100644 --- a/src/dpp/thread_pool.cpp +++ b/src/dpp/thread_pool.cpp @@ -21,7 +21,7 @@ ************************************************************************************/ #include -#include +#include #include thread_pool::thread_pool(size_t num_threads) { From 22bcddab9900622afe3137a89a72f2c173c23d3a Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 11 Nov 2024 13:00:18 +0000 Subject: [PATCH 015/112] feat: socket engine thread pool --- include/dpp/socketengine.h | 29 ++++++---- src/dpp/socketengine.cpp | 42 ++++++++------- src/dpp/socketengines/epoll.cpp | 92 ++++++++++++++++++++------------ src/dpp/socketengines/kqueue.cpp | 20 +++---- src/dpp/socketengines/poll.cpp | 12 ++--- src/sockettest/socket.cpp | 42 ++++++++++++++- 6 files changed, 158 insertions(+), 79 deletions(-) diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index 4ab8c3566b..96c5c4216e 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include namespace dpp { @@ -33,37 +35,46 @@ enum socket_event_flags : uint8_t { WANT_ERROR = 4, }; -using socket_read_event = auto (*)(dpp::socket fd, const struct socket_events&) -> void; -using socket_write_event = auto (*)(dpp::socket fd, const struct socket_events&) -> void; -using socket_error_event = auto (*)(dpp::socket fd, const struct socket_events&, int error_code) -> void; +using socket_read_event = std::function; +using socket_write_event = std::function; +using socket_error_event = std::function; struct socket_events { + dpp::socket fd{INVALID_SOCKET}; uint8_t flags{0}; socket_read_event on_read{}; socket_write_event on_write{}; socket_error_event on_error{}; + 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) { } + }; using socket_container = std::unordered_map>; struct socket_engine_base { socket_container fds; + std::unique_ptr pool; socket_engine_base(); - socket_engine_base(const socket_engine_base&) = default; - socket_engine_base(socket_engine_base&&) = default; - socket_engine_base& operator=(const socket_engine_base&) = default; - socket_engine_base& operator=(socket_engine_base&&) = default; + socket_engine_base(const socket_engine_base&) = delete; + socket_engine_base(socket_engine_base&&) = delete; + socket_engine_base& operator=(const socket_engine_base&) = delete; + socket_engine_base& operator=(socket_engine_base&&) = delete; virtual ~socket_engine_base() = default; virtual void process_events() = 0; - virtual bool register_socket(dpp::socket fd, const socket_events& e); - virtual bool update_socket(dpp::socket fd, const socket_events& e); + virtual bool register_socket(const socket_events& e); + virtual bool update_socket(const socket_events& e); virtual bool remove_socket(dpp::socket fd); }; /* This is implemented by whatever derived form socket_engine takes */ std::unique_ptr create_socket_engine(); +#ifndef _WIN32 + void set_signal_handler(int signal); +#endif + }; diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index e0fb7610c2..ed632f3522 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -23,20 +23,21 @@ #include #include #include +#include namespace dpp { -bool socket_engine_base::register_socket(dpp::socket fd, const socket_events &e) { - if (fd > INVALID_SOCKET && fds.find(fd) == fds.end()) { - fds.emplace(fd, std::make_unique(e)); +bool socket_engine_base::register_socket(const socket_events &e) { + if (e.fd > INVALID_SOCKET && fds.find(e.fd) == fds.end()) { + fds.emplace(e.fd, std::make_unique(e)); return true; } return false; } -bool socket_engine_base::update_socket(dpp::socket fd, const socket_events &e) { - if (fd > INVALID_SOCKET && fds.find(fd) != fds.end()) { - auto iter = fds.find(fd); +bool socket_engine_base::update_socket(const socket_events &e) { + if (e.fd > INVALID_SOCKET && fds.find(e.fd) != fds.end()) { + auto iter = fds.find(e.fd); *(iter->second) = e; return true; } @@ -53,19 +54,20 @@ bool socket_engine_base::remove_socket(dpp::socket fd) { } socket_engine_base::socket_engine_base() { - #ifndef WIN32 - signal(SIGALRM, SIG_IGN); - signal(SIGHUP, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - signal(SIGCHLD, SIG_IGN); - signal(SIGXFSZ, SIG_IGN); - #else - // Set up winsock. - WSADATA wsadata; - if (WSAStartup(MAKEWORD(2, 2), &wsadata)) { - throw dpp::connection_exception(err_connect_failure, "WSAStartup failure"); - } - #endif +#ifndef WIN32 + set_signal_handler(SIGALRM); + set_signal_handler(SIGHUP); + set_signal_handler(SIGPIPE); + set_signal_handler(SIGCHLD); + set_signal_handler(SIGXFSZ); +#else + // Set up winsock. + WSADATA wsadata; + if (WSAStartup(MAKEWORD(2, 2), &wsadata)) { + throw dpp::connection_exception(err_connect_failure, "WSAStartup failure"); + } +#endif + pool = std::make_unique(); } -}; +} diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index b1f9b42ecc..e98cd405ea 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -26,19 +26,39 @@ #include #include #include +#include namespace dpp { +int modify_event(int epoll_handle, socket_events* eh, int new_events) { + if (new_events != eh->flags) { + struct epoll_event new_ev{}; + new_ev.events = EPOLLET; + if ((new_events & WANT_READ) != 0) { + new_ev.events |= EPOLLIN; + } + if ((new_events & WANT_WRITE) != 0) { + new_ev.events |= EPOLLOUT; + } + if ((new_events & WANT_ERROR) != 0) { + new_ev.events |= EPOLLERR; + } + new_ev.data.ptr = static_cast(eh); + epoll_ctl(epoll_handle, EPOLL_CTL_MOD, eh->fd, &new_ev); + } + return new_events; +} + struct socket_engine_epoll : public socket_engine_base { int epoll_handle{INVALID_SOCKET}; static const int epoll_hint = 128; std::vector events; - socket_engine_epoll(const socket_engine_epoll&) = default; - socket_engine_epoll(socket_engine_epoll&&) = default; - socket_engine_epoll& operator=(const socket_engine_epoll&) = default; - socket_engine_epoll& operator=(socket_engine_epoll&&) = default; + socket_engine_epoll(const socket_engine_epoll&) = delete; + socket_engine_epoll(socket_engine_epoll&&) = delete; + socket_engine_epoll& operator=(const socket_engine_epoll&) = delete; + socket_engine_epoll& operator=(socket_engine_epoll&&) = delete; socket_engine_epoll() : epoll_handle(epoll_create(socket_engine_epoll::epoll_hint)) { events.resize(socket_engine_epoll::epoll_hint); @@ -61,50 +81,55 @@ struct socket_engine_epoll : public socket_engine_base { epoll_event ev = events[j]; auto* const eh = static_cast(ev.data.ptr); - const int fd = ev.data.fd; + const int fd = eh->fd; if (fd == INVALID_SOCKET) { continue; } if ((ev.events & EPOLLHUP) != 0U) { - eh->on_error(fd, *eh, 0); + eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_ERROR); + pool->enqueue([this, eh, fd]() { + eh->on_error(fd, *eh, 0); + eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_ERROR); + }); continue; } if ((ev.events & EPOLLERR) != 0U) { /* Get error number */ - socklen_t codesize = sizeof(int); - int errcode{}; - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { - errcode = errno; - } - eh->on_error(fd, *eh, errcode); + eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_ERROR); + pool->enqueue([this, eh, fd]() { + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { + errcode = errno; + } + eh->on_error(fd, *eh, errcode); + eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_ERROR); + }); continue; } if ((ev.events & EPOLLOUT) != 0U) { - int new_events = eh->flags & ~WANT_WRITE; - if (new_events != eh->flags) { - ev.events = new_events; - ev.data.ptr = static_cast(eh); - epoll_ctl(epoll_handle, EPOLL_CTL_MOD, fd, &ev); - } - eh->flags = new_events; - } - if (ev.events & EPOLLIN) { - eh->on_read(fd, *eh); + eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_WRITE); + pool->enqueue([eh, fd]() {eh->on_write(fd, *eh); }); } - if (ev.events & EPOLLOUT) { - eh->on_write(fd, *eh); + + if ((ev.events & EPOLLIN) != 0U) { + eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_READ); + pool->enqueue([this, eh, fd]() { + eh->on_read(fd, *eh); + eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_READ); + }); } } - } - bool register_socket(dpp::socket fd, const socket_events& e) final { - bool r = socket_engine_base::register_socket(fd, e); + bool register_socket(const socket_events& e) final { + bool r = socket_engine_base::register_socket(e); if (r) { struct epoll_event ev{}; + ev.events = EPOLLET; if ((e.flags & WANT_READ) != 0) { ev.events |= EPOLLIN; } @@ -114,8 +139,8 @@ struct socket_engine_epoll : public socket_engine_base { if ((e.flags & WANT_ERROR) != 0) { ev.events |= EPOLLERR; } - ev.data.ptr = fds.find(fd)->second.get(); - int i = epoll_ctl(epoll_handle, EPOLL_CTL_ADD, fd, &ev); + ev.data.ptr = fds.find(e.fd)->second.get(); + int i = epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev); if (i < 0) { throw dpp::connection_exception("Failed to register socket to epoll_ctl()"); } @@ -126,10 +151,11 @@ struct socket_engine_epoll : public socket_engine_base { return r; } - bool update_socket(dpp::socket fd, const socket_events& e) final { - bool r = socket_engine_base::update_socket(fd, e); + bool update_socket(const socket_events& e) final { + bool r = socket_engine_base::update_socket(e); if (r) { struct epoll_event ev{}; + ev.events = EPOLLET; if ((e.flags & WANT_READ) != 0) { ev.events |= EPOLLIN; } @@ -139,8 +165,8 @@ struct socket_engine_epoll : public socket_engine_base { if ((e.flags & WANT_ERROR) != 0) { ev.events |= EPOLLERR; } - ev.data.ptr = fds.find(fd)->second.get(); - int i = epoll_ctl(epoll_handle, EPOLL_CTL_MOD, fd, &ev); + ev.data.ptr = fds.find(e.fd)->second.get(); + int i = epoll_ctl(epoll_handle, EPOLL_CTL_MOD, e.fd, &ev); if (i < 0) { throw dpp::connection_exception("Failed to modify socket with epoll_ctl()"); } diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 6416d73de2..fe07206df4 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -118,15 +118,15 @@ struct socket_engine_kqueue : public socket_engine_base { } } - bool register_socket(dpp::socket fd, const socket_events& e) final { - bool r = socket_engine_base::register_socket(fd, e); + bool register_socket(const socket_events& e) final { + bool r = socket_engine_base::register_socket(e); if (r) { struct kevent* ke = get_change_kevent(); - socket_events* se = fds.find(fd)->second.get(); + socket_events* se = fds.find(e.fd)->second.get(); if ((se->flags & WANT_READ) != 0) { - EV_SET(ke, fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); + EV_SET(ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); } - set_event_write_flags(fd, se, 0, e.flags); + set_event_write_flags(e.fd, se, 0, e.flags); if (fds.size() * 2 > ke_list.size()) { ke_list.resize(fds.size() * 2); } @@ -134,15 +134,15 @@ struct socket_engine_kqueue : public socket_engine_base { return r; } - bool update_socket(dpp::socket fd, const socket_events& e) final { - bool r = socket_engine_base::update_socket(fd, e); + bool update_socket(const socket_events& e) final { + bool r = socket_engine_base::update_socket(e); if (r) { struct kevent* ke = get_change_kevent(); - socket_events* se = fds.find(fd)->second.get(); + socket_events* se = fds.find(e.fd)->second.get(); if ((se->flags & WANT_READ) != 0) { - EV_SET(ke, fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); + EV_SET(ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); } - set_event_write_flags(fd, se, 0, e.flags); + set_event_write_flags(e.fd, se, 0, e.flags); if (fds.size() * 2 > ke_list.size()) { ke_list.resize(fds.size() * 2); } diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 05825528dd..11734eab15 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -101,11 +101,11 @@ struct socket_engine_poll : public socket_engine_base { } } - bool register_socket(dpp::socket fd, const socket_events& e) final { - bool r = socket_engine_base::register_socket(fd, e); + bool register_socket(const socket_events& e) final { + bool r = socket_engine_base::register_socket(e); if (r) { pollfd fd_info{}; - fd_info.fd = fd; + fd_info.fd = e.fd; fd_info.events = 0; if ((e.flags & WANT_READ) != 0) { fd_info.events |= POLLIN; @@ -121,12 +121,12 @@ struct socket_engine_poll : public socket_engine_base { return r; } - bool update_socket(dpp::socket fd, const socket_events& e) final { - bool r = socket_engine_base::update_socket(fd, e); + bool update_socket(const socket_events& e) final { + bool r = socket_engine_base::update_socket(e); if (r) { /* We know this will succeed */ for (pollfd& fd_info : poll_set) { - if (fd_info.fd != fd) { + if (fd_info.fd != e.fd) { continue; } fd_info.events = 0; diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp index fc1693b9af..6177f389b9 100644 --- a/src/sockettest/socket.cpp +++ b/src/sockettest/socket.cpp @@ -24,11 +24,51 @@ #include #include #include +#include +#include int main() { auto se = dpp::create_socket_engine(); + + const dpp::dns_cache_entry* addr = dpp::resolve_hostname("neuron.brainbox.cc", "80"); + std::cout << "Connect to IP: " << addr->resolved_addr << "\n"; + dpp::socket sfd = addr->make_connecting_socket(); + dpp::address_t destination = addr->get_connecting_address(80); + if (sfd == INVALID_SOCKET) { + std::cerr << "Couldn't create outbound socket on port 80\n"; + exit(1); + } else if (::connect(sfd, destination.get_socket_address(), destination.size()) != 0) { + dpp::close_socket(sfd); + std::cerr << "Couldn't connect outbound socket on port 80\n"; + exit(1); + } + + dpp::socket_events events( + sfd, + dpp::WANT_READ | dpp::WANT_WRITE | dpp::WANT_ERROR, + [](dpp::socket fd, const struct dpp::socket_events& e) { + int r = 0; + do { + char buf[128]{0}; + r = ::read(e.fd, buf, 127); + if (r > 0) { + buf[127] = 0; + std::cout << buf; + } + } while (r > 0); + }, + [](dpp::socket fd, const struct dpp::socket_events& e) { + std::cout << "WANT_WRITE event on socket " << fd << "\n"; + ::write(e.fd, "GET / HTTP/1.0\r\n\r\n", strlen("GET / HTTP/1.0\r\n\r\n")); + }, + [](dpp::socket fd, const struct dpp::socket_events&, int error_code) { + std::cout << "WANT_ERROR event on socket " << fd << " with code " << error_code << "\n"; + } + ); + + se->register_socket(events); + do { se->process_events(); - std::cout << "Loop...\n"; } while (true); } From 8ce80b2787e3bc99db987661b29792927fe010f9 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 11 Nov 2024 15:25:55 +0000 Subject: [PATCH 016/112] document socket_engine_base --- include/dpp/socketengine.h | 166 ++++++++++++++++++++++++++++++- src/dpp/socketengine.cpp | 38 +++++-- src/dpp/socketengines/epoll.cpp | 3 + src/dpp/socketengines/kqueue.cpp | 2 + src/dpp/socketengines/poll.cpp | 2 + src/sockettest/socket.cpp | 9 +- 6 files changed, 208 insertions(+), 12 deletions(-) diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index 96c5c4216e..9c3be2ac47 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -29,48 +29,212 @@ 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 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 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 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 socket_engine_base { + /** + * @brief File descriptors, and their states + */ socket_container fds; + + /** + * @brief Thread pool. + * Event calls go into the thread pool and are called as + * and when threads in the pool are available. + */ std::unique_ptr pool; + /** + * @brief Number of file descriptors we are waiting to delete + */ + size_t to_delete_count{0}; + + /** + * @brief Default constructor + */ socket_engine_base(); + + /** + * @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(); +protected: + virtual bool remove_socket(dpp::socket fd); }; -/* This is implemented by whatever derived form socket_engine takes */ +/** + * @brief This is implemented by whatever derived form socket_engine takes + */ std::unique_ptr create_socket_engine(); #ifndef _WIN32 diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index ed632f3522..ef16d12e3b 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace dpp { @@ -44,15 +45,6 @@ bool socket_engine_base::update_socket(const socket_events &e) { return false; } -bool socket_engine_base::remove_socket(dpp::socket fd) { - auto iter = fds.find(fd); - if (iter != fds.end()) { - fds.erase(iter); - return true; - } - return false; -} - socket_engine_base::socket_engine_base() { #ifndef WIN32 set_signal_handler(SIGALRM); @@ -70,4 +62,32 @@ socket_engine_base::socket_engine_base() { pool = std::make_unique(); } +void socket_engine_base::prune() { + if (to_delete_count > 0) { + for (auto it = fds.cbegin(); it != fds.cend();) { + if ((it->second->flags & WANT_DELETION) != 0L) { + remove_socket(it->second->fd); + it = fds.erase(it); + } else { + ++it; + } + } + to_delete_count = 0; + } +} + +bool socket_engine_base::delete_socket(dpp::socket fd) { + auto iter = fds.find(fd); + if (iter == fds.end() || ((iter->second->flags & WANT_DELETION) != 0L)) { + return false; + } + iter->second->flags |= WANT_DELETION; + to_delete_count++; + return true; +} + +bool socket_engine_base::remove_socket(dpp::socket fd) { + return false; +} + } diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index e98cd405ea..06ec5c4224 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -123,6 +123,7 @@ struct socket_engine_epoll : public socket_engine_base { }); } } + prune(); } bool register_socket(const socket_events& e) final { @@ -174,6 +175,8 @@ struct socket_engine_epoll : public socket_engine_base { return r; } +protected: + bool remove_socket(dpp::socket fd) final { bool r = socket_engine_base::remove_socket(fd); if (r) { diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index fe07206df4..13b841f2bd 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -150,6 +150,8 @@ struct socket_engine_kqueue : public socket_engine_base { return r; } +protected: + bool remove_socket(dpp::socket fd) final { bool r = socket_engine_base::remove_socket(fd); if (r) { diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 11734eab15..82a02cf20a 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -145,6 +145,8 @@ struct socket_engine_poll : public socket_engine_base { return r; } +protected: + bool remove_socket(dpp::socket fd) final { bool r = socket_engine_base::remove_socket(fd); if (r) { diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp index 6177f389b9..8087f55b79 100644 --- a/src/sockettest/socket.cpp +++ b/src/sockettest/socket.cpp @@ -46,7 +46,7 @@ int main() { dpp::socket_events events( sfd, dpp::WANT_READ | dpp::WANT_WRITE | dpp::WANT_ERROR, - [](dpp::socket fd, const struct dpp::socket_events& e) { + [&se](dpp::socket fd, const struct dpp::socket_events& e) { int r = 0; do { char buf[128]{0}; @@ -54,12 +54,17 @@ int main() { if (r > 0) { buf[127] = 0; std::cout << buf; + std::cout.flush(); } } while (r > 0); + if (r == 0 || (errno && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { + dpp::close_socket(fd); + se->delete_socket(fd); + } }, [](dpp::socket fd, const struct dpp::socket_events& e) { std::cout << "WANT_WRITE event on socket " << fd << "\n"; - ::write(e.fd, "GET / HTTP/1.0\r\n\r\n", strlen("GET / HTTP/1.0\r\n\r\n")); + ::write(e.fd, "GET / HTTP/1.0\r\nConnection: close\r\n\r\n", strlen("GET / HTTP/1.0\r\nConnection: close\r\n\r\n")); }, [](dpp::socket fd, const struct dpp::socket_events&, int error_code) { std::cout << "WANT_ERROR event on socket " << fd << " with code " << error_code << "\n"; From 1c434a860a1c9c493ec90c4dde2958a7458b4727 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 11 Nov 2024 15:35:07 +0000 Subject: [PATCH 017/112] unistd.h is required --- src/sockettest/socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp index 8087f55b79..2a9ddec7dd 100644 --- a/src/sockettest/socket.cpp +++ b/src/sockettest/socket.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include From 910ded26eb8e5be782ce022f1be347e652e44d1d Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Nov 2024 18:02:18 +0000 Subject: [PATCH 018/112] [skip ci] working sslclient - full of debug, needs properly integrating. next step integrate https request queues --- include/dpp/cluster.h | 16 +- include/dpp/httpsclient.h | 2 +- include/dpp/socketengine.h | 11 +- include/dpp/sslclient.h | 30 +- include/dpp/wsclient.h | 2 +- src/dpp/cluster.cpp | 22 +- src/dpp/discordclient.cpp | 27 +- src/dpp/httpsclient.cpp | 4 +- src/dpp/queues.cpp | 6 +- src/dpp/socketengine.cpp | 25 +- src/dpp/socketengines/epoll.cpp | 36 +- src/dpp/socketengines/kqueue.cpp | 7 +- src/dpp/socketengines/poll.cpp | 6 +- src/dpp/sslclient.cpp | 628 +++++++++++--------------- src/dpp/voice/enabled/constructor.cpp | 2 +- src/dpp/voice/stub/stubs.cpp | 2 +- src/dpp/wsclient.cpp | 5 +- src/soaktest/soak.cpp | 8 +- src/unittest/test.cpp | 12 +- 19 files changed, 401 insertions(+), 450 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 9c283e4a72..a1dad6fe38 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -48,6 +48,7 @@ #include #include #include +#include namespace dpp { @@ -165,11 +166,6 @@ class DPP_EXPORT cluster { std::map named_commands; #endif - /** - * @brief Tick active timers - */ - void tick_timers(); - /** * @brief Reschedule a timer for its next tick * @@ -239,6 +235,11 @@ class DPP_EXPORT cluster { */ uint16_t request_timeout = 20; + /** + * @brief Socket engine instance + */ + std::unique_ptr socketengine; + /** * @brief Constructor for creating a cluster. All but the token are optional. * @param token The bot token to use for all HTTP commands and websocket connections @@ -310,6 +311,11 @@ class DPP_EXPORT cluster { */ cluster& set_websocket_protocol(websocket_protocol_t mode); + /** + * @brief Tick active timers + */ + void tick_timers(); + /** * @brief Set the audit log reason for the next REST call to be made. * This is set per-thread, so you must ensure that if you call this method, your request that diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h index 5a0ff481cf..69cd8f55cf 100644 --- a/include/dpp/httpsclient.h +++ b/include/dpp/httpsclient.h @@ -256,7 +256,7 @@ class DPP_EXPORT https_client : public ssl_client { * @param request_timeout How many seconds before the connection is considered failed if not finished * @param protocol Request HTTP protocol (default: 1.1) */ - https_client(const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5, const std::string &protocol = "1.1"); + https_client(cluster* creator, const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5, const std::string &protocol = "1.1"); /** * @brief Destroy the https client object diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index 9c3be2ac47..b793cc098e 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -161,10 +161,16 @@ struct socket_engine_base { */ size_t to_delete_count{0}; + /** + * @brief Owning cluster + */ + class cluster* owner{nullptr}; + /** * @brief Default constructor + * @param creator Owning cluster */ - socket_engine_base(); + socket_engine_base(class cluster* creator); /** * @brief Non-copyable @@ -234,8 +240,9 @@ struct socket_engine_base { /** * @brief This is implemented by whatever derived form socket_engine takes + * @param creator Creating cluster */ -std::unique_ptr create_socket_engine(); +std::unique_ptr create_socket_engine(class cluster* creator); #ifndef _WIN32 void set_signal_handler(int signal); diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index cd1828fcb1..75fcd10138 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -64,6 +64,16 @@ bool close_socket(dpp::socket sfd); */ bool set_nonblocking(dpp::socket sockfd, bool non_blocking); +/* You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each. + * SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as + * it'd go unused. + */ +constexpr uint16_t DPP_BUFSIZE{16 * 1024}; + +/* Represents a failed socket system call, e.g. connect() failure */ +constexpr int ERROR_STATUS{-1}; + + /** * @brief Implements a simple non-blocking SSL stream client. * @@ -141,9 +151,9 @@ class DPP_EXPORT ssl_client bool plaintext; /** - * @brief True if we are establishing a new connection, false if otherwise. + * @brief True if connection is completed */ - bool make_new; + bool connected{false}; /** @@ -207,6 +217,12 @@ class DPP_EXPORT ssl_client */ bool keepalive; + class cluster* owner; + + size_t client_to_server_length = 0, client_to_server_offset = 0; + char client_to_server_buffer[DPP_BUFSIZE], server_to_client_buffer[DPP_BUFSIZE]; + + /** * @brief Connect to a specified host and port. Throws std::runtime_error on fatal error. * @param _hostname The hostname to connect to @@ -217,7 +233,7 @@ class DPP_EXPORT ssl_client * connection to non-Discord addresses such as within dpp::cluster::request(). * @throw dpp::exception Failed to initialise connection */ - ssl_client(const std::string &_hostname, const std::string &_port = "443", bool plaintext_downgrade = false, bool reuse = false); + ssl_client(cluster* creator, const std::string &_hostname, const std::string &_port = "443", bool plaintext_downgrade = false, bool reuse = false); /** * @brief Nonblocking I/O loop @@ -256,6 +272,14 @@ class DPP_EXPORT ssl_client * @param msg Log message to send */ virtual void log(dpp::loglevel severity, const std::string &msg) const; + + void complete_handshake(const struct socket_events* ev); + + void on_read(dpp::socket fd, const struct dpp::socket_events& ev); + + void on_write(dpp::socket fd, const struct dpp::socket_events& e); + + void on_error(dpp::socket fd, const struct dpp::socket_events&, int error_code); }; } diff --git a/include/dpp/wsclient.h b/include/dpp/wsclient.h index fbbf72dee7..73f8eb9321 100644 --- a/include/dpp/wsclient.h +++ b/include/dpp/wsclient.h @@ -183,7 +183,7 @@ class DPP_EXPORT websocket_client : public ssl_client { * @note Voice websockets only support OP_TEXT, and other websockets must be * OP_BINARY if you are going to send ETF. */ - websocket_client(const std::string& hostname, const std::string& port = "443", const std::string& urlpath = "", ws_opcode opcode = OP_BINARY); + websocket_client(cluster* creator, const std::string& hostname, const std::string& port = "443", const std::string& urlpath = "", ws_opcode opcode = OP_BINARY); /** * @brief Destroy the websocket client object diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index e39bc75b1e..d9ea131b41 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -88,6 +88,7 @@ cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, : default_gateway("gateway.discord.gg"), rest(nullptr), raw_rest(nullptr), compressed(comp), start_time(0), token(_token), last_identify(time(nullptr) - 5), intents(_intents), numshards(_shards), cluster_id(_cluster_id), maxclusters(_maxclusters), rest_ping(0.0), cache_policy(policy), ws_mode(ws_json) { + socketengine = create_socket_engine(this); /* Instantiate REST request queues */ try { rest = new request_queue(this, request_threads); @@ -222,10 +223,10 @@ void cluster::start(bool return_after) { gateway g; try { #ifdef DPP_CORO - confirmation_callback_t cc = co_get_gateway_bot().sync_wait(); - g = std::get(cc.value); + //confirmation_callback_t cc = co_get_gateway_bot().sync_wait(); + //g = std::get(cc.value); #else - g = dpp::sync(this, &cluster::get_gateway_bot); + //g = dpp::sync(this, &cluster::get_gateway_bot); #endif log(ll_debug, "Cluster: " + std::to_string(g.session_start_remaining) + " of " + std::to_string(g.session_start_total) + " session starts remaining"); if (g.session_start_remaining < g.shards) { @@ -273,10 +274,10 @@ void cluster::start(bool return_after) { * so it will pause after every shard. For any with non-zero concurrency it'll pause 5 seconds * after every batch. */ - if (((s + 1) % g.session_start_max_concurrency) == 0) { + /*if (((s + 1) % g.session_start_max_concurrency) == 0) { size_t wait_time = 5; if (g.session_start_max_concurrency > 1) { - /* If large bot sharding, be sure to give the batch of shards time to settle */ + // If large bot sharding, be sure to give the batch of shards time to settle bool all_connected = true; do { all_connected = true; @@ -290,21 +291,26 @@ void cluster::start(bool return_after) { } while (all_connected); } std::this_thread::sleep_for(std::chrono::seconds(wait_time)); - } + }*/ } } /* Get all active DM channels and map them to user id -> dm id */ - this->current_user_get_dms([this](const dpp::confirmation_callback_t& completion) { + /*this->current_user_get_dms([this](const dpp::confirmation_callback_t& completion) { dpp::channel_map dmchannels = std::get(completion.value); for (auto & c : dmchannels) { for (auto & u : c.second.recipients) { this->set_dm_channel(u, c.second.id); } } - }); + });*/ log(ll_debug, "Shards started."); + + // TODO: Temporary for testing + do { + socketengine->process_events(); + } while (true); if (!return_after) { block_calling_thread(); diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index cf43b76fca..3d200cc53a 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -64,7 +64,7 @@ class zlibcontext { thread_local static std::string last_ping_message; discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) - : websocket_client(_cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), + : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), terminating(false), runner(nullptr), compressed(comp), @@ -167,7 +167,15 @@ void discord_client::thread_run() bool error = false; ready = false; message_queue.clear(); + + std::cout << "before read_loop\n"; ssl_client::read_loop(); + std::cout << "after read_loop\n"; + while (!terminating) { + sleep(1); + } + std::cout << "after read_loop while\n"; + if (!terminating) { ssl_client::close(); end_zlib(); @@ -486,23 +494,6 @@ void discord_client::one_second_timer() websocket_client::one_second_timer(); - /* Every minute, rehash all containers from first shard. - * We can't just get shard with the id 0 because this won't - * work on a clustered environment - */ - auto shards = creator->get_shards(); - auto first_iter = shards.begin(); - if (first_iter != shards.end()) { - dpp::discord_client* first_shard = first_iter->second; - if (first_shard == this) { - creator->tick_timers(); - - if ((time(nullptr) % 60) == 0) { - dpp::garbage_collection(); - } - } - } - /* This all only triggers if we are connected (have completed websocket, and received READY or RESUMED) */ if (this->is_connected()) { diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 8c7cff3258..812b142e41 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -30,8 +30,8 @@ namespace dpp { -https_client::https_client(const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout, const std::string &protocol) - : ssl_client(hostname, std::to_string(port), plaintext_connection, false), +https_client::https_client(cluster* creator, const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout, const std::string &protocol) + : ssl_client(creator, hostname, std::to_string(port), plaintext_connection, false), state(HTTPS_HEADERS), request_type(verb), path(urlpath), diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 96aa1d5288..814611d504 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -175,14 +175,14 @@ http_request_completion_t http_request::run(cluster* owner) { } http_connect_info hci = https_client::get_host_info(_host); try { - https_client cli(hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl, owner->request_timeout, protocol); + https_client cli(owner, hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl, owner->request_timeout, protocol); rv.latency = dpp::utility::time_f() - start; if (cli.timed_out) { rv.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": Timed out while waiting for the response"); + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Timed out while waiting for the response"); } else if (cli.get_status() < 100) { rv.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": Malformed HTTP response"); + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Malformed HTTP response"); } else { populate_result(_url, owner, rv, cli); } diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index ef16d12e3b..f79d4779f3 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include namespace dpp { @@ -45,13 +47,13 @@ bool socket_engine_base::update_socket(const socket_events &e) { return false; } -socket_engine_base::socket_engine_base() { +socket_engine_base::socket_engine_base(cluster* creator) : owner(creator) { #ifndef WIN32 set_signal_handler(SIGALRM); - set_signal_handler(SIGHUP); - set_signal_handler(SIGPIPE); - set_signal_handler(SIGCHLD); set_signal_handler(SIGXFSZ); + set_signal_handler(SIGCHLD); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); #else // Set up winsock. WSADATA wsadata; @@ -59,9 +61,11 @@ socket_engine_base::socket_engine_base() { throw dpp::connection_exception(err_connect_failure, "WSAStartup failure"); } #endif - pool = std::make_unique(); + //pool = std::make_unique(); } +time_t last_time = time(nullptr); + void socket_engine_base::prune() { if (to_delete_count > 0) { for (auto it = fds.cbegin(); it != fds.cend();) { @@ -74,6 +78,17 @@ void socket_engine_base::prune() { } to_delete_count = 0; } + if (time(nullptr) != last_time) { + /* Every minute, rehash all cache containers. + * We do this from the socket engine now, not from + * shard 0, so no need to run shards to have timers! + */ + owner->tick_timers(); + + if ((time(nullptr) % 60) == 0) { + dpp::garbage_collection(); + } + } } bool socket_engine_base::delete_socket(dpp::socket fd) { diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 06ec5c4224..b2b1edb75d 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -60,7 +60,7 @@ struct socket_engine_epoll : public socket_engine_base { socket_engine_epoll& operator=(const socket_engine_epoll&) = delete; socket_engine_epoll& operator=(socket_engine_epoll&&) = delete; - socket_engine_epoll() : epoll_handle(epoll_create(socket_engine_epoll::epoll_hint)) { + socket_engine_epoll(cluster* creator) : socket_engine_base(creator), epoll_handle(epoll_create(socket_engine_epoll::epoll_hint)) { events.resize(socket_engine_epoll::epoll_hint); if (epoll_handle == -1) { throw dpp::connection_exception("Failed to initialise epoll()"); @@ -87,40 +87,42 @@ struct socket_engine_epoll : public socket_engine_base { } if ((ev.events & EPOLLHUP) != 0U) { - eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_ERROR); - pool->enqueue([this, eh, fd]() { + //eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_ERROR); + //pool->enqueue([this, eh, fd]() { eh->on_error(fd, *eh, 0); - eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_ERROR); - }); + // eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_ERROR); + //}); continue; } if ((ev.events & EPOLLERR) != 0U) { /* Get error number */ - eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_ERROR); - pool->enqueue([this, eh, fd]() { + //eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_ERROR); + //pool->enqueue([this, eh, fd]() { socklen_t codesize = sizeof(int); int errcode{}; if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { errcode = errno; } eh->on_error(fd, *eh, errcode); - eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_ERROR); - }); + // eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_ERROR); + //}); continue; } if ((ev.events & EPOLLOUT) != 0U) { eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_WRITE); - pool->enqueue([eh, fd]() {eh->on_write(fd, *eh); }); + //pool->enqueue([eh, fd]() { + eh->on_write(fd, *eh); + //}); } if ((ev.events & EPOLLIN) != 0U) { - eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_READ); - pool->enqueue([this, eh, fd]() { + //eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_READ); + //pool->enqueue([this, eh, fd]() { eh->on_read(fd, *eh); - eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_READ); - }); + // eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_READ); + //}); } } prune(); @@ -128,6 +130,7 @@ struct socket_engine_epoll : public socket_engine_base { bool register_socket(const socket_events& e) final { bool r = socket_engine_base::register_socket(e); + std::cout << "return register: " << r << "\n"; if (r) { struct epoll_event ev{}; ev.events = EPOLLET; @@ -142,6 +145,7 @@ struct socket_engine_epoll : public socket_engine_base { } ev.data.ptr = fds.find(e.fd)->second.get(); int i = epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev); + std::cout << "epoll_ctl for fd " << e.fd << ": " << i << "\n"; if (i < 0) { throw dpp::connection_exception("Failed to register socket to epoll_ctl()"); } @@ -191,8 +195,8 @@ struct socket_engine_epoll : public socket_engine_base { } }; -std::unique_ptr create_socket_engine() { - return std::make_unique(); +std::unique_ptr create_socket_engine(cluster* creator) { + return std::make_unique(creator); } }; diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 13b841f2bd..4a6e9a8d9e 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #if defined __NetBSD__ && __NetBSD_Version__ <= 999001400 #define CAST_TYPE intptr_t @@ -48,7 +49,7 @@ struct socket_engine_kqueue : public socket_engine_base { socket_engine_kqueue& operator=(const socket_engine_kqueue&) = default; socket_engine_kqueue& operator=(socket_engine_kqueue&&) = default; - socket_engine_kqueue() : kqueue_handle(kqueue()) { + socket_engine_kqueue(cluster* creator) : socket_engine_base(creator), kqueue_handle(kqueue()) { change_list.resize(8); ke_list.resize(16); if (kqueue_handle == -1) { @@ -166,8 +167,8 @@ struct socket_engine_kqueue : public socket_engine_base { } }; -std::unique_ptr create_socket_engine() { - return std::make_unique(); +std::unique_ptr create_socket_engine(cluster* creator) { + return std::make_unique(creator); } }; diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 82a02cf20a..10c7e85df3 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -159,10 +159,12 @@ struct socket_engine_poll : public socket_engine_base { } return false; } + + socket_engine_poll(cluster* creator) : socket_engine_base(creator) { }; }; -std::unique_ptr create_socket_engine() { - return std::make_unique(); +std::unique_ptr create_socket_engine(cluster* creator) { + return std::make_unique(creator); } }; diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 7cdf92150e..fa13f31e15 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -60,11 +60,13 @@ #include #include #include +#include #include #include #include #include #include +#include /* Maximum allowed time in milliseconds for socket read/write timeouts and connect() */ constexpr uint16_t SOCKET_OP_TIMEOUT{5000}; @@ -81,7 +83,7 @@ class openssl_connection { /** * @brief OpenSSL session */ - SSL* ssl; + SSL* ssl{nullptr}; }; /** @@ -113,15 +115,6 @@ thread_local std::unique_ptr openssl_context; */ thread_local std::unordered_map keepalives; -/* You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each. - * SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as - * it'd go unused. - */ -constexpr uint16_t DPP_BUFSIZE{16 * 1024}; - -/* Represents a failed socket system call, e.g. connect() failure */ -constexpr int ERROR_STATUS{-1}; - bool close_socket(dpp::socket sfd) { /* close_socket on an error socket is a non-op */ @@ -168,11 +161,7 @@ bool set_nonblocking(dpp::socket sockfd, bool non_blocking) * @return int -1 on error, 0 on succcess just like POSIX connect() * @throw dpp::connection_exception on failure */ -int connect_with_timeout(dpp::socket sockfd, const struct sockaddr *addr, socklen_t addrlen, unsigned int timeout_ms) { -#ifdef __APPLE__ - /* Unreliable on OSX right now */ - return (::connect(sockfd, addr, addrlen)); -#else +int start_connecting(dpp::socket sockfd, const struct sockaddr *addr, socklen_t addrlen, unsigned int timeout_ms) { if (!set_nonblocking(sockfd, true)) { throw dpp::connection_exception(err_nonblocking_failure, "Can't switch socket to non-blocking mode!"); } @@ -186,36 +175,15 @@ int connect_with_timeout(dpp::socket sockfd, const struct sockaddr *addr, sockle int err = EWOULDBLOCK; #else /* Standard POSIX connection behaviour */ + std::cout << "connect\n"; int rc = (::connect(sockfd, addr, addrlen)); int err = errno; + std::cout << "rc=" << rc << " err=" << err << "\n"; #endif if (rc == -1 && err != EWOULDBLOCK && err != EINPROGRESS) { throw connection_exception(err_connect_failure, strerror(errno)); } - - /* Set a deadline timestamp 'timeout' ms from now */ - double deadline = utility::time_f() + (timeout_ms / 1000.0); - - do { - if (utility::time_f() >= deadline) { - throw connection_exception(err_connection_timed_out, "Connection timed out"); - } - pollfd pfd = {}; - pfd.fd = sockfd; - pfd.events = POLLOUT; - const int r = ::poll(&pfd, 1, 10); - if (r > 0 && pfd.revents & POLLOUT) { - rc = 0; - } else if (r != 0 || pfd.revents & POLLERR) { - throw connection_exception(err_connection_timed_out, strerror(errno)); - } - } while (rc == -1); - - if (!set_nonblocking(sockfd, false)) { - throw connection_exception(err_nonblocking_failure, "Can't switch socket to blocking mode!"); - } - return rc; -#endif + return 0; } #ifndef _WIN32 @@ -230,7 +198,7 @@ void set_signal_handler(int signal) } #endif -ssl_client::ssl_client(const std::string &_hostname, const std::string &_port, bool plaintext_downgrade, bool reuse) : +ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std::string &_port, bool plaintext_downgrade, bool reuse) : nonblocking(false), sfd(INVALID_SOCKET), ssl(nullptr), @@ -240,61 +208,18 @@ ssl_client::ssl_client(const std::string &_hostname, const std::string &_port, b bytes_out(0), bytes_in(0), plaintext(plaintext_downgrade), - make_new(true), - keepalive(reuse) + keepalive(reuse), + owner(creator) { -#ifndef WIN32 - set_signal_handler(SIGALRM); - set_signal_handler(SIGXFSZ); - set_signal_handler(SIGCHLD); - signal(SIGHUP, SIG_IGN); - signal(SIGPIPE, SIG_IGN); -#else - // Set up winsock. - WSADATA wsadata; - if (WSAStartup(MAKEWORD(2, 2), &wsadata)) { - throw dpp::connection_exception(err_connect_failure, "WSAStartup failure"); - } -#endif - if (keepalive) { - std::string identifier((!plaintext ? "ssl://" : "tcp://") + hostname + ":" + port); - auto iter = keepalives.find(identifier); - if (iter != keepalives.end()) { - /* Found a keepalive connection, check it is still connected/valid via poll() for error */ - pollfd pfd = {}; - pfd.fd = iter->second.sfd; - pfd.events = POLLOUT; - int r = ::poll(&pfd, 1, 1); - if (time(nullptr) > (iter->second.created + 60) || r < 0 || pfd.revents & POLLERR) { - make_new = true; - /* This connection is dead, free its resources and make a new one */ - if (iter->second.ssl->ssl) { - SSL_free(iter->second.ssl->ssl); - iter->second.ssl->ssl = nullptr; - } - close_socket(iter->second.sfd); - iter->second.sfd = INVALID_SOCKET; - delete iter->second.ssl; - } else { - /* Connection is good, lets use it */ - this->sfd = iter->second.sfd; - this->ssl = iter->second.ssl; - make_new = false; - } - /* We don't keep in-flight connections in the keepalives list */ - keepalives.erase(iter); - } - - } - if (make_new) { - if (plaintext) { - ssl = nullptr; - } else { - ssl = new openssl_connection(); - } + if (plaintext) { + ssl = nullptr; + } else { + ssl = new openssl_connection(); } try { + std::cout << "this->connect() call\n"; this->connect(); + std::cout << "this->connect() done\n"; } catch (std::exception&) { cleanup(); @@ -305,98 +230,32 @@ ssl_client::ssl_client(const std::string &_hostname, const std::string &_port, b /* SSL Client constructor throws std::runtime_error if it can't connect to the host */ void ssl_client::connect() { - /* Initial connection is done in blocking mode. There is a timeout on it. */ - nonblocking = false; - - if (make_new) { - /* Resolve hostname to IP */ - int err = 0; - const dns_cache_entry* addr = resolve_hostname(hostname, port); - sfd = addr->make_connecting_socket(); - address_t destination = addr->get_connecting_address(from_string(this->port, std::dec)); - if (sfd == ERROR_STATUS) { - err = errno; - } else if (connect_with_timeout(sfd, destination.get_socket_address(), destination.size(), SOCKET_OP_TIMEOUT) != 0) { - close_socket(sfd); - sfd = ERROR_STATUS; - } - - /* Check if none of the IPs yielded a valid connection */ - if (sfd == ERROR_STATUS) { - throw dpp::connection_exception(err_connect_failure, strerror(err)); - } - - if (!plaintext) { - /* Each thread needs a context, but we don't need to make a new one for each connection */ - if (!openssl_context) { - /* We're good to go - hand the fd over to openssl */ - const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */ - - /* Create SSL context */ - openssl_context.reset(SSL_CTX_new(method)); - if (!openssl_context) { - throw dpp::connection_exception(err_ssl_context, "Failed to create SSL client context!"); - } - - /* Do not allow SSL 3.0, TLS 1.0 or 1.1 - * https://www.packetlabs.net/posts/tls-1-1-no-longer-secure/ - */ - if (!SSL_CTX_set_min_proto_version(openssl_context.get(), TLS1_2_VERSION)) { - throw dpp::connection_exception(err_ssl_version, "Failed to set minimum SSL version!"); - } - } - - /* Create SSL session */ - ssl->ssl = SSL_new(openssl_context.get()); - if (ssl->ssl == nullptr) { - throw dpp::connection_exception(err_ssl_new, "SSL_new failed!"); - } - - SSL_set_fd(ssl->ssl, (int)sfd); - - /* Server name identification (SNI) */ - SSL_set_tlsext_host_name(ssl->ssl, hostname.c_str()); - -#ifndef _WIN32 - /* On Linux, we can set socket timeouts so that SSL_connect eventually gives up */ - timeval tv; - tv.tv_sec = 0; - tv.tv_usec = SOCKET_OP_TIMEOUT * 1000; - setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - setsockopt(sfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); -#endif - if (SSL_connect(ssl->ssl) != 1) { - throw dpp::connection_exception(err_ssl_connect, "SSL_connect error"); - } - - this->cipher = SSL_get_cipher(ssl->ssl); - } + /* Resolve hostname to IP */ + int err = 0; + std::cout << "before resolve\n"; + const dns_cache_entry* addr = resolve_hostname(hostname, port); + std::cout << "after resolve\n"; + sfd = addr->make_connecting_socket(); + address_t destination = addr->get_connecting_address(from_string(this->port, std::dec)); + std::cout << "got dest\n"; + if (sfd == ERROR_STATUS) { + std::cout << "dest error\n"; + err = errno; + } else { + start_connecting(sfd, destination.get_socket_address(), destination.size(), SOCKET_OP_TIMEOUT); + } + /* Check if valid connection started */ + if (sfd == ERROR_STATUS) { + std::cout << "sfd error status\n"; + throw dpp::connection_exception(err_connect_failure, strerror(err)); } + std::cout << "start_connecting done!\n"; } void ssl_client::socket_write(const std::string_view data) { - /* If we are in nonblocking mode, append to the buffer, - * otherwise just use SSL_write directly. The only time we - * use SSL_write directly is during connection before the - * ReadLoop is called, which allows for guaranteed simple - * lock-step delivery e.g. for HTTP header negotiation - */ - if (nonblocking) { - obuffer += data; - return; - } - - const int data_length = static_cast(data.length()); - if (plaintext) { - if (sfd == INVALID_SOCKET || ::send(sfd, data.data(), data_length, 0) != data_length) { - throw dpp::connection_exception(err_write, "write() failed"); - } - } else { - if (SSL_write(ssl->ssl, data.data(), data_length) != data_length) { - throw dpp::connection_exception(err_ssl_write, "SSL_write() failed"); - } - } + obuffer += data; + return; } void ssl_client::one_second_timer() @@ -411,211 +270,256 @@ void ssl_client::log(dpp::loglevel severity, const std::string &msg) const { } -void ssl_client::read_loop() +void ssl_client::complete_handshake(const socket_events* ev) { - /* The read loop is non-blocking using poll(). This method - * cannot read while it is waiting for write, or write while it is - * waiting for read. This is a limitation of the openssl libraries, - * as SSL is sent and received in low level ~16k frames which must - * be synchronised and ordered correctly. Attempting to send while - * we need another frame or receive while we are due to send a frame - * would cause the protocol to break. - */ - int r = 0, sockets = 1; - size_t client_to_server_length = 0, client_to_server_offset = 0; - bool read_blocked_on_write = false, write_blocked_on_read = false, read_blocked = false; - pollfd pfd[2] = {}; - char client_to_server_buffer[DPP_BUFSIZE], server_to_client_buffer[DPP_BUFSIZE]; - - try { - - if (sfd == INVALID_SOCKET) { - throw dpp::connection_exception(err_invalid_socket, "Invalid file descriptor in read_loop()"); - } - - /* Make the socket nonblocking */ - if (!set_nonblocking(sfd, true)) { - throw dpp::connection_exception(err_nonblocking_failure, "Can't switch socket to non-blocking mode!"); - } - nonblocking = true; - - /* Loop until there is a socket error */ - while(true) { - - if (last_tick != time(nullptr)) { - this->one_second_timer(); - last_tick = time(nullptr); + std::cout << "complete_handshake\n"; + auto status = SSL_do_handshake(ssl->ssl); + if (status != 1) { + auto code = SSL_get_error(ssl->ssl, status); + switch (code) { + case SSL_ERROR_NONE: { + std::cout << "Handshake err none\n"; + connected = true; + break; } - - sockets = 1; - pfd[0].fd = sfd; - pfd[0].events = POLLIN; - pfd[1].events = 0; - - if (custom_readable_fd && custom_readable_fd() >= 0) { - int cfd = (int)custom_readable_fd(); - pfd[1].fd = cfd; - pfd[1].events = POLLIN; - sockets = 2; + case SSL_ERROR_WANT_WRITE: { + std::cout << "Handshake want write\n"; + socket_events se{*ev}; + se.flags = dpp::WANT_READ | dpp::WANT_WRITE | dpp::WANT_ERROR; + owner->socketengine->update_socket(se); + break; } - if (custom_writeable_fd && custom_writeable_fd() >= 0) { - int cfd = (int)custom_writeable_fd(); - pfd[1].fd = cfd; - pfd[1].events |= POLLOUT; - sockets = 2; + case SSL_ERROR_WANT_READ: { + std::cout << "Handshake want read\n"; + socket_events se{*ev}; + se.flags = dpp::WANT_READ | dpp::WANT_ERROR; + owner->socketengine->update_socket(se); + break; } - - if (sfd == -1) { - throw dpp::connection_exception(err_invalid_socket, "File descriptor invalidated, connection died"); - } - - /* If we're waiting for a read on the socket don't try to write to the server */ - if (client_to_server_length || obuffer.length() || read_blocked_on_write) { - pfd[0].events |= POLLOUT; - } - - const int64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - int poll_time = 1000 - (now % 1000); - poll_time = poll_time > 400 ? 1000 : poll_time + poll_time / 3 + 1; - r = ::poll(pfd, sockets, now / 1000 == (int64_t)last_tick ? poll_time : 0); - - if (r == 0) { - continue; - } - - if (custom_writeable_fd && custom_writeable_fd() >= 0 && pfd[1].revents & POLLOUT) { - custom_writeable_ready(); - } - if (custom_readable_fd && custom_readable_fd() >= 0 && pfd[1].revents & POLLIN) { - custom_readable_ready(); - } - if ((pfd[0].revents & POLLERR) || (pfd[0].revents & POLLNVAL) || sfd == INVALID_SOCKET) { - throw dpp::connection_exception(err_socket_error, strerror(errno)); + default: { + throw dpp::connection_exception(err_ssl_connect, "SSL_do_handshake error: " + std::to_string(status) +";" + std::to_string(code)); } + } + } else { + socket_events se{*ev}; + se.flags = dpp::WANT_WRITE | dpp::WANT_READ | dpp::WANT_ERROR; + owner->socketengine->update_socket(se); + connected = true; + this->cipher = SSL_get_cipher(ssl->ssl); + std::cout << "*** Handshake completion; cipher=" + this->cipher + "\n"; + } - /* Now check if there's data to read */ - if (((pfd[0].revents & POLLIN) && !write_blocked_on_read) || (read_blocked_on_write && (pfd[0].revents & POLLOUT))) { - if (plaintext) { - read_blocked_on_write = false; - read_blocked = false; - r = (int) ::recv(sfd, server_to_client_buffer, DPP_BUFSIZE, 0); +} - if (r <= 0) { - /* error or EOF */ - return; - } +void ssl_client::on_read(socket fd, const struct socket_events& ev) { + if (plaintext && connected) { + int r = (int) ::recv(sfd, server_to_client_buffer, DPP_BUFSIZE, 0); + if (r <= 0) { + std::cout << "read default\n"; + this->close(); + return; + } + buffer.append(server_to_client_buffer, r); + if (!this->handle_buffer(buffer)) { + return; + } + bytes_in += r; + } else if (!plaintext && connected) { + int r = SSL_read(ssl->ssl,server_to_client_buffer,DPP_BUFSIZE); + int e = SSL_get_error(ssl->ssl,r); + + switch (e) { + case SSL_ERROR_NONE: + /* Data received, add it to the buffer */ + if (r > 0) { buffer.append(server_to_client_buffer, r); + if (!this->handle_buffer(buffer)) { return; + } else { + socket_events se{ev}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); } bytes_in += r; - } else { - do { - read_blocked_on_write = false; - read_blocked = false; - - r = SSL_read(ssl->ssl,server_to_client_buffer,DPP_BUFSIZE); - int e = SSL_get_error(ssl->ssl,r); - - switch (e) { - case SSL_ERROR_NONE: - /* Data received, add it to the buffer */ - if (r > 0) { - buffer.append(server_to_client_buffer, r); - if (!this->handle_buffer(buffer)) { - return; - } - bytes_in += r; - } - break; - case SSL_ERROR_ZERO_RETURN: - /* End of data */ - SSL_shutdown(ssl->ssl); - return; - break; - case SSL_ERROR_WANT_READ: - read_blocked = true; - break; - - /* We get a WANT_WRITE if we're trying to rehandshake and we block on a write during that rehandshake. - * We need to wait on the socket to be writeable but reinitiate the read when it is - */ - case SSL_ERROR_WANT_WRITE: - read_blocked_on_write = true; - break; - default: - return; - break; - } - - /* We need a check for read_blocked here because SSL_pending() doesn't work properly during the - * handshake. This check prevents a busy-wait loop around SSL_read() - */ - } while (SSL_pending(ssl->ssl) && !read_blocked); } + break; + case SSL_ERROR_ZERO_RETURN: + /* End of data */ + SSL_shutdown(ssl->ssl); + return; + break; + case SSL_ERROR_WANT_READ: { + socket_events se{ev}; + se.flags = WANT_READ | WANT_ERROR; + owner->socketengine->update_socket(se); + break; } - - /* Check for input on the sendq */ - if (obuffer.length() && client_to_server_length == 0) { - memcpy(&client_to_server_buffer, obuffer.data(), obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length()); - client_to_server_length = obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length(); - obuffer = obuffer.substr(client_to_server_length, obuffer.length()); - client_to_server_offset = 0; + /* We get a WANT_WRITE if we're trying to rehandshake, and we block on a write during that rehandshake. + * We need to wait on the socket to be writeable but initiate the read when it is + */ + case SSL_ERROR_WANT_WRITE: { + socket_events se{ev}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + break; + } + case SSL_ERROR_SYSCALL: { + if (errno != 0) { + this->close(); + std::cout << "read error " << errno << ": " << strerror(errno) << "\n"; + } + break; } + default: { + std::cout << "read default\n"; + this->close(); + return; + } + } + } - /* If the socket is writeable... */ - if (((pfd[0].revents & POLLOUT) && client_to_server_length) || (write_blocked_on_read && (pfd[0].revents & POLLIN))) { - write_blocked_on_read = false; - /* Try to write */ + if (!connected && !plaintext) { + complete_handshake(&ev); + } - if (plaintext) { - r = (int) ::send(sfd, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length, 0); + if (connected && ssl && ssl->ssl && (!obuffer.empty() || SSL_want_write(ssl->ssl))) { + socket_events se{ev}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + } +} - if (r < 0) { - /* Write error */ - return; - } +void ssl_client::on_write(socket fd, const struct socket_events& e) { + if (connected) { + if (obuffer.length() && client_to_server_length == 0) { + memcpy(&client_to_server_buffer, obuffer.data(), obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length()); + client_to_server_length = obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length(); + obuffer = obuffer.substr(client_to_server_length, obuffer.length()); + client_to_server_offset = 0; + } + if (plaintext) { + int r = (int) ::send(sfd, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length, 0); + if (r < 0) { + /* Write error */ + std::cout << "plaintext write\n"; + this->close(); + return; + } + client_to_server_length -= r; + client_to_server_offset += r; + bytes_out += r; + if (client_to_server_length > 0) { + socket_events se{e}; + se.flags = WANT_READ | WANT_ERROR; + owner->socketengine->update_socket(se); + } + } else { + int r = SSL_write(ssl->ssl, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length); + int err = SSL_get_error(ssl->ssl, r); + + switch (err) { + /* We wrote something */ + case SSL_ERROR_NONE: client_to_server_length -= r; client_to_server_offset += r; bytes_out += r; - } else { - r = SSL_write(ssl->ssl, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length); - - switch(SSL_get_error(ssl->ssl,r)) { - /* We wrote something */ - case SSL_ERROR_NONE: - client_to_server_length -= r; - client_to_server_offset += r; - bytes_out += r; - break; - - /* We would have blocked */ - case SSL_ERROR_WANT_WRITE: - break; - - /* We get a WANT_READ if we're trying to rehandshake and we block onwrite during the current connection. - * We need to wait on the socket to be readable but reinitiate our write when it is - */ - case SSL_ERROR_WANT_READ: - write_blocked_on_read = true; - break; - - /* Some other error */ - default: - return; - break; + break; + /* We would have blocked */ + case SSL_ERROR_WANT_READ: { + socket_events se{e}; + se.flags = WANT_READ | WANT_ERROR; + owner->socketengine->update_socket(se); + break; + } + case SSL_ERROR_WANT_WRITE: { + socket_events se{e}; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(se); + break; + } + case SSL_ERROR_SYSCALL: { + if (errno != 0) { + this->close(); + std::cout << "write error " << errno << ": " << strerror(errno) << "\n"; } + break; } + /* Some other error */ + default: { + std::cout << "write error " << err << "\n"; + //this->close(); + return; + } + } + } + } else { + if (!plaintext) { + /* Each thread needs a context, but we don't need to make a new one for each connection */ + if (!openssl_context) { + std::cout << "*** SSL_CTX_new\n"; + /* We're good to go - hand the fd over to openssl */ + const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */ + + /* Create SSL context */ + openssl_context.reset(SSL_CTX_new(method)); + if (!openssl_context) { + throw dpp::connection_exception(err_ssl_context, "Failed to create SSL client context!"); + } + + /* Do not allow SSL 3.0, TLS 1.0 or 1.1 + * https://www.packetlabs.net/posts/tls-1-1-no-longer-secure/ + */ + if (!SSL_CTX_set_min_proto_version(openssl_context.get(), TLS1_2_VERSION)) { + throw dpp::connection_exception(err_ssl_version, "Failed to set minimum SSL version!"); + } + } + if (!ssl->ssl) { + std::cout << "*** SSL_new\n"; + /* Create SSL session */ + ssl->ssl = SSL_new(openssl_context.get()); + if (ssl->ssl == nullptr) { + throw dpp::connection_exception(err_ssl_new, "SSL_new failed!"); + } + + SSL_set_fd(ssl->ssl, (int) sfd); + SSL_set_connect_state(ssl->ssl); + + /* Server name identification (SNI) */ + SSL_set_tlsext_host_name(ssl->ssl, hostname.c_str()); } } } - catch (const std::exception &e) { - log(ll_warning, std::string("Read loop ended: ") + e.what()); + + if (!connected && !plaintext) { + complete_handshake(&e); } } +void ssl_client::on_error(socket fd, const struct socket_events&, int error_code) { + std::cout << "error event\n"; + throw dpp::connection_exception(err_socket_error, strerror(errno)); +} + +using namespace std::placeholders; + +void ssl_client::read_loop() +{ + dpp::socket_events events( + sfd, + WANT_READ | WANT_WRITE | WANT_ERROR, + [this](socket fd, const struct socket_events& e) { on_read(fd, e); }, + [this](socket fd, const struct socket_events& e) { on_write(fd, e); }, + [this](socket fd, const struct socket_events& e, int error_code) { on_error(fd, e, error_code); } + ); + owner->socketengine->register_socket(events); + owner->start_timer([this](auto handle) { + one_second_timer(); + }, 1); +} + uint64_t ssl_client::get_bytes_out() { return bytes_out; @@ -633,24 +537,13 @@ bool ssl_client::handle_buffer(std::string &buffer) void ssl_client::close() { - if (keepalive && this->sfd != INVALID_SOCKET) { - std::string identifier((!plaintext ? "ssl://" : "tcp://") + hostname + ":" + port); - auto iter = keepalives.find(identifier); - if (iter == keepalives.end()) { - keepalive_cache_t kc; - kc.created = time(nullptr); - kc.sfd = this->sfd; - kc.ssl = this->ssl; - keepalives.emplace(identifier, kc); - } - return; - } - + std::cout << "close()\n"; if (!plaintext && ssl->ssl) { SSL_free(ssl->ssl); ssl->ssl = nullptr; } close_socket(sfd); + owner->socketengine->delete_socket(sfd); sfd = INVALID_SOCKET; obuffer.clear(); buffer.clear(); @@ -659,13 +552,12 @@ void ssl_client::close() void ssl_client::cleanup() { this->close(); - if (!keepalive) { - delete ssl; - } + delete ssl; } ssl_client::~ssl_client() { + std::cout << "~ssl_client()\n"; cleanup(); } diff --git a/src/dpp/voice/enabled/constructor.cpp b/src/dpp/voice/enabled/constructor.cpp index fc8e16d0d5..939648fc9a 100644 --- a/src/dpp/voice/enabled/constructor.cpp +++ b/src/dpp/voice/enabled/constructor.cpp @@ -33,7 +33,7 @@ namespace dpp { discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave) - : websocket_client(_host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT), + : websocket_client(_cluster, _host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT), runner(nullptr), connect_time(0), mixer(std::make_unique()), diff --git a/src/dpp/voice/stub/stubs.cpp b/src/dpp/voice/stub/stubs.cpp index c027aa8b43..52bb2d5fe2 100644 --- a/src/dpp/voice/stub/stubs.cpp +++ b/src/dpp/voice/stub/stubs.cpp @@ -28,7 +28,7 @@ namespace dpp { discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave) - : websocket_client(_host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT) + : websocket_client(_cluster, _host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT) { throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); } diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index 618c7e7282..e5ebe762de 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace dpp { @@ -37,8 +38,8 @@ constexpr size_t WS_MAX_PAYLOAD_LENGTH_SMALL = 125; constexpr size_t WS_MAX_PAYLOAD_LENGTH_LARGE = 65535; constexpr size_t MAXHEADERSIZE = sizeof(uint64_t) + 2; -websocket_client::websocket_client(const std::string& hostname, const std::string& port, const std::string& urlpath, ws_opcode opcode) - : ssl_client(hostname, port), +websocket_client::websocket_client(cluster* creator, const std::string& hostname, const std::string& port, const std::string& urlpath, ws_opcode opcode) + : ssl_client(creator, hostname, port), state(HTTP_HEADERS), path(urlpath), data_opcode(opcode) diff --git a/src/soaktest/soak.cpp b/src/soaktest/soak.cpp index 97195f21fd..83621a6a41 100644 --- a/src/soaktest/soak.cpp +++ b/src/soaktest/soak.cpp @@ -29,9 +29,11 @@ int main() { using namespace std::chrono_literals; char* t = getenv("DPP_UNIT_TEST_TOKEN"); if (t) { - dpp::cluster soak_test(t, dpp::i_default_intents | dpp::i_guild_members); - soak_test.set_websocket_protocol(dpp::ws_etf); - soak_test.on_log(dpp::utility::cout_logger()); + dpp::cluster soak_test(t, dpp::i_default_intents | dpp::i_guild_members, 1, 0, 1); + //soak_test.set_websocket_protocol(dpp::ws_etf); + soak_test.on_log([&](const dpp::log_t& log) { + std::cout << "[" << dpp::utility::current_date_time() << "] " << dpp::utility::loglevel(log.severity) << ": " << log.message << std::endl; + }); soak_test.start(dpp::st_return); while (true) { std::this_thread::sleep_for(60s); diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index da96a28096..5c586da5e9 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -288,7 +288,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b "{\"content\":\"test\"}", {"test.txt", "blob.blob"}, {"ABCDEFGHI", "BLOB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}, {"text/plain", "application/octet-stream"} ); try { - dpp::https_client c("discord.com", 443, "/api/channels/" + std::to_string(TEST_TEXT_CHANNEL_ID) + "/messages", "POST", multipart.body, + /*dpp::https_client c("discord.com", 443, "/api/channels/" + std::to_string(TEST_TEXT_CHANNEL_ID) + "/messages", "POST", multipart.body, { {"Content-Type", multipart.mimetype}, {"Authorization", "Bot " + token} @@ -296,7 +296,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b ); std::string hdr1 = c.get_header("server"); std::string content1 = c.get_content(); - set_test(HTTPS, hdr1 == "cloudflare" && c.get_status() == 200); + set_test(HTTPS, hdr1 == "cloudflare" && c.get_status() == 200);*/ } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; @@ -306,10 +306,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(HTTP, false); try { - dpp::https_client c2("github.com", 80, "/", "GET", "", {}, true); + /*dpp::https_client c2("github.com", 80, "/", "GET", "", {}, true); std::string hdr2 = c2.get_header("location"); std::string content2 = c2.get_content(); - set_test(HTTP, hdr2 == "https://github.com/" && c2.get_status() == 301); + set_test(HTTP, hdr2 == "https://github.com/" && c2.get_status() == 301);*/ } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; @@ -318,11 +318,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(MULTIHEADER, false); try { - dpp::https_client c2("dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}); + /*dpp::https_client c2("dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}); size_t count = c2.get_header_count("set-cookie"); size_t count_list = c2.get_header_list("set-cookie").size(); // Google sets a bunch of cookies when we start accessing it. - set_test(MULTIHEADER, c2.get_status() == 200 && count > 1 && count == count_list); + set_test(MULTIHEADER, c2.get_status() == 200 && count > 1 && count == count_list);*/ } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; From 406642e67a2011153e79920b768988017bcb22b9 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Nov 2024 18:04:41 +0000 Subject: [PATCH 019/112] [skip ci] fix test program --- src/sockettest/socket.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp index 2a9ddec7dd..c1054ccedf 100644 --- a/src/sockettest/socket.cpp +++ b/src/sockettest/socket.cpp @@ -28,7 +28,8 @@ #include int main() { - auto se = dpp::create_socket_engine(); + dpp::cluster cl("no-token"); + auto se = dpp::create_socket_engine(cl); const dpp::dns_cache_entry* addr = dpp::resolve_hostname("neuron.brainbox.cc", "80"); std::cout << "Connect to IP: " << addr->resolved_addr << "\n"; From 9cc64adc85d6cb54d2bbd2311f828fbcf2ca309a Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Nov 2024 18:05:43 +0000 Subject: [PATCH 020/112] [skip ci] fix test program --- src/sockettest/socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp index c1054ccedf..e6e19e652d 100644 --- a/src/sockettest/socket.cpp +++ b/src/sockettest/socket.cpp @@ -29,7 +29,7 @@ int main() { dpp::cluster cl("no-token"); - auto se = dpp::create_socket_engine(cl); + auto se = dpp::create_socket_engine(&cl); const dpp::dns_cache_entry* addr = dpp::resolve_hostname("neuron.brainbox.cc", "80"); std::cout << "Connect to IP: " << addr->resolved_addr << "\n"; From c6278eb4755b9318cfa74105d2ffef866a04bf86 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 13 Nov 2024 18:13:22 +0000 Subject: [PATCH 021/112] [skip ci] i love to break stuff --- src/dpp/sslclient.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index fa13f31e15..625daa1def 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -503,8 +503,6 @@ void ssl_client::on_error(socket fd, const struct socket_events&, int error_code throw dpp::connection_exception(err_socket_error, strerror(errno)); } -using namespace std::placeholders; - void ssl_client::read_loop() { dpp::socket_events events( From a742604c73acc7395aab45b42afcab6fca28820f Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 14 Nov 2024 16:02:37 +0000 Subject: [PATCH 022/112] priority queue --- include/dpp/thread_pool.h | 13 +++++++++++-- src/dpp/thread_pool.cpp | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/include/dpp/thread_pool.h b/include/dpp/thread_pool.h index 65c7b9fc71..bc9cfab066 100644 --- a/include/dpp/thread_pool.h +++ b/include/dpp/thread_pool.h @@ -31,7 +31,16 @@ /** * A task within a thread pool. A simple lambda that accepts no parameters and returns void. */ -using thread_pool_task = std::function; +struct thread_pool_task { + int priority; + std::function function; +}; + +struct thread_pool_task_comparator { + bool operator()(const thread_pool_task &a, const thread_pool_task &b) { + return a.priority < b.priority; + }; +}; /** * @brief A thread pool contains 1 or more worker threads which accept thread_pool_task lambadas @@ -39,7 +48,7 @@ using thread_pool_task = std::function; */ struct thread_pool { std::vector threads; - std::queue tasks; + std::priority_queue, thread_pool_task_comparator> tasks; std::mutex queue_mutex; std::condition_variable cv; bool stop{false}; diff --git a/src/dpp/thread_pool.cpp b/src/dpp/thread_pool.cpp index 95469a784c..7d32baf19d 100644 --- a/src/dpp/thread_pool.cpp +++ b/src/dpp/thread_pool.cpp @@ -41,11 +41,11 @@ thread_pool::thread_pool(size_t num_threads) { return; } - task = std::move(tasks.front()); + task = std::move(tasks.top()); tasks.pop(); } - task(); + task.function(); } }); } From 3ad17622213058f82ff99007b4caf8295d188693 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 15 Nov 2024 13:08:47 +0000 Subject: [PATCH 023/112] [skip ci] https client/queues migration to socketengine --- include/dpp/httpsclient.h | 14 ++- include/dpp/queues.h | 57 +++++++----- include/dpp/sslclient.h | 35 ++++++- src/dpp/cluster.cpp | 132 ++++++++++++-------------- src/dpp/cluster/timer.cpp | 15 ++- src/dpp/httpsclient.cpp | 27 +++++- src/dpp/queues.cpp | 189 +++++++++++++++++++++----------------- src/dpp/socketengine.cpp | 2 + src/dpp/sslclient.cpp | 33 +------ 9 files changed, 285 insertions(+), 219 deletions(-) diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h index 69cd8f55cf..fdb2336aea 100644 --- a/include/dpp/httpsclient.h +++ b/include/dpp/httpsclient.h @@ -130,6 +130,8 @@ struct http_connect_info { uint16_t port; }; +using https_client_completion_event = std::function; + /** * @brief Implements a HTTPS socket client based on the SSL client. * @note plaintext HTTP without SSL is also supported via a "downgrade" setting @@ -233,7 +235,12 @@ class DPP_EXPORT https_client : public ssl_client { /** * @brief If true the response timed out while waiting */ - bool timed_out; + bool timed_out; + + /** + * @brief Function to call when HTTP request is completed + */ + https_client_completion_event completed; /** * @brief Connect to a specific HTTP(S) server and complete a request. @@ -255,13 +262,14 @@ class DPP_EXPORT https_client : public ssl_client { * @param plaintext_connection Set to true to make the connection plaintext (turns off SSL) * @param request_timeout How many seconds before the connection is considered failed if not finished * @param protocol Request HTTP protocol (default: 1.1) + * @param done Function to call when https_client request is completed */ - https_client(cluster* creator, const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5, const std::string &protocol = "1.1"); + https_client(cluster* creator, const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5, const std::string &protocol = "1.1", https_client_completion_event done = {}); /** * @brief Destroy the https client object */ - virtual ~https_client() = default; + virtual ~https_client(); /** * @brief Build a multipart content from a set of files and some json diff --git a/include/dpp/queues.h b/include/dpp/queues.h index c502647936..61dc588baa 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -31,6 +31,7 @@ #include #include #include +#include namespace dpp { @@ -231,6 +232,12 @@ class DPP_EXPORT http_request { * @brief True for requests that are not going to discord (rate limits code skipped). */ bool non_discord; + + /** + * @brief HTTPS client + */ + std::unique_ptr cli; + public: /** * @brief Endpoint name @@ -301,6 +308,11 @@ class DPP_EXPORT http_request { */ DPP_DEPRECATED("Please now use dpp::cluster::request_timeout") time_t request_timeout; + /** + * @brief Unique ptr to self + */ + std::unique_ptr me{nullptr}; + /** * @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request(). * @param _endpoint The API endpoint, e.g. /api/guilds @@ -349,6 +361,12 @@ class DPP_EXPORT http_request { */ ~http_request(); + /** + * @brief stash unique ptr to self for moving into outbound queue when done + * @param self unique self + */ + void stash_self(std::unique_ptr self); + /** * @brief Call the completion callback, if the request is complete. * @param c callback to call @@ -359,7 +377,7 @@ class DPP_EXPORT http_request { * @brief Execute the HTTP request and mark the request complete. * @param owner creating cluster */ - http_request_completion_t run(class cluster* owner); + http_request_completion_t run(class in_thread* processor, class cluster* owner); /** @brief Returns true if the request is complete */ bool is_completed(); @@ -406,7 +424,7 @@ struct DPP_EXPORT bucket_t { * waiting for internal containers to be usable. */ class DPP_EXPORT in_thread { -private: +public: /** * @brief True if ending. */ @@ -452,7 +470,7 @@ class DPP_EXPORT in_thread { * @param index Thread index */ void in_loop(uint32_t index); -public: + /** * @brief Construct a new in thread object * @@ -500,7 +518,7 @@ class DPP_EXPORT in_thread { * used to support user REST calls via dpp::cluster::request(). */ class DPP_EXPORT request_queue { -protected: +public: /** * @brief Required so in_thread can access these member variables */ @@ -511,11 +529,6 @@ class DPP_EXPORT request_queue { */ class cluster* creator; - /** - * @brief Outbound queue mutex thread safety - */ - std::shared_mutex out_mutex; - /** * @brief Outbound queue thread * Note that although there are many 'in queues', which handle the HTTP requests, @@ -525,12 +538,6 @@ class DPP_EXPORT request_queue { */ std::thread* out_thread; - /** - * @brief Outbound queue condition. - * Signalled when there are requests completed to call callbacks for. - */ - std::condition_variable out_ready; - /** * @brief A completed request. Contains both the request and the response */ @@ -546,11 +553,6 @@ class DPP_EXPORT request_queue { std::unique_ptr response; }; - /** - * @brief Completed requests queue - */ - std::queue responses_out; - /** * @brief A vector of inbound request threads forming a pool. * There are a set number of these defined by a constant in queues.cpp. A request is always placed @@ -622,7 +624,6 @@ class DPP_EXPORT request_queue { * @brief Outbound queue thread loop */ void out_loop(); -public: /** * @brief constructor @@ -669,6 +670,20 @@ class DPP_EXPORT request_queue { * @return true if globally rate limited */ bool is_globally_ratelimited() const; + + /** + * @brief Completed requests queue + */ + std::queue responses_out; + /** + * @brief Outbound queue condition. + * Signalled when there are requests completed to call callbacks for. + */ + std::condition_variable out_ready; + /** + * @brief Outbound queue mutex thread safety + */ + std::shared_mutex out_mutex; }; } diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 75fcd10138..7d97e26945 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace dpp { @@ -88,6 +89,27 @@ class DPP_EXPORT ssl_client * @brief Clean up resources */ void cleanup(); + + /** + * @brief Start offset into internal ring buffer for client to server IO + */ + size_t client_to_server_length = 0; + + /** + * @brief Start offset into internal ring buffer for server to client IO + */ + size_t client_to_server_offset = 0; + + /** + * @brief Internal ring buffer for client to server IO + */ + char client_to_server_buffer[DPP_BUFSIZE]; + + /** + * @brief Internal ring buffer for server to client IO + */ + char server_to_client_buffer[DPP_BUFSIZE]; + protected: /** * @brief Input buffer received from socket @@ -155,6 +177,11 @@ class DPP_EXPORT ssl_client */ bool connected{false}; + /** + * @brief Timer handle for one second timer + */ + timer timer_handle; + /** * @brief Called every second @@ -166,6 +193,7 @@ class DPP_EXPORT ssl_client * @throw dpp::exception Failed to initialise connection */ virtual void connect(); + public: /** * @brief Get the bytes out objectGet total bytes sent @@ -217,12 +245,11 @@ class DPP_EXPORT ssl_client */ bool keepalive; + /** + * @brief Owning cluster + */ class cluster* owner; - size_t client_to_server_length = 0, client_to_server_offset = 0; - char client_to_server_buffer[DPP_BUFSIZE], server_to_client_buffer[DPP_BUFSIZE]; - - /** * @brief Connect to a specified host and port. Throws std::runtime_error on fatal error. * @param _hostname The hostname to connect to diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index d9ea131b41..e0307c7230 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -220,22 +220,21 @@ void cluster::start(bool return_after) { } /* Start up all shards */ - gateway g; - try { -#ifdef DPP_CORO - //confirmation_callback_t cc = co_get_gateway_bot().sync_wait(); - //g = std::get(cc.value); -#else - //g = dpp::sync(this, &cluster::get_gateway_bot); -#endif + get_gateway_bot([this](const auto& response) { + if (response.is_error()) { + // TODO: Check for 401 unauthorized + // throw dpp::invalid_token_exception(err_unauthorized, "Invalid bot token (401: Unauthorized when getting gateway shard count)"); + return; + } + auto g = std::get(response.value); log(ll_debug, "Cluster: " + std::to_string(g.session_start_remaining) + " of " + std::to_string(g.session_start_total) + " session starts remaining"); - if (g.session_start_remaining < g.shards) { + if (g.session_start_remaining < g.shards || g.shards == 0) { throw dpp::connection_exception(err_no_sessions_left, "Discord indicates you cannot start enough sessions to boot this cluster! Cluster startup aborted. Try again later."); - } - if (g.session_start_max_concurrency > 1) { + } else if (g. session_start_max_concurrency == 0) { + throw dpp::connection_exception(err_auto_shard, "Cluster: Could not determine concurrency, startup aborted!"); + } else if (g.session_start_max_concurrency > 1) { log(ll_debug, "Cluster: Large bot sharding; Using session concurrency: " + std::to_string(g.session_start_max_concurrency)); - } - if (numshards == 0) { + } else if (numshards == 0) { if (g.shards) { log(ll_info, "Auto Shard: Bot requires " + std::to_string(g.shards) + std::string(" shard") + ((g.shards > 1) ? "s" : "")); } else { @@ -243,75 +242,64 @@ void cluster::start(bool return_after) { } numshards = g.shards; } - } - catch (const dpp::rest_exception& e) { - if (std::string(e.what()) == "401: Unauthorized") { - /* Throw special form of exception for invalid token */ - throw dpp::invalid_token_exception(err_unauthorized, "Invalid bot token (401: Unauthorized when getting gateway shard count)"); - } else { - /* Rethrow */ - throw e; - } - } - - start_time = time(nullptr); - - log(ll_debug, "Starting with " + std::to_string(numshards) + " shards..."); - - for (uint32_t s = 0; s < numshards; ++s) { - /* Filter out shards that aren't part of the current cluster, if the bot is clustered */ - if (s % maxclusters == cluster_id) { - /* Each discord_client spawns its own thread in its run() */ - try { - this->shards[s] = new discord_client(this, s, numshards, token, intents, compressed, ws_mode); - this->shards[s]->run(); - } - catch (const std::exception &e) { - log(dpp::ll_critical, "Could not start shard " + std::to_string(s) + ": " + std::string(e.what())); - } - /* Stagger the shard startups, pausing every 'session_start_max_concurrency' shards for 5 seconds. - * This means that for bots that don't have large bot sharding, any number % 1 is always 0, - * so it will pause after every shard. For any with non-zero concurrency it'll pause 5 seconds - * after every batch. - */ - /*if (((s + 1) % g.session_start_max_concurrency) == 0) { - size_t wait_time = 5; - if (g.session_start_max_concurrency > 1) { - // If large bot sharding, be sure to give the batch of shards time to settle - bool all_connected = true; - do { - all_connected = true; - for (auto& shard : this->shards) { - if (!shard.second->ready) { - all_connected = false; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - break; + start_time = time(nullptr); + log(ll_debug, "Starting with " + std::to_string(numshards) + " shards..."); + + for (uint32_t s = 0; s < numshards; ++s) { + /* Filter out shards that aren't part of the current cluster, if the bot is clustered */ + if (s % maxclusters == cluster_id) { + /* Each discord_client is inserted into the socket engine when we call run() */ + try { + this->shards[s] = new discord_client(this, s, numshards, token, intents, compressed, ws_mode); + this->shards[s]->run(); + } + catch (const std::exception &e) { + log(dpp::ll_critical, "Could not start shard " + std::to_string(s) + ": " + std::string(e.what())); + } + /* Stagger the shard startups, pausing every 'session_start_max_concurrency' shards for 5 seconds. + * This means that for bots that don't have large bot sharding, any number % 1 is always 0, + * so it will pause after every shard. For any with non-zero concurrency it'll pause 5 seconds + * after every batch. + */ + if (((s + 1) % g.session_start_max_concurrency) == 0) { + size_t wait_time = 5; + if (g.session_start_max_concurrency > 1) { + /* If large bot sharding, be sure to give the batch of shards time to settle */ + bool all_connected = true; + do { + all_connected = true; + for (auto& shard : this->shards) { + if (!shard.second->ready) { + all_connected = false; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + break; + } } - } - } while (all_connected); + } while (all_connected); + } + std::this_thread::sleep_for(std::chrono::seconds(wait_time)); } - std::this_thread::sleep_for(std::chrono::seconds(wait_time)); - }*/ + } } - } - /* Get all active DM channels and map them to user id -> dm id */ - /*this->current_user_get_dms([this](const dpp::confirmation_callback_t& completion) { - dpp::channel_map dmchannels = std::get(completion.value); - for (auto & c : dmchannels) { - for (auto & u : c.second.recipients) { - this->set_dm_channel(u, c.second.id); + /* Get all active DM channels and map them to user id -> dm id */ + current_user_get_dms([this](const dpp::confirmation_callback_t& completion) { + dpp::channel_map dmchannels = std::get(completion.value); + for (auto & c : dmchannels) { + for (auto & u : c.second.recipients) { + set_dm_channel(u, c.second.id); + } } - } - });*/ + }); - log(ll_debug, "Shards started."); + log(ll_debug, "Shards started."); + }); - // TODO: Temporary for testing do { + // TODO: Thread this socketengine->process_events(); } while (true); - + if (!return_after) { block_calling_thread(); } diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index a6520306f2..c41c38ec4b 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -39,12 +39,16 @@ timer cluster::start_timer(timer_callback_t on_tick, uint64_t frequency, timer_c timer_list[newtimer->handle] = newtimer; next_timer.emplace(newtimer->next_tick, newtimer); + std::cout << "start_timer " << newtimer->handle << "\n"; + return newtimer->handle; } bool cluster::stop_timer(timer t) { std::lock_guard l(timer_guard); + std::cout << "stop_timer " << t << "\n"; + auto i = timer_list.find(t); if (i != timer_list.end()) { timer_t* tptr = i->second; @@ -54,8 +58,9 @@ bool cluster::stop_timer(timer t) { } timer_list.erase(i); auto j = next_timer.find(tptr->next_tick); - if (j != next_timer.end()) { + while (j != next_timer.end()) { next_timer.erase(j); + j = next_timer.find(tptr->next_tick); } delete tptr; return true; @@ -84,12 +89,12 @@ void cluster::tick_timers() { { time_t now = time(nullptr); std::lock_guard l(timer_guard); - for (auto i = next_timer.begin(); i != next_timer.end(); ++i) { - if (now >= i->second->next_tick) { - scheduled.push_back(i->second); + for (auto & i : next_timer) { + if (now >= i.second->next_tick) { + scheduled.push_back(i.second); } else { /* The first time we encounter an entry which is not due, - * we can bail out, because std::map is ordered storage so + * we can bail out, because std::map is ordered storage, so * we know at this point no more will match either. */ break; diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 812b142e41..dc7f222e08 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -30,7 +30,7 @@ namespace dpp { -https_client::https_client(cluster* creator, const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout, const std::string &protocol) +https_client::https_client(cluster* creator, const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout, const std::string &protocol, https_client_completion_event done) : ssl_client(creator, hostname, std::to_string(port), plaintext_connection, false), state(HTTPS_HEADERS), request_type(verb), @@ -41,7 +41,8 @@ https_client::https_client(cluster* creator, const std::string &hostname, uint16 status(0), http_protocol(protocol), timeout(request_timeout), - timed_out(false) + timed_out(false), + completed(done) { nonblocking = false; timeout = time(nullptr) + request_timeout; @@ -249,6 +250,10 @@ bool https_client::handle_buffer(std::string &buffer) if (buffer.length() >= 2 && buffer.substr(0, 2) == "\r\n") { if (state == HTTPS_CHUNK_LAST) { state = HTTPS_DONE; + if (completed) { + completed(this); + completed = {}; + } this->close(); return false; } else { @@ -284,11 +289,19 @@ bool https_client::handle_buffer(std::string &buffer) buffer.clear(); if (content_length == ULLONG_MAX || body.length() >= content_length) { state = HTTPS_DONE; + if (completed) { + completed(this); + completed = {}; + } this->close(); return false; } break; case HTTPS_DONE: + if (completed) { + completed(this); + completed = {}; + } this->close(); return false; break; @@ -325,6 +338,16 @@ void https_client::close() { if (state != HTTPS_DONE) { state = HTTPS_DONE; ssl_client::close(); + if (completed) { + completed(this); + completed = {}; + } + } +} + +https_client::~https_client() { + if (sfd != INVALID_SOCKET) { + https_client::close(); } } diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 814611d504..efad7be945 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -31,6 +31,46 @@ namespace dpp { +namespace +{ + +/** + * @brief Comparator for sorting a request container + */ +struct compare_request { + /** + * @brief Less_than comparator for sorting + * @param lhs Left-hand side + * @param rhs Right-hand side + * @return Whether lhs comes before rhs in strict ordering + */ + bool operator()(const std::unique_ptr& lhs, const std::unique_ptr& rhs) const noexcept { + return std::less{}(lhs->endpoint, rhs->endpoint); + }; + + /** + * @brief Less_than comparator for sorting + * @param lhs Left-hand side + * @param rhs Right-hand side + * @return Whether lhs comes before rhs in strict ordering + */ + bool operator()(const std::unique_ptr& lhs, std::string_view rhs) const noexcept { + return std::less{}(lhs->endpoint, rhs); + }; + + /** + * @brief Less_than comparator for sorting + * @param lhs Left-hand side + * @param rhs Right-hand side + * @return Whether lhs comes before rhs in strict ordering + */ + bool operator()(std::string_view lhs, const std::unique_ptr& rhs) const noexcept { + return std::less{}(lhs, rhs->endpoint); + }; +}; + +} + http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method _method, const std::string &audit_reason, const std::string &filename, const std::string &filecontent, const std::string &filemimetype, const std::string &http_protocol) : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(_method), reason(audit_reason), mimetype("application/json"), waiting(false), protocol(http_protocol), request_timeout(5) { @@ -104,7 +144,7 @@ bool http_request::is_completed() } /* Execute a HTTP request */ -http_request_completion_t http_request::run(cluster* owner) { +http_request_completion_t http_request::run(in_thread* processor, cluster* owner) { http_request_completion_t rv; double start = dpp::utility::time_f(); @@ -175,28 +215,67 @@ http_request_completion_t http_request::run(cluster* owner) { } http_connect_info hci = https_client::get_host_info(_host); try { - https_client cli(owner, hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl, owner->request_timeout, protocol); - rv.latency = dpp::utility::time_f() - start; - if (cli.timed_out) { - rv.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Timed out while waiting for the response"); - } else if (cli.get_status() < 100) { - rv.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Malformed HTTP response"); - } else { - populate_result(_url, owner, rv, cli); - } + cli = std::make_unique( + owner, + hci.hostname, + hci.port, + _url, + request_verb[method], + multipart.body, + headers, + !hci.is_ssl, + owner->request_timeout, + protocol, + [processor, rv, hci, this, owner, start, _url](https_client* client) { + http_request_completion_t result{rv}; + result.latency = dpp::utility::time_f() - start; + if (client->timed_out) { + result.error = h_connection; + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Timed out while waiting for the response"); + } else if (cli->get_status() < 100) { + result.error = h_connection; + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Malformed HTTP response"); + } else { + populate_result(_url, owner, result, *client); + } + /* Set completion flag */ + completed = true; + + bucket_t newbucket; + newbucket.limit = result.ratelimit_limit; + newbucket.remaining = result.ratelimit_remaining; + newbucket.reset_after = result.ratelimit_reset_after; + newbucket.retry_after = result.ratelimit_retry_after; + newbucket.timestamp = time(nullptr); + processor->requests->globally_ratelimited = rv.ratelimit_global; + if (processor->requests->globally_ratelimited) { + processor->requests->globally_limited_for = (newbucket.retry_after ? newbucket.retry_after : newbucket.reset_after); + } + processor->buckets[this->endpoint] = newbucket; + + /* Transfer it to completed requests */ + /* Make a new entry in the completion list and notify */ + auto hrc = std::make_unique(); + *hrc = result; + { + std::scoped_lock lock1(processor->requests->out_mutex); + processor->requests->responses_out.push({std::move(me), std::move(hrc)}); + } + processor->requests->out_ready.notify_one(); + } + ); } catch (const std::exception& e) { owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": " + std::string(e.what())); rv.error = h_connection; } - - /* Set completion flag */ - completed = true; return rv; } +void http_request::stash_self(std::unique_ptr self) { + me = std::move(self); +} + request_queue::request_queue(class cluster* owner, uint32_t request_threads) : creator(owner), terminating(false), globally_ratelimited(false), globally_limited_for(0), in_thread_pool_size(request_threads) { for (uint32_t in_alloc = 0; in_alloc < in_thread_pool_size; ++in_alloc) { @@ -248,46 +327,6 @@ request_queue::~request_queue() delete out_thread; } -namespace -{ - -/** - * @brief Comparator for sorting a request container - */ -struct compare_request { - /** - * @brief Less_than comparator for sorting - * @param lhs Left-hand side - * @param rhs Right-hand side - * @return Whether lhs comes before rhs in strict ordering - */ - bool operator()(const std::unique_ptr& lhs, const std::unique_ptr& rhs) const noexcept { - return std::less{}(lhs->endpoint, rhs->endpoint); - }; - - /** - * @brief Less_than comparator for sorting - * @param lhs Left-hand side - * @param rhs Right-hand side - * @return Whether lhs comes before rhs in strict ordering - */ - bool operator()(const std::unique_ptr& lhs, std::string_view rhs) const noexcept { - return std::less{}(lhs->endpoint, rhs); - }; - - /** - * @brief Less_than comparator for sorting - * @param lhs Left-hand side - * @param rhs Right-hand side - * @return Whether lhs comes before rhs in strict ordering - */ - bool operator()(std::string_view lhs, const std::unique_ptr& rhs) const noexcept { - return std::less{}(lhs, rhs->endpoint); - }; -}; - -} - void in_thread::in_loop(uint32_t index) { utility::set_thread_name(std::string("http_req/") + std::to_string(index)); @@ -316,7 +355,7 @@ void in_thread::in_loop(uint32_t index) for (auto& request_view : requests_view) { const std::string &key = request_view->endpoint; http_request_completion_t rv; - auto currbucket = buckets.find(key); + auto currbucket = buckets.find(key); if (currbucket != buckets.end()) { /* There's a bucket for this request. Check its status. If the bucket says to wait, @@ -326,7 +365,7 @@ void in_thread::in_loop(uint32_t index) uint64_t wait = (currbucket->second.retry_after ? currbucket->second.retry_after : currbucket->second.reset_after); if ((uint64_t)time(nullptr) > currbucket->second.timestamp + wait) { /* Time has passed, we can process this bucket again. send its request. */ - rv = request_view->run(creator); + request_view->run(this, creator); } else { if (!request_view->waiting) { request_view->waiting = true; @@ -336,49 +375,31 @@ void in_thread::in_loop(uint32_t index) } } else { /* There's limit remaining, we can just run the request */ - rv = request_view->run(creator); + request_view->run(this, creator); } } else { /* No bucket for this endpoint yet. Just send it, and make one from its reply */ - rv = request_view->run(creator); + request_view->run(this, creator); } - bucket_t newbucket; - newbucket.limit = rv.ratelimit_limit; - newbucket.remaining = rv.ratelimit_remaining; - newbucket.reset_after = rv.ratelimit_reset_after; - newbucket.retry_after = rv.ratelimit_retry_after; - newbucket.timestamp = time(nullptr); - requests->globally_ratelimited = rv.ratelimit_global; - if (requests->globally_ratelimited) { - requests->globally_limited_for = (newbucket.retry_after ? newbucket.retry_after : newbucket.reset_after); - } - buckets[request_view->endpoint] = newbucket; - - /* Remove the request from the incoming requests to transfer it to completed requests */ - std::unique_ptr request; + /* Remove from inbound requests */ + std::unique_ptr rq; { /* Find the owned pointer in requests_in */ std::scoped_lock lock1{in_mutex}; + const std::string &key = request_view->endpoint; auto [begin, end] = std::equal_range(requests_in.begin(), requests_in.end(), key, compare_request{}); for (auto it = begin; it != end; ++it) { if (it->get() == request_view) { /* Grab and remove */ - request = std::move(*it); + // NOTE: Where to move this to?! + request_view->stash_self(std::move(*it)); requests_in.erase(it); break; } } } - /* Make a new entry in the completion list and notify */ - auto hrc = std::make_unique(); - *hrc = rv; - { - std::scoped_lock lock1(requests->out_mutex); - requests->responses_out.push({std::move(request), std::move(hrc)}); - } - requests->out_ready.notify_one(); } } else { @@ -423,8 +444,8 @@ void request_queue::out_loop() if (queue_head.request && queue_head.response) { queue_head.request->complete(*queue_head.response); - /* Queue deletions for 60 seconds from now */ - auto when = now + 60; + /* Queue deletions for 5 seconds from now */ + auto when = now + 5; auto where = std::lower_bound(responses_to_delete.begin(), responses_to_delete.end(), when); responses_to_delete.insert(where, {when, std::move(queue_head)}); } diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index f79d4779f3..bb611b5828 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -88,6 +88,8 @@ void socket_engine_base::prune() { if ((time(nullptr) % 60) == 0) { dpp::garbage_collection(); } + + last_time = time(nullptr); } } diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 625daa1def..3365b342db 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -175,10 +175,8 @@ int start_connecting(dpp::socket sockfd, const struct sockaddr *addr, socklen_t int err = EWOULDBLOCK; #else /* Standard POSIX connection behaviour */ - std::cout << "connect\n"; int rc = (::connect(sockfd, addr, addrlen)); int err = errno; - std::cout << "rc=" << rc << " err=" << err << "\n"; #endif if (rc == -1 && err != EWOULDBLOCK && err != EINPROGRESS) { throw connection_exception(err_connect_failure, strerror(errno)); @@ -208,6 +206,7 @@ ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std bytes_out(0), bytes_in(0), plaintext(plaintext_downgrade), + timer_handle(0), keepalive(reuse), owner(creator) { @@ -217,9 +216,7 @@ ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std ssl = new openssl_connection(); } try { - std::cout << "this->connect() call\n"; this->connect(); - std::cout << "this->connect() done\n"; } catch (std::exception&) { cleanup(); @@ -232,24 +229,18 @@ void ssl_client::connect() { /* Resolve hostname to IP */ int err = 0; - std::cout << "before resolve\n"; const dns_cache_entry* addr = resolve_hostname(hostname, port); - std::cout << "after resolve\n"; sfd = addr->make_connecting_socket(); address_t destination = addr->get_connecting_address(from_string(this->port, std::dec)); - std::cout << "got dest\n"; if (sfd == ERROR_STATUS) { - std::cout << "dest error\n"; err = errno; } else { start_connecting(sfd, destination.get_socket_address(), destination.size(), SOCKET_OP_TIMEOUT); } /* Check if valid connection started */ if (sfd == ERROR_STATUS) { - std::cout << "sfd error status\n"; throw dpp::connection_exception(err_connect_failure, strerror(err)); } - std::cout << "start_connecting done!\n"; } void ssl_client::socket_write(const std::string_view data) @@ -272,25 +263,21 @@ void ssl_client::log(dpp::loglevel severity, const std::string &msg) const void ssl_client::complete_handshake(const socket_events* ev) { - std::cout << "complete_handshake\n"; auto status = SSL_do_handshake(ssl->ssl); if (status != 1) { auto code = SSL_get_error(ssl->ssl, status); switch (code) { case SSL_ERROR_NONE: { - std::cout << "Handshake err none\n"; connected = true; break; } case SSL_ERROR_WANT_WRITE: { - std::cout << "Handshake want write\n"; socket_events se{*ev}; se.flags = dpp::WANT_READ | dpp::WANT_WRITE | dpp::WANT_ERROR; owner->socketengine->update_socket(se); break; } case SSL_ERROR_WANT_READ: { - std::cout << "Handshake want read\n"; socket_events se{*ev}; se.flags = dpp::WANT_READ | dpp::WANT_ERROR; owner->socketengine->update_socket(se); @@ -306,7 +293,6 @@ void ssl_client::complete_handshake(const socket_events* ev) owner->socketengine->update_socket(se); connected = true; this->cipher = SSL_get_cipher(ssl->ssl); - std::cout << "*** Handshake completion; cipher=" + this->cipher + "\n"; } } @@ -316,7 +302,6 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { if (plaintext && connected) { int r = (int) ::recv(sfd, server_to_client_buffer, DPP_BUFSIZE, 0); if (r <= 0) { - std::cout << "read default\n"; this->close(); return; } @@ -368,12 +353,10 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { case SSL_ERROR_SYSCALL: { if (errno != 0) { this->close(); - std::cout << "read error " << errno << ": " << strerror(errno) << "\n"; } break; } default: { - std::cout << "read default\n"; this->close(); return; } @@ -404,7 +387,6 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { int r = (int) ::send(sfd, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length, 0); if (r < 0) { /* Write error */ - std::cout << "plaintext write\n"; this->close(); return; } @@ -443,14 +425,11 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { case SSL_ERROR_SYSCALL: { if (errno != 0) { this->close(); - std::cout << "write error " << errno << ": " << strerror(errno) << "\n"; } break; } /* Some other error */ default: { - std::cout << "write error " << err << "\n"; - //this->close(); return; } } @@ -459,7 +438,6 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { if (!plaintext) { /* Each thread needs a context, but we don't need to make a new one for each connection */ if (!openssl_context) { - std::cout << "*** SSL_CTX_new\n"; /* We're good to go - hand the fd over to openssl */ const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */ @@ -477,7 +455,6 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { } } if (!ssl->ssl) { - std::cout << "*** SSL_new\n"; /* Create SSL session */ ssl->ssl = SSL_new(openssl_context.get()); if (ssl->ssl == nullptr) { @@ -499,7 +476,6 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { } void ssl_client::on_error(socket fd, const struct socket_events&, int error_code) { - std::cout << "error event\n"; throw dpp::connection_exception(err_socket_error, strerror(errno)); } @@ -513,7 +489,7 @@ void ssl_client::read_loop() [this](socket fd, const struct socket_events& e, int error_code) { on_error(fd, e, error_code); } ); owner->socketengine->register_socket(events); - owner->start_timer([this](auto handle) { + timer_handle = owner->start_timer([this](auto handle) { one_second_timer(); }, 1); } @@ -535,7 +511,6 @@ bool ssl_client::handle_buffer(std::string &buffer) void ssl_client::close() { - std::cout << "close()\n"; if (!plaintext && ssl->ssl) { SSL_free(ssl->ssl); ssl->ssl = nullptr; @@ -555,8 +530,10 @@ void ssl_client::cleanup() ssl_client::~ssl_client() { - std::cout << "~ssl_client()\n"; cleanup(); + if (timer_handle) { + owner->stop_timer(timer_handle); + } } } From f479209116d6a98329c59716946733c2a27d89ca Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 15 Nov 2024 15:16:26 +0000 Subject: [PATCH 024/112] [skip ci] fixes --- include/dpp/cluster.h | 5 +++ src/dpp/cluster.cpp | 1 + src/dpp/cluster/timer.cpp | 13 +++---- src/dpp/discordclient.cpp | 60 +++++++-------------------------- src/dpp/queues.cpp | 2 +- src/dpp/socketengines/epoll.cpp | 2 -- 6 files changed, 24 insertions(+), 59 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index a1dad6fe38..cad44951b9 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -240,6 +240,11 @@ class DPP_EXPORT cluster { */ std::unique_ptr socketengine; + /** + * @brief Thread pool + */ + std::unique_ptr pool{nullptr}; + /** * @brief Constructor for creating a cluster. All but the token are optional. * @param token The bot token to use for all HTTP commands and websocket connections diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index e0307c7230..46e5086dd6 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -89,6 +89,7 @@ cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, numshards(_shards), cluster_id(_cluster_id), maxclusters(_maxclusters), rest_ping(0.0), cache_policy(policy), ws_mode(ws_json) { socketengine = create_socket_engine(this); + pool = std::make_unique(request_threads); /* Instantiate REST request queues */ try { rest = new request_queue(this, request_threads); diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index c41c38ec4b..c255626d3b 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -39,16 +39,12 @@ timer cluster::start_timer(timer_callback_t on_tick, uint64_t frequency, timer_c timer_list[newtimer->handle] = newtimer; next_timer.emplace(newtimer->next_tick, newtimer); - std::cout << "start_timer " << newtimer->handle << "\n"; - return newtimer->handle; } bool cluster::stop_timer(timer t) { std::lock_guard l(timer_guard); - std::cout << "stop_timer " << t << "\n"; - auto i = timer_list.find(t); if (i != timer_list.end()) { timer_t* tptr = i->second; @@ -57,10 +53,11 @@ bool cluster::stop_timer(timer t) { tptr->on_stop(t); } timer_list.erase(i); - auto j = next_timer.find(tptr->next_tick); - while (j != next_timer.end()) { - next_timer.erase(j); - j = next_timer.find(tptr->next_tick); + for (auto this_timer = next_timer.begin(); this_timer != next_timer.end(); ++this_timer) { + if (this_timer->second == tptr) { + next_timer.erase(this_timer); + break; + } } delete tptr; return true; diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 3d200cc53a..3abe70ffa4 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -161,58 +161,18 @@ void discord_client::set_resume_hostname() void discord_client::thread_run() { - utility::set_thread_name(std::string("shard/") + std::to_string(shard_id)); - setup_zlib(); - do { - bool error = false; - ready = false; - message_queue.clear(); - - std::cout << "before read_loop\n"; - ssl_client::read_loop(); - std::cout << "after read_loop\n"; - while (!terminating) { - sleep(1); - } - std::cout << "after read_loop while\n"; - - if (!terminating) { - ssl_client::close(); - end_zlib(); - setup_zlib(); - do { - this->log(ll_debug, "Attempting reconnection of shard " + std::to_string(this->shard_id) + " to wss://" + resume_gateway_url); - error = false; - try { - set_resume_hostname(); - ssl_client::connect(); - websocket_client::connect(); - } - catch (const std::exception &e) { - log(dpp::ll_error, std::string("Error establishing connection, retry in 5 seconds: ") + e.what()); - ssl_client::close(); - std::this_thread::sleep_for(std::chrono::seconds(5)); - error = true; - } - } while (error); - } - } while(!terminating); - if (this->sfd != INVALID_SOCKET) { - /* Send a graceful termination */ - this->log(ll_debug, "Graceful shutdown of shard " + std::to_string(this->shard_id) + " succeeded."); - this->nonblocking = false; - this->send_close_packet(); - ssl_client::close(); - } else { - this->log(ll_debug, "Graceful shutdown of shard " + std::to_string(this->shard_id) + " not possible, socket already closed."); - } - end_zlib(); } void discord_client::run() { - this->runner = new std::thread(&discord_client::thread_run, this); - this->thread_id = runner->native_handle(); + // TODO: This only runs once. Replace the reconnect mechanics. + // To make this work, we will need to intercept errors. + setup_zlib(); + ready = false; + message_queue.clear(); + ssl_client::read_loop(); + //ssl_client::close(); + //end_zlib(); } bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) @@ -299,6 +259,8 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) break; } + //log(dpp::ll_trace, "R: " + j.dump()); + auto seq = j.find("s"); if (seq != j.end() && !seq->is_null()) { last_seq = seq->get(); @@ -493,6 +455,7 @@ void discord_client::one_second_timer() } websocket_client::one_second_timer(); + std::cout << "Shard tick\n"; /* This all only triggers if we are connected (have completed websocket, and received READY or RESUMED) */ if (this->is_connected()) { @@ -532,6 +495,7 @@ void discord_client::one_second_timer() if (this->heartbeat_interval && this->last_seq) { /* Check if we're due to emit a heartbeat */ if (time(nullptr) > last_heartbeat + ((heartbeat_interval / 1000.0) * 0.75)) { + std::cout << "Send ping\n"; last_ping_message = jsonobj_to_string(json({{"op", 1}, {"d", last_seq}})); queue_message(last_ping_message, true); last_heartbeat = time(nullptr); diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index efad7be945..47f05ee459 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -278,7 +278,7 @@ void http_request::stash_self(std::unique_ptr self) { request_queue::request_queue(class cluster* owner, uint32_t request_threads) : creator(owner), terminating(false), globally_ratelimited(false), globally_limited_for(0), in_thread_pool_size(request_threads) { - for (uint32_t in_alloc = 0; in_alloc < in_thread_pool_size; ++in_alloc) { + for (up_t32_t in_alloc = 0; in_alloc < in_thread_pool_size; ++in_alloc) { requests_in.push_back(std::make_unique(owner, this, in_alloc)); } out_thread = new std::thread(&request_queue::out_loop, this); diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index b2b1edb75d..e092a7ba70 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -130,7 +130,6 @@ struct socket_engine_epoll : public socket_engine_base { bool register_socket(const socket_events& e) final { bool r = socket_engine_base::register_socket(e); - std::cout << "return register: " << r << "\n"; if (r) { struct epoll_event ev{}; ev.events = EPOLLET; @@ -145,7 +144,6 @@ struct socket_engine_epoll : public socket_engine_base { } ev.data.ptr = fds.find(e.fd)->second.get(); int i = epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev); - std::cout << "epoll_ctl for fd " << e.fd << ": " << i << "\n"; if (i < 0) { throw dpp::connection_exception("Failed to register socket to epoll_ctl()"); } From 6e9fa8deb65a5c8ace48429e6f0af32cfb0b5440 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 15 Nov 2024 16:05:40 +0000 Subject: [PATCH 025/112] [skip ci] no longer any need for out_queue -> goes into the main thread pool --- include/dpp/cluster.h | 24 ++++++++++++--- include/dpp/queues.h | 62 ------------------------------------- include/dpp/thread_pool.h | 4 ++- src/dpp/cluster.cpp | 4 +++ src/dpp/discordclient.cpp | 2 -- src/dpp/queues.cpp | 65 +++++---------------------------------- 6 files changed, 33 insertions(+), 128 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index cad44951b9..44a7f521f2 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -172,6 +172,13 @@ class DPP_EXPORT cluster { * @param t Timer to reschedule */ void timer_reschedule(timer_t* t); + + /** + * @brief Thread pool + */ + std::unique_ptr pool{nullptr}; + + public: /** * @brief Current bot token for all shards on this cluster and all commands sent via HTTP @@ -240,11 +247,6 @@ class DPP_EXPORT cluster { */ std::unique_ptr socketengine; - /** - * @brief Thread pool - */ - std::unique_ptr pool{nullptr}; - /** * @brief Constructor for creating a cluster. All but the token are optional. * @param token The bot token to use for all HTTP commands and websocket connections @@ -261,6 +263,18 @@ class DPP_EXPORT cluster { */ cluster(const std::string& token, uint32_t intents = i_default_intents, uint32_t shards = 0, uint32_t cluster_id = 0, uint32_t maxclusters = 1, bool compressed = true, cache_policy_t policy = cache_policy::cpol_default, uint32_t request_threads = 12, uint32_t request_threads_raw = 1); + /** + * @brief Place some arbitrary work into the thread pool for execution when time permits. + * + * Work units are fetched into threads on the thread pool from the queue in order of priority, + * lowest numeric values first. Low numeric values should be reserved for API replies from Discord, + * guild creation events, etc. + * + * @param priority Priority of the work unit + * @param task Task to queue + */ + void queue_work(int priority, work_unit task); + /** * @brief dpp::cluster is non-copyable */ diff --git a/include/dpp/queues.h b/include/dpp/queues.h index 61dc588baa..bd43db6a72 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -529,15 +529,6 @@ class DPP_EXPORT request_queue { */ class cluster* creator; - /** - * @brief Outbound queue thread - * Note that although there are many 'in queues', which handle the HTTP requests, - * there is only ever one 'out queue' which dispatches the results to the caller. - * This is to simplify thread management in bots that use the library, as less mutexing - * and thread safety boilerplate is required. - */ - std::thread* out_thread; - /** * @brief A completed request. Contains both the request and the response */ @@ -564,40 +555,6 @@ class DPP_EXPORT request_queue { */ std::vector> requests_in; - /** - * @brief A request queued for deletion in the queue. - */ - struct queued_deleting_request { - /** - * @brief Time to delete the request - */ - time_t time_to_delete; - - /** - * @brief The request to delete - */ - completed_request request; - - /** - * @brief Comparator for sorting purposes - * @param other Other queued request to compare the deletion time with - * @return bool Whether this request comes before another in strict ordering - */ - bool operator<(const queued_deleting_request& other) const noexcept; - - /** - * @brief Comparator for sorting purposes - * @param time Time to compare with - * @return bool Whether this request's deletion time is lower than the time given, for strict ordering - */ - bool operator<(time_t time) const noexcept; - }; - - /** - * @brief Completed requests to delete. Sorted by deletion time - */ - std::vector responses_to_delete; - /** * @brief Set to true if the threads should terminate */ @@ -620,11 +577,6 @@ class DPP_EXPORT request_queue { */ uint32_t in_thread_pool_size; - /** - * @brief Outbound queue thread loop - */ - void out_loop(); - /** * @brief constructor * @param owner The creating cluster. @@ -670,20 +622,6 @@ class DPP_EXPORT request_queue { * @return true if globally rate limited */ bool is_globally_ratelimited() const; - - /** - * @brief Completed requests queue - */ - std::queue responses_out; - /** - * @brief Outbound queue condition. - * Signalled when there are requests completed to call callbacks for. - */ - std::condition_variable out_ready; - /** - * @brief Outbound queue mutex thread safety - */ - std::shared_mutex out_mutex; }; } diff --git a/include/dpp/thread_pool.h b/include/dpp/thread_pool.h index bc9cfab066..76398d1daf 100644 --- a/include/dpp/thread_pool.h +++ b/include/dpp/thread_pool.h @@ -28,12 +28,14 @@ #include #include +using work_unit = std::function; + /** * A task within a thread pool. A simple lambda that accepts no parameters and returns void. */ struct thread_pool_task { int priority; - std::function function; + work_unit function; }; struct thread_pool_task_comparator { diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 46e5086dd6..9c804a9075 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -176,6 +176,10 @@ cluster& cluster::set_websocket_protocol(websocket_protocol_t mode) { return *this; } +void cluster::queue_work(int priority, work_unit task) { + pool->enqueue({priority, task}); +} + void cluster::log(dpp::loglevel severity, const std::string &msg) const { if (!on_log.empty()) { /* Pass to user if they've hooked the event */ diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 3abe70ffa4..15b8fd24f2 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -455,7 +455,6 @@ void discord_client::one_second_timer() } websocket_client::one_second_timer(); - std::cout << "Shard tick\n"; /* This all only triggers if we are connected (have completed websocket, and received READY or RESUMED) */ if (this->is_connected()) { @@ -495,7 +494,6 @@ void discord_client::one_second_timer() if (this->heartbeat_interval && this->last_seq) { /* Check if we're due to emit a heartbeat */ if (time(nullptr) > last_heartbeat + ((heartbeat_interval / 1000.0) * 0.75)) { - std::cout << "Send ping\n"; last_ping_message = jsonobj_to_string(json({{"op", 1}, {"d", last_seq}})); queue_message(last_ping_message, true); last_heartbeat = time(nullptr); diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 47f05ee459..1a2f22e3d5 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -235,6 +235,9 @@ http_request_completion_t http_request::run(in_thread* processor, cluster* owner } else if (cli->get_status() < 100) { result.error = h_connection; owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Malformed HTTP response"); + } else if (cli->get_status() >= 400) { + owner->log(ll_error, "HTTP(S) error " + std::to_string(cli->get_status()) + " on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": " + cli->get_content()); + result.error = h_connection; } else { populate_result(_url, owner, result, *client); } @@ -254,14 +257,9 @@ http_request_completion_t http_request::run(in_thread* processor, cluster* owner processor->buckets[this->endpoint] = newbucket; /* Transfer it to completed requests */ - /* Make a new entry in the completion list and notify */ - auto hrc = std::make_unique(); - *hrc = result; - { - std::scoped_lock lock1(processor->requests->out_mutex); - processor->requests->responses_out.push({std::move(me), std::move(hrc)}); - } - processor->requests->out_ready.notify_one(); + owner->queue_work(0, [this, result]() { + complete(result); + }); } ); } @@ -278,10 +276,9 @@ void http_request::stash_self(std::unique_ptr self) { request_queue::request_queue(class cluster* owner, uint32_t request_threads) : creator(owner), terminating(false), globally_ratelimited(false), globally_limited_for(0), in_thread_pool_size(request_threads) { - for (up_t32_t in_alloc = 0; in_alloc < in_thread_pool_size; ++in_alloc) { + for (uint32_t in_alloc = 0; in_alloc < in_thread_pool_size; ++in_alloc) { requests_in.push_back(std::make_unique(owner, this, in_alloc)); } - out_thread = new std::thread(&request_queue::out_loop, this); } request_queue& request_queue::add_request_threads(uint32_t request_threads) @@ -319,12 +316,9 @@ void in_thread::terminate() request_queue::~request_queue() { terminating.store(true, std::memory_order_relaxed); - out_ready.notify_one(); for (auto& in_thr : requests_in) { in_thr->terminate(); // signal all of them here, otherwise they will all join 1 by 1 and it will take forever } - out_thread->join(); - delete out_thread; } void in_thread::in_loop(uint32_t index) @@ -413,51 +407,6 @@ void in_thread::in_loop(uint32_t index) } } -bool request_queue::queued_deleting_request::operator<(const queued_deleting_request& other) const noexcept { - return time_to_delete < other.time_to_delete; -} - -bool request_queue::queued_deleting_request::operator<(time_t time) const noexcept { - return time_to_delete < time; -} - - -void request_queue::out_loop() -{ - utility::set_thread_name("req_callback"); - while (!terminating.load(std::memory_order_relaxed)) { - - std::mutex mtx; - std::unique_lock lock{ mtx }; - out_ready.wait_for(lock, std::chrono::seconds(1)); - time_t now = time(nullptr); - - /* A request has been completed! */ - completed_request queue_head = {}; - { - std::scoped_lock lock1(out_mutex); - if (responses_out.size()) { - queue_head = std::move(responses_out.front()); - responses_out.pop(); - } - } - - if (queue_head.request && queue_head.response) { - queue_head.request->complete(*queue_head.response); - /* Queue deletions for 5 seconds from now */ - auto when = now + 5; - auto where = std::lower_bound(responses_to_delete.begin(), responses_to_delete.end(), when); - responses_to_delete.insert(where, {when, std::move(queue_head)}); - } - - /* Check for deletable items every second regardless of select status */ - auto end = std::lower_bound(responses_to_delete.begin(), responses_to_delete.end(), now); - if (end != responses_to_delete.begin()) { - responses_to_delete.erase(responses_to_delete.begin(), end); - } - } -} - /* Post a http_request into the queue */ void in_thread::post_request(std::unique_ptr req) { From 3917ec70c34152295c775d3359c4fd0b8b13a67c Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 15 Nov 2024 16:37:36 +0000 Subject: [PATCH 026/112] thread pooled events --- src/dpp/events/automod_rule_create.cpp | 4 +++- src/dpp/events/automod_rule_delete.cpp | 4 +++- src/dpp/events/automod_rule_execute.cpp | 4 +++- src/dpp/events/automod_rule_update.cpp | 4 +++- src/dpp/events/channel_create.cpp | 4 +++- src/dpp/events/channel_delete.cpp | 4 +++- src/dpp/events/channel_pins_update.cpp | 5 +++-- src/dpp/events/channel_update.cpp | 4 +++- src/dpp/events/entitlement_create.cpp | 4 +++- src/dpp/events/entitlement_delete.cpp | 4 +++- src/dpp/events/entitlement_update.cpp | 4 +++- .../events/guild_audit_log_entry_create.cpp | 4 +++- src/dpp/events/guild_ban_add.cpp | 4 +++- src/dpp/events/guild_ban_remove.cpp | 4 +++- src/dpp/events/guild_create.cpp | 4 +++- src/dpp/events/guild_delete.cpp | 4 +++- src/dpp/events/guild_emojis_update.cpp | 4 +++- src/dpp/events/guild_integrations_update.cpp | 4 +++- src/dpp/events/guild_join_request_delete.cpp | 4 +++- src/dpp/events/guild_member_add.cpp | 8 ++++++-- src/dpp/events/guild_member_remove.cpp | 4 +++- src/dpp/events/guild_member_update.cpp | 4 +++- src/dpp/events/guild_members_chunk.cpp | 4 +++- src/dpp/events/guild_role_create.cpp | 8 ++++++-- src/dpp/events/guild_role_delete.cpp | 8 ++++++-- src/dpp/voice/enabled/handle_frame.cpp | 20 ++++++++++++++----- src/dpp/voice/enabled/write_ready.cpp | 9 +++++++-- 27 files changed, 106 insertions(+), 36 deletions(-) diff --git a/src/dpp/events/automod_rule_create.cpp b/src/dpp/events/automod_rule_create.cpp index b31d5b9f5d..3ee13113aa 100644 --- a/src/dpp/events/automod_rule_create.cpp +++ b/src/dpp/events/automod_rule_create.cpp @@ -41,7 +41,9 @@ void automod_rule_create::handle(discord_client* client, json &j, const std::str json& d = j["d"]; automod_rule_create_t arc(client, raw); arc.created = automod_rule().fill_from_json(&d); - client->creator->on_automod_rule_create.call(arc); + client->creator->queue_work(0, [client, arc]() { + client->creator->on_automod_rule_create.call(arc); + }); } } diff --git a/src/dpp/events/automod_rule_delete.cpp b/src/dpp/events/automod_rule_delete.cpp index d8a0702390..9919d3705b 100644 --- a/src/dpp/events/automod_rule_delete.cpp +++ b/src/dpp/events/automod_rule_delete.cpp @@ -39,7 +39,9 @@ void automod_rule_delete::handle(discord_client* client, json &j, const std::str json& d = j["d"]; automod_rule_delete_t ard(client, raw); ard.deleted = automod_rule().fill_from_json(&d); - client->creator->on_automod_rule_delete.call(ard); + client->creator->queue_work(0, [client, ard]() { + client->creator->on_automod_rule_delete.call(ard); + }); } } diff --git a/src/dpp/events/automod_rule_execute.cpp b/src/dpp/events/automod_rule_execute.cpp index 4b4d6e4a16..482d9aa521 100644 --- a/src/dpp/events/automod_rule_execute.cpp +++ b/src/dpp/events/automod_rule_execute.cpp @@ -49,7 +49,9 @@ void automod_rule_execute::handle(discord_client* client, json &j, const std::st are.content = string_not_null(&d, "content"); are.matched_keyword = string_not_null(&d, "matched_keyword"); are.matched_content = string_not_null(&d, "matched_content"); - client->creator->on_automod_rule_execute.call(are); + client->creator->queue_work(0, [client, are]() { + client->creator->on_automod_rule_execute.call(are); + }); } } diff --git a/src/dpp/events/automod_rule_update.cpp b/src/dpp/events/automod_rule_update.cpp index 1e78541473..fa052e8979 100644 --- a/src/dpp/events/automod_rule_update.cpp +++ b/src/dpp/events/automod_rule_update.cpp @@ -39,7 +39,9 @@ void automod_rule_update::handle(discord_client* client, json &j, const std::str json& d = j["d"]; automod_rule_update_t aru(client, raw); aru.updated = automod_rule().fill_from_json(&d); - client->creator->on_automod_rule_update.call(aru); + client->creator->queue_work(0, [client, aru]() { + client->creator->on_automod_rule_update.call(aru); + }); } } diff --git a/src/dpp/events/channel_create.cpp b/src/dpp/events/channel_create.cpp index b252432c71..7d86a21929 100644 --- a/src/dpp/events/channel_create.cpp +++ b/src/dpp/events/channel_create.cpp @@ -70,7 +70,9 @@ void channel_create::handle(discord_client* client, json &j, const std::string & dpp::channel_create_t cc(client, raw); cc.created = c; cc.creating_guild = g; - client->creator->on_channel_create.call(cc); + client->creator->queue_work(1, [client, cc]() { + client->creator->on_channel_create.call(cc); + }); } } diff --git a/src/dpp/events/channel_delete.cpp b/src/dpp/events/channel_delete.cpp index 3009ff3f4a..e8238ed056 100644 --- a/src/dpp/events/channel_delete.cpp +++ b/src/dpp/events/channel_delete.cpp @@ -51,7 +51,9 @@ void channel_delete::handle(discord_client* client, json &j, const std::string & channel_delete_t cd(client, raw); cd.deleted = c; cd.deleting_guild = g; - client->creator->on_channel_delete.call(cd); + client->creator->queue_work(1, [client, cd]() { + client->creator->on_channel_delete.call(cd); + }); } } diff --git a/src/dpp/events/channel_pins_update.cpp b/src/dpp/events/channel_pins_update.cpp index 29ec7c2595..602ee7948e 100644 --- a/src/dpp/events/channel_pins_update.cpp +++ b/src/dpp/events/channel_pins_update.cpp @@ -43,8 +43,9 @@ void channel_pins_update::handle(discord_client* client, json &j, const std::str cpu.pin_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); cpu.pin_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); cpu.timestamp = ts_not_null(&d, "last_pin_timestamp"); - - client->creator->on_channel_pins_update.call(cpu); + client->creator->queue_work(0, [client, cpu]() { + client->creator->on_channel_pins_update.call(cpu); + }); } } diff --git a/src/dpp/events/channel_update.cpp b/src/dpp/events/channel_update.cpp index 33ecd580c0..ccfe812206 100644 --- a/src/dpp/events/channel_update.cpp +++ b/src/dpp/events/channel_update.cpp @@ -53,7 +53,9 @@ void channel_update::handle(discord_client* client, json &j, const std::string & dpp::channel_update_t cu(client, raw); cu.updated = c; cu.updating_guild = dpp::find_guild(c->guild_id); - client->creator->on_channel_update.call(cu); + client->creator->queue_work(1, [client, cu]() { + client->creator->on_channel_update.call(cu); + }); } } diff --git a/src/dpp/events/entitlement_create.cpp b/src/dpp/events/entitlement_create.cpp index d2ed6b71cc..a51803182b 100644 --- a/src/dpp/events/entitlement_create.cpp +++ b/src/dpp/events/entitlement_create.cpp @@ -41,7 +41,9 @@ void entitlement_create::handle(discord_client* client, json &j, const std::stri dpp::entitlement_create_t entitlement_event(client, raw); entitlement_event.created = ent; - client->creator->on_entitlement_create.call(entitlement_event); + client->creator->queue_work(0, [client, entitlement_event]() { + client->creator->on_entitlement_create.call(entitlement_event); + }); } } diff --git a/src/dpp/events/entitlement_delete.cpp b/src/dpp/events/entitlement_delete.cpp index 79f6f61f34..5c27f352a0 100644 --- a/src/dpp/events/entitlement_delete.cpp +++ b/src/dpp/events/entitlement_delete.cpp @@ -41,7 +41,9 @@ void entitlement_delete::handle(discord_client* client, json &j, const std::stri dpp::entitlement_delete_t entitlement_event(client, raw); entitlement_event.deleted = ent; - client->creator->on_entitlement_delete.call(entitlement_event); + client->creator->queue_work(0, [client, entitlement_event]() { + client->creator->on_entitlement_delete.call(entitlement_event); + }); } } diff --git a/src/dpp/events/entitlement_update.cpp b/src/dpp/events/entitlement_update.cpp index 648a9310d1..2a02396512 100644 --- a/src/dpp/events/entitlement_update.cpp +++ b/src/dpp/events/entitlement_update.cpp @@ -41,7 +41,9 @@ void entitlement_update::handle(discord_client* client, json &j, const std::stri dpp::entitlement_update_t entitlement_event(client, raw); entitlement_event.updating_entitlement = ent; - client->creator->on_entitlement_update.call(entitlement_event); + client->creator->queue_work(0, [client, entitlement_event]() { + client->creator->on_entitlement_update.call(entitlement_event); + }); } } diff --git a/src/dpp/events/guild_audit_log_entry_create.cpp b/src/dpp/events/guild_audit_log_entry_create.cpp index 131f5e4b9e..b172a5175b 100644 --- a/src/dpp/events/guild_audit_log_entry_create.cpp +++ b/src/dpp/events/guild_audit_log_entry_create.cpp @@ -37,7 +37,9 @@ void guild_audit_log_entry_create::handle(discord_client* client, json &j, const if (!client->creator->on_guild_audit_log_entry_create.empty()) { dpp::guild_audit_log_entry_create_t ec(client, raw); ec.entry.fill_from_json(&d); - client->creator->on_guild_audit_log_entry_create.call(ec); + client->creator->queue_work(2, [client, ec]() { + client->creator->on_guild_audit_log_entry_create.call(ec); + }); } } diff --git a/src/dpp/events/guild_ban_add.cpp b/src/dpp/events/guild_ban_add.cpp index 646656c996..e507140d93 100644 --- a/src/dpp/events/guild_ban_add.cpp +++ b/src/dpp/events/guild_ban_add.cpp @@ -44,7 +44,9 @@ void guild_ban_add::handle(discord_client* client, json &j, const std::string &r dpp::guild_ban_add_t gba(client, raw); gba.banning_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); gba.banned = dpp::user().fill_from_json(&(d["user"])); - client->creator->on_guild_ban_add.call(gba); + client->creator->queue_work(1, [client, gba]() { + client->creator->on_guild_ban_add.call(gba); + }); } } diff --git a/src/dpp/events/guild_ban_remove.cpp b/src/dpp/events/guild_ban_remove.cpp index 9d0d54d05b..23c967841d 100644 --- a/src/dpp/events/guild_ban_remove.cpp +++ b/src/dpp/events/guild_ban_remove.cpp @@ -44,7 +44,9 @@ void guild_ban_remove::handle(discord_client* client, json &j, const std::string dpp::guild_ban_remove_t gbr(client, raw); gbr.unbanning_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); gbr.unbanned = dpp::user().fill_from_json(&(d["user"])); - client->creator->on_guild_ban_remove.call(gbr); + client->creator->queue_work(1, [client, gbr]() { + client->creator->on_guild_ban_remove.call(gbr); + }); } } diff --git a/src/dpp/events/guild_create.cpp b/src/dpp/events/guild_create.cpp index 8e4ac63b18..354cce0a09 100644 --- a/src/dpp/events/guild_create.cpp +++ b/src/dpp/events/guild_create.cpp @@ -203,7 +203,9 @@ void guild_create::handle(discord_client* client, json &j, const std::string &ra } } - client->creator->on_guild_create.call(gc); + client->creator->queue_work(0, [client, gc]() { + client->creator->on_guild_create.call(gc); + }); } } diff --git a/src/dpp/events/guild_delete.cpp b/src/dpp/events/guild_delete.cpp index d8e01119ed..bcba7efc48 100644 --- a/src/dpp/events/guild_delete.cpp +++ b/src/dpp/events/guild_delete.cpp @@ -90,7 +90,9 @@ void guild_delete::handle(discord_client* client, json &j, const std::string &ra dpp::guild_delete_t gd(client, raw); gd.deleted = guild_del; gd.guild_id = guild_del.id; - client->creator->on_guild_delete.call(gd); + client->creator->queue_work(0, [client, gd]() { + client->creator->on_guild_delete.call(gd); + }); } } diff --git a/src/dpp/events/guild_emojis_update.cpp b/src/dpp/events/guild_emojis_update.cpp index 9d4a6676bc..57977e29b5 100644 --- a/src/dpp/events/guild_emojis_update.cpp +++ b/src/dpp/events/guild_emojis_update.cpp @@ -74,7 +74,9 @@ void guild_emojis_update::handle(discord_client* client, json &j, const std::str dpp::guild_emojis_update_t geu(client, raw); geu.emojis = emojis; geu.updating_guild = g; - client->creator->on_guild_emojis_update.call(geu); + client->creator->queue_work(1, [client, geu]() { + client->creator->on_guild_emojis_update.call(geu); + }); } } diff --git a/src/dpp/events/guild_integrations_update.cpp b/src/dpp/events/guild_integrations_update.cpp index 72346c2ee6..54feea42d9 100644 --- a/src/dpp/events/guild_integrations_update.cpp +++ b/src/dpp/events/guild_integrations_update.cpp @@ -41,7 +41,9 @@ void guild_integrations_update::handle(class discord_client* client, json &j, co json& d = j["d"]; dpp::guild_integrations_update_t giu(client, raw); giu.updating_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); - client->creator->on_guild_integrations_update.call(giu); + client->creator->queue_work(1, [client, giu]() { + client->creator->on_guild_integrations_update.call(giu); + }); } } diff --git a/src/dpp/events/guild_join_request_delete.cpp b/src/dpp/events/guild_join_request_delete.cpp index f349a5756a..9822aff0a0 100644 --- a/src/dpp/events/guild_join_request_delete.cpp +++ b/src/dpp/events/guild_join_request_delete.cpp @@ -41,7 +41,9 @@ void guild_join_request_delete::handle(class discord_client* client, json &j, co dpp::guild_join_request_delete_t grd(client, raw); grd.user_id = snowflake_not_null(&d, "user_id"); grd.guild_id = snowflake_not_null(&d, "guild_id"); - client->creator->on_guild_join_request_delete.call(grd); + client->creator->queue_work(1, [client, grd]() { + client->creator->on_guild_join_request_delete.call(grd); + }); } } diff --git a/src/dpp/events/guild_member_add.cpp b/src/dpp/events/guild_member_add.cpp index d253174f42..78e4b72c5a 100644 --- a/src/dpp/events/guild_member_add.cpp +++ b/src/dpp/events/guild_member_add.cpp @@ -47,7 +47,9 @@ void guild_member_add::handle(discord_client* client, json &j, const std::string gmr.added = gm; if (!client->creator->on_guild_member_add.empty()) { gmr.adding_guild = g; - client->creator->on_guild_member_add.call(gmr); + client->creator->queue_work(1, [client, gmr]() { + client->creator->on_guild_member_add.call(gmr); + }); } } else { dpp::user* u = dpp::find_user(snowflake_not_null(&(d["user"]), "id")); @@ -69,7 +71,9 @@ void guild_member_add::handle(discord_client* client, json &j, const std::string } if (!client->creator->on_guild_member_add.empty()) { gmr.adding_guild = g; - client->creator->on_guild_member_add.call(gmr); + client->creator->queue_work(1, [client, gmr]() { + client->creator->on_guild_member_add.call(gmr); + }); } } } diff --git a/src/dpp/events/guild_member_remove.cpp b/src/dpp/events/guild_member_remove.cpp index 9ac3e5c8bc..e376275fab 100644 --- a/src/dpp/events/guild_member_remove.cpp +++ b/src/dpp/events/guild_member_remove.cpp @@ -45,7 +45,9 @@ void guild_member_remove::handle(discord_client* client, json &j, const std::str gmr.removing_guild = dpp::find_guild(gmr.guild_id); if (!client->creator->on_guild_member_remove.empty()) { - client->creator->on_guild_member_remove.call(gmr); + client->creator->queue_work(1, [client, gmr]() { + client->creator->on_guild_member_remove.call(gmr); + }); } if (client->creator->cache_policy.user_policy != dpp::cp_none && gmr.removing_guild) { diff --git a/src/dpp/events/guild_member_update.cpp b/src/dpp/events/guild_member_update.cpp index cca7422f3a..022980ef08 100644 --- a/src/dpp/events/guild_member_update.cpp +++ b/src/dpp/events/guild_member_update.cpp @@ -51,7 +51,9 @@ void guild_member_update::handle(discord_client* client, json &j, const std::str m.fill_from_json(&user, guild_id, u.id); gmu.updated = m; } - client->creator->on_guild_member_update.call(gmu); + client->creator->queue_work(1, [client, gmu]() { + client->creator->on_guild_member_update.call(gmu); + }); } else { dpp::user* u = dpp::find_user(from_string(d["user"]["id"].get())); if (u) { diff --git a/src/dpp/events/guild_members_chunk.cpp b/src/dpp/events/guild_members_chunk.cpp index a5c4be9f8b..32da895e82 100644 --- a/src/dpp/events/guild_members_chunk.cpp +++ b/src/dpp/events/guild_members_chunk.cpp @@ -67,7 +67,9 @@ void guild_members_chunk::handle(discord_client* client, json &j, const std::str dpp::guild_members_chunk_t gmc(client, raw); gmc.adding = g; gmc.members = &um; - client->creator->on_guild_members_chunk.call(gmc); + client->creator->queue_work(1, [client, gmc]() { + client->creator->on_guild_members_chunk.call(gmc); + }); } } diff --git a/src/dpp/events/guild_role_create.cpp b/src/dpp/events/guild_role_create.cpp index a3780da3ea..8e4a692837 100644 --- a/src/dpp/events/guild_role_create.cpp +++ b/src/dpp/events/guild_role_create.cpp @@ -49,7 +49,9 @@ void guild_role_create::handle(discord_client* client, json &j, const std::strin dpp::guild_role_create_t grc(client, raw); grc.creating_guild = g; grc.created = &r; - client->creator->on_guild_role_create.call(grc); + client->creator->queue_work(1, [client, grc]() { + client->creator->on_guild_role_create.call(grc); + }); } } else { json &role = d["role"]; @@ -66,7 +68,9 @@ void guild_role_create::handle(discord_client* client, json &j, const std::strin dpp::guild_role_create_t grc(client, raw); grc.creating_guild = g; grc.created = r; - client->creator->on_guild_role_create.call(grc); + client->creator->queue_work(1, [client, grc]() { + client->creator->on_guild_role_create.call(grc); + }); } } } diff --git a/src/dpp/events/guild_role_delete.cpp b/src/dpp/events/guild_role_delete.cpp index a32628ead0..f6e707acc2 100644 --- a/src/dpp/events/guild_role_delete.cpp +++ b/src/dpp/events/guild_role_delete.cpp @@ -48,7 +48,9 @@ void guild_role_delete::handle(discord_client* client, json &j, const std::strin grd.deleting_guild = g; grd.role_id = role_id; grd.deleted = nullptr; - client->creator->on_guild_role_delete.call(grd); + client->creator->queue_work(1, [client, grd]() { + client->creator->on_guild_role_delete.call(grd); + }); } } else { dpp::role *r = dpp::find_role(role_id); @@ -57,7 +59,9 @@ void guild_role_delete::handle(discord_client* client, json &j, const std::strin grd.deleting_guild = g; grd.deleted = r; grd.role_id = role_id; - client->creator->on_guild_role_delete.call(grd); + client->creator->queue_work(1, [client, grd]() { + client->creator->on_guild_role_delete.call(grd); + }); } if (r) { if (g) { diff --git a/src/dpp/voice/enabled/handle_frame.cpp b/src/dpp/voice/enabled/handle_frame.cpp index 2a8faca93c..bfd85da758 100644 --- a/src/dpp/voice/enabled/handle_frame.cpp +++ b/src/dpp/voice/enabled/handle_frame.cpp @@ -208,7 +208,9 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod vcp.voice_client = this; vcp.user_id = snowflake_not_null(&j["d"], "user_id"); vcp.platform = static_cast(int8_not_null(&j["d"], "platform")); - creator->on_voice_client_platform.call(vcp); + creator->queue_work(0, [this, vcp]() { + creator->on_voice_client_platform.call(vcp); + }); } break; case voice_opcode_multiple_clients_connect: { @@ -303,7 +305,9 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod voice_client_disconnect_t vcd(nullptr, data); vcd.voice_client = this; vcd.user_id = u_id; - creator->on_voice_client_disconnect.call(vcd); + creator->queue_work(0, [this, vcd]() { + creator->on_voice_client_disconnect.call(vcd); + }); } } } @@ -322,7 +326,9 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod vcs.voice_client = this; vcs.user_id = u_id; vcs.ssrc = u_ssrc; - creator->on_voice_client_speaking.call(vcs); + creator->queue_work(0, [this, vcs]() { + creator->on_voice_client_speaking.call(vcs); + }); } } } @@ -419,7 +425,9 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod voice_ready_t rdy(nullptr, data); rdy.voice_client = this; rdy.voice_channel_id = this->channel_id; - creator->on_voice_ready.call(rdy); + creator->queue_work(0, [this, rdy]() { + creator->on_voice_ready.call(rdy); + }); } } } @@ -521,7 +529,9 @@ void discord_voice_client::ready_for_transition(const std::string &data) { voice_ready_t rdy(nullptr, data); rdy.voice_client = this; rdy.voice_channel_id = this->channel_id; - creator->on_voice_ready.call(rdy); + creator->queue_work(0, [this, rdy]() { + creator->on_voice_ready.call(rdy); + }); } } } diff --git a/src/dpp/voice/enabled/write_ready.cpp b/src/dpp/voice/enabled/write_ready.cpp index 8287dea2a3..ade6b20716 100644 --- a/src/dpp/voice/enabled/write_ready.cpp +++ b/src/dpp/voice/enabled/write_ready.cpp @@ -99,7 +99,9 @@ void discord_voice_client::write_ready() { snd.buffer_size = bufsize; snd.packets_left = outbuf.size(); snd.voice_client = this; - creator->on_voice_buffer_send.call(snd); + creator->queue_work(-1, [this, snd]() { + creator->on_voice_buffer_send.call(snd); + }); } } if (track_marker_found) { @@ -113,7 +115,10 @@ void discord_voice_client::write_ready() { track_meta.erase(track_meta.begin()); } } - creator->on_voice_track_marker.call(vtm); + creator->queue_work(-1, [this, vtm]() { + creator->on_voice_track_marker.call(vtm); + }); + } } } From 67dc0b703e6255483b587cfd4e8cdac89f999d19 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 14:04:58 +0000 Subject: [PATCH 027/112] put websocket events into the work queue --- src/dpp/events/guild_role_update.cpp | 8 +++-- .../events/guild_scheduled_event_create.cpp | 4 ++- .../events/guild_scheduled_event_delete.cpp | 4 ++- .../events/guild_scheduled_event_update.cpp | 4 ++- .../events/guild_scheduled_event_user_add.cpp | 4 ++- .../guild_scheduled_event_user_remove.cpp | 4 ++- src/dpp/events/guild_stickers_update.cpp | 4 ++- src/dpp/events/guild_update.cpp | 4 ++- src/dpp/events/integration_create.cpp | 4 ++- src/dpp/events/integration_delete.cpp | 4 ++- src/dpp/events/integration_update.cpp | 4 ++- src/dpp/events/interaction_create.cpp | 32 ++++++++++++++----- src/dpp/events/invite_create.cpp | 4 ++- src/dpp/events/invite_delete.cpp | 4 ++- src/dpp/events/message_create.cpp | 4 ++- src/dpp/events/message_delete.cpp | 4 ++- src/dpp/events/message_delete_bulk.cpp | 4 ++- src/dpp/events/message_poll_vote_add.cpp | 4 ++- src/dpp/events/message_poll_vote_remove.cpp | 4 ++- src/dpp/events/message_reaction_add.cpp | 4 ++- src/dpp/events/message_reaction_remove.cpp | 4 ++- .../events/message_reaction_remove_all.cpp | 4 ++- .../events/message_reaction_remove_emoji.cpp | 4 ++- src/dpp/events/message_update.cpp | 4 ++- src/dpp/events/presence_update.cpp | 4 ++- src/dpp/events/ready.cpp | 4 ++- src/dpp/events/resumed.cpp | 4 ++- src/dpp/events/stage_instance_create.cpp | 4 ++- src/dpp/events/stage_instance_delete.cpp | 4 ++- src/dpp/events/stage_instance_update.cpp | 4 ++- src/dpp/events/thread_create.cpp | 4 ++- src/dpp/events/thread_delete.cpp | 4 ++- src/dpp/events/thread_list_sync.cpp | 4 ++- src/dpp/events/thread_member_update.cpp | 4 ++- src/dpp/events/thread_members_update.cpp | 4 ++- src/dpp/events/thread_update.cpp | 4 ++- src/dpp/events/typing_start.cpp | 4 ++- src/dpp/events/user_update.cpp | 8 +++-- src/dpp/events/voice_server_update.cpp | 4 ++- src/dpp/events/voice_state_update.cpp | 4 ++- src/dpp/events/webhooks_update.cpp | 4 ++- 41 files changed, 150 insertions(+), 50 deletions(-) diff --git a/src/dpp/events/guild_role_update.cpp b/src/dpp/events/guild_role_update.cpp index 83a348e271..6f8ca5a691 100644 --- a/src/dpp/events/guild_role_update.cpp +++ b/src/dpp/events/guild_role_update.cpp @@ -47,7 +47,9 @@ void guild_role_update::handle(discord_client* client, json &j, const std::strin dpp::guild_role_update_t gru(client, raw); gru.updating_guild = g; gru.updated = &r; - client->creator->on_guild_role_update.call(gru); + client->creator->queue_work(1, [client, gru]() { + client->creator->on_guild_role_update.call(gru); + }); } } else { json& role = d["role"]; @@ -58,7 +60,9 @@ void guild_role_update::handle(discord_client* client, json &j, const std::strin dpp::guild_role_update_t gru(client, raw); gru.updating_guild = g; gru.updated = r; - client->creator->on_guild_role_update.call(gru); + client->creator->queue_work(1, [client, gru]() { + client->creator->on_guild_role_update.call(gru); + }); } } } diff --git a/src/dpp/events/guild_scheduled_event_create.cpp b/src/dpp/events/guild_scheduled_event_create.cpp index 15414756a9..0ada5b1c3e 100644 --- a/src/dpp/events/guild_scheduled_event_create.cpp +++ b/src/dpp/events/guild_scheduled_event_create.cpp @@ -41,7 +41,9 @@ void guild_scheduled_event_create::handle(discord_client* client, json &j, const if (!client->creator->on_guild_scheduled_event_create.empty()) { dpp::guild_scheduled_event_create_t ec(client, raw); ec.created.fill_from_json(&d); - client->creator->on_guild_scheduled_event_create.call(ec); + client->creator->queue_work(1, [client, ec]() { + client->creator->on_guild_scheduled_event_create.call(ec); + }); } } diff --git a/src/dpp/events/guild_scheduled_event_delete.cpp b/src/dpp/events/guild_scheduled_event_delete.cpp index 4552743d86..0db680efbf 100644 --- a/src/dpp/events/guild_scheduled_event_delete.cpp +++ b/src/dpp/events/guild_scheduled_event_delete.cpp @@ -42,7 +42,9 @@ void guild_scheduled_event_delete::handle(discord_client* client, json &j, const if (!client->creator->on_guild_scheduled_event_delete.empty()) { dpp::guild_scheduled_event_delete_t ed(client, raw); ed.deleted.fill_from_json(&d); - client->creator->on_guild_scheduled_event_delete.call(ed); + client->creator->queue_work(1, [client, ed]() { + client->creator->on_guild_scheduled_event_delete.call(ed); + }); } } diff --git a/src/dpp/events/guild_scheduled_event_update.cpp b/src/dpp/events/guild_scheduled_event_update.cpp index 7f297879a7..87affe33fc 100644 --- a/src/dpp/events/guild_scheduled_event_update.cpp +++ b/src/dpp/events/guild_scheduled_event_update.cpp @@ -42,7 +42,9 @@ void guild_scheduled_event_update::handle(discord_client* client, json &j, const if (!client->creator->on_guild_scheduled_event_update.empty()) { dpp::guild_scheduled_event_update_t eu(client, raw); eu.updated.fill_from_json(&d); - client->creator->on_guild_scheduled_event_update.call(eu); + client->creator->queue_work(1, [client, eu]() { + client->creator->on_guild_scheduled_event_update.call(eu); + }); } } diff --git a/src/dpp/events/guild_scheduled_event_user_add.cpp b/src/dpp/events/guild_scheduled_event_user_add.cpp index deecd3ff3f..714d4f4c1a 100644 --- a/src/dpp/events/guild_scheduled_event_user_add.cpp +++ b/src/dpp/events/guild_scheduled_event_user_add.cpp @@ -43,7 +43,9 @@ void guild_scheduled_event_user_add::handle(discord_client* client, json &j, con eua.guild_id = snowflake_not_null(&d, "guild_id"); eua.user_id = snowflake_not_null(&d, "user_id"); eua.event_id = snowflake_not_null(&d, "guild_scheduled_event_id"); - client->creator->on_guild_scheduled_event_user_add.call(eua); + client->creator->queue_work(1, [client, eua]() { + client->creator->on_guild_scheduled_event_user_add.call(eua); + }); } } diff --git a/src/dpp/events/guild_scheduled_event_user_remove.cpp b/src/dpp/events/guild_scheduled_event_user_remove.cpp index 64e4d31106..685bd5cfcc 100644 --- a/src/dpp/events/guild_scheduled_event_user_remove.cpp +++ b/src/dpp/events/guild_scheduled_event_user_remove.cpp @@ -43,7 +43,9 @@ void guild_scheduled_event_user_remove::handle(discord_client* client, json &j, eur.guild_id = snowflake_not_null(&d, "guild_id"); eur.user_id = snowflake_not_null(&d, "user_id"); eur.event_id = snowflake_not_null(&d, "guild_scheduled_event_id"); - client->creator->on_guild_scheduled_event_user_remove.call(eur); + client->creator->queue_work(1, [client, eur]() { + client->creator->on_guild_scheduled_event_user_remove.call(eur); + }); } } diff --git a/src/dpp/events/guild_stickers_update.cpp b/src/dpp/events/guild_stickers_update.cpp index d6b4e16237..5bf7831c0b 100644 --- a/src/dpp/events/guild_stickers_update.cpp +++ b/src/dpp/events/guild_stickers_update.cpp @@ -49,7 +49,9 @@ void guild_stickers_update::handle(discord_client* client, json &j, const std::s gsu.stickers.emplace_back(s); } gsu.updating_guild = g; - client->creator->on_guild_stickers_update.call(gsu); + client->creator->queue_work(1, [client, gsu]() { + client->creator->on_guild_stickers_update.call(gsu); + }); } } diff --git a/src/dpp/events/guild_update.cpp b/src/dpp/events/guild_update.cpp index 4f96fa698e..0b75360076 100644 --- a/src/dpp/events/guild_update.cpp +++ b/src/dpp/events/guild_update.cpp @@ -67,7 +67,9 @@ void guild_update::handle(discord_client* client, json &j, const std::string &ra if (!client->creator->on_guild_update.empty()) { dpp::guild_update_t gu(client, raw); gu.updated = g; - client->creator->on_guild_update.call(gu); + client->creator->queue_work(1, [client, gu]() { + client->creator->on_guild_update.call(gu); + }); } } diff --git a/src/dpp/events/integration_create.cpp b/src/dpp/events/integration_create.cpp index 13b475ab9d..b7101482a7 100644 --- a/src/dpp/events/integration_create.cpp +++ b/src/dpp/events/integration_create.cpp @@ -42,7 +42,9 @@ void integration_create::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; dpp::integration_create_t ic(client, raw); ic.created_integration = dpp::integration().fill_from_json(&d); - client->creator->on_integration_create.call(ic); + client->creator->queue_work(1, [client, ic]() { + client->creator->on_integration_create.call(ic); + }); } } diff --git a/src/dpp/events/integration_delete.cpp b/src/dpp/events/integration_delete.cpp index 8dce94a072..94a6acde24 100644 --- a/src/dpp/events/integration_delete.cpp +++ b/src/dpp/events/integration_delete.cpp @@ -41,7 +41,9 @@ void integration_delete::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; dpp::integration_delete_t id(client, raw); id.deleted_integration = dpp::integration().fill_from_json(&d); - client->creator->on_integration_delete.call(id); + client->creator->queue_work(1, [client, id]() { + client->creator->on_integration_delete.call(id); + }); } } diff --git a/src/dpp/events/integration_update.cpp b/src/dpp/events/integration_update.cpp index aad1ab66c8..9fed3bb708 100644 --- a/src/dpp/events/integration_update.cpp +++ b/src/dpp/events/integration_update.cpp @@ -41,7 +41,9 @@ void integration_update::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; dpp::integration_update_t iu(client, raw); iu.updated_integration = dpp::integration().fill_from_json(&d); - client->creator->on_integration_update.call(iu); + client->creator->queue_work(1, [client, iu]() { + client->creator->on_integration_update.call(iu); + }); } } diff --git a/src/dpp/events/interaction_create.cpp b/src/dpp/events/interaction_create.cpp index 37919206ce..6c46349a7f 100644 --- a/src/dpp/events/interaction_create.cpp +++ b/src/dpp/events/interaction_create.cpp @@ -102,7 +102,9 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri message_context_menu_t mcm(client, raw); mcm.command = i; mcm.set_message(i.resolved.messages.begin()->second); - client->creator->on_message_context_menu.call(mcm); + client->creator->queue_work(1, [client, mcm]() { + client->creator->on_message_context_menu.call(mcm); + }); } } else if (cmd_data.type == ctxm_user && !client->creator->on_user_context_menu.empty()) { if (i.resolved.users.size()) { @@ -110,12 +112,16 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri user_context_menu_t ucm(client, raw); ucm.command = i; ucm.set_user(i.resolved.users.begin()->second); - client->creator->on_user_context_menu.call(ucm); + client->creator->queue_work(1, [client, ucm]() { + client->creator->on_user_context_menu.call(ucm); + }); } } else if (cmd_data.type == ctxm_chat_input && !client->creator->on_slashcommand.empty()) { dpp::slashcommand_t sc(client, raw); sc.command = i; - client->creator->on_slashcommand.call(sc); + client->creator->queue_work(1, [client, sc]() { + client->creator->on_slashcommand.call(sc); + }); } if (!client->creator->on_interaction_create.empty()) { /* Standard chat input. Note that for backwards compatibility, context menu @@ -124,7 +130,9 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri */ dpp::interaction_create_t ic(client, raw); ic.command = i; - client->creator->on_interaction_create.call(ic); + client->creator->queue_work(1, [client, ic]() { + client->creator->on_interaction_create.call(ic); + }); } } else if (i.type == it_modal_submit) { if (!client->creator->on_form_submit.empty()) { @@ -134,7 +142,9 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri for (auto & c : d["data"]["components"]) { fs.components.push_back(dpp::component().fill_from_json(&c)); } - client->creator->on_form_submit.call(fs); + client->creator->queue_work(1, [client, fs]() { + client->creator->on_form_submit.call(fs); + }); } } else if (i.type == it_autocomplete) { // "data":{"id":"903319628816728104","name":"blep","options":[{"focused":true,"name":"animal","type":3,"value":"a"}],"type":1} @@ -144,7 +154,9 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri ac.name = string_not_null(&(d["data"]), "name"); fill_options(d["data"]["options"], ac.options); ac.command = i; - client->creator->on_autocomplete.call(ac); + client->creator->queue_work(1, [client, ac]() { + client->creator->on_autocomplete.call(ac); + }); } } else if (i.type == it_component_button) { dpp::component_interaction bi = std::get(i.data); @@ -154,7 +166,9 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri ic.command = i; ic.custom_id = bi.custom_id; ic.component_type = bi.component_type; - client->creator->on_button_click.call(ic); + client->creator->queue_work(1, [client, ic]() { + client->creator->on_button_click.call(ic); + }); } } else if (bi.component_type == cot_selectmenu || bi.component_type == cot_user_selectmenu || bi.component_type == cot_role_selectmenu || bi.component_type == cot_mentionable_selectmenu || @@ -165,7 +179,9 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri ic.custom_id = bi.custom_id; ic.component_type = bi.component_type; ic.values = bi.values; - client->creator->on_select_click.call(ic); + client->creator->queue_work(1, [client, ic]() { + client->creator->on_select_click.call(ic); + }); } } } diff --git a/src/dpp/events/invite_create.cpp b/src/dpp/events/invite_create.cpp index 626ac857e8..dccdcf377d 100644 --- a/src/dpp/events/invite_create.cpp +++ b/src/dpp/events/invite_create.cpp @@ -41,7 +41,9 @@ void invite_create::handle(discord_client* client, json &j, const std::string &r json& d = j["d"]; dpp::invite_create_t ci(client, raw); ci.created_invite = dpp::invite().fill_from_json(&d); - client->creator->on_invite_create.call(ci); + client->creator->queue_work(1, [client, ci]() { + client->creator->on_invite_create.call(ci); + }); } } diff --git a/src/dpp/events/invite_delete.cpp b/src/dpp/events/invite_delete.cpp index bd121f6105..cb9eb5acf2 100644 --- a/src/dpp/events/invite_delete.cpp +++ b/src/dpp/events/invite_delete.cpp @@ -41,7 +41,9 @@ void invite_delete::handle(discord_client* client, json &j, const std::string &r json& d = j["d"]; dpp::invite_delete_t cd(client, raw); cd.deleted_invite = dpp::invite().fill_from_json(&d); - client->creator->on_invite_delete.call(cd); + client->creator->queue_work(1, [client, cd]() { + client->creator->on_invite_delete.call(cd); + }); } } diff --git a/src/dpp/events/message_create.cpp b/src/dpp/events/message_create.cpp index a1ce729b7b..e017438ab8 100644 --- a/src/dpp/events/message_create.cpp +++ b/src/dpp/events/message_create.cpp @@ -43,7 +43,9 @@ void message_create::handle(discord_client* client, json &j, const std::string & dpp::message_create_t msg(client, raw); msg.msg.fill_from_json(&d, client->creator->cache_policy); msg.msg.owner = client->creator; - client->creator->on_message_create.call(msg); + client->creator->queue_work(1, [client, msg]() { + client->creator->on_message_create.call(msg); + }); } } diff --git a/src/dpp/events/message_delete.cpp b/src/dpp/events/message_delete.cpp index 60f0e97d81..c76f5fc5a1 100644 --- a/src/dpp/events/message_delete.cpp +++ b/src/dpp/events/message_delete.cpp @@ -43,7 +43,9 @@ void message_delete::handle(discord_client* client, json &j, const std::string & msg.id = snowflake_not_null(&d, "id"); msg.guild_id = snowflake_not_null(&d, "guild_id"); msg.channel_id = snowflake_not_null(&d, "channel_id"); - client->creator->on_message_delete.call(msg); + client->creator->queue_work(1, [client, msg]() { + client->creator->on_message_delete.call(msg); + }); } } diff --git a/src/dpp/events/message_delete_bulk.cpp b/src/dpp/events/message_delete_bulk.cpp index 394d408fb2..554d4f6f59 100644 --- a/src/dpp/events/message_delete_bulk.cpp +++ b/src/dpp/events/message_delete_bulk.cpp @@ -44,7 +44,9 @@ void message_delete_bulk::handle(discord_client* client, json &j, const std::str for (auto& m : d["ids"]) { msg.deleted.push_back(from_string(m.get())); } - client->creator->on_message_delete_bulk.call(msg); + client->creator->queue_work(1, [client, msg]() { + client->creator->on_message_delete_bulk.call(msg); + }); } } diff --git a/src/dpp/events/message_poll_vote_add.cpp b/src/dpp/events/message_poll_vote_add.cpp index 319d809e9c..de957e9f41 100644 --- a/src/dpp/events/message_poll_vote_add.cpp +++ b/src/dpp/events/message_poll_vote_add.cpp @@ -46,7 +46,9 @@ void message_poll_vote_add::handle(discord_client* client, json &j, const std::s vote.channel_id = snowflake_not_null(&j, "channel_id"); vote.guild_id = snowflake_not_null(&j, "guild_id"); vote.answer_id = int32_not_null(&j, "answer_id"); - client->creator->on_message_poll_vote_add.call(vote); + client->creator->queue_work(1, [client, vote]() { + client->creator->on_message_poll_vote_add.call(vote); + }); } } diff --git a/src/dpp/events/message_poll_vote_remove.cpp b/src/dpp/events/message_poll_vote_remove.cpp index 55c243e767..3d4cb0eb13 100644 --- a/src/dpp/events/message_poll_vote_remove.cpp +++ b/src/dpp/events/message_poll_vote_remove.cpp @@ -46,7 +46,9 @@ void message_poll_vote_remove::handle(discord_client* client, json &j, const std vote.channel_id = snowflake_not_null(&j, "channel_id"); vote.guild_id = snowflake_not_null(&j, "guild_id"); vote.answer_id = int32_not_null(&j, "answer_id"); - client->creator->on_message_poll_vote_remove.call(vote); + client->creator->queue_work(1, [client, vote]() { + client->creator->on_message_poll_vote_remove.call(vote); + }); } } diff --git a/src/dpp/events/message_reaction_add.cpp b/src/dpp/events/message_reaction_add.cpp index 88a2d05608..ccb28ace04 100644 --- a/src/dpp/events/message_reaction_add.cpp +++ b/src/dpp/events/message_reaction_add.cpp @@ -50,7 +50,9 @@ void message_reaction_add::handle(discord_client* client, json &j, const std::st mra.message_author_id = snowflake_not_null(&d, "message_author_id"); mra.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mra.channel_id && mra.message_id) { - client->creator->on_message_reaction_add.call(mra); + client->creator->queue_work(1, [client, mra]() { + client->creator->on_message_reaction_add.call(mra); + }); } } } diff --git a/src/dpp/events/message_reaction_remove.cpp b/src/dpp/events/message_reaction_remove.cpp index 36b367c9cb..794f84c583 100644 --- a/src/dpp/events/message_reaction_remove.cpp +++ b/src/dpp/events/message_reaction_remove.cpp @@ -48,7 +48,9 @@ void message_reaction_remove::handle(discord_client* client, json &j, const std: mrr.message_id = snowflake_not_null(&d, "message_id"); mrr.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mrr.channel_id && mrr.message_id) { - client->creator->on_message_reaction_remove.call(mrr); + client->creator->queue_work(1, [client, mrr]() { + client->creator->on_message_reaction_remove.call(mrr); + }); } } } diff --git a/src/dpp/events/message_reaction_remove_all.cpp b/src/dpp/events/message_reaction_remove_all.cpp index 71fa31b873..dd0ae2de61 100644 --- a/src/dpp/events/message_reaction_remove_all.cpp +++ b/src/dpp/events/message_reaction_remove_all.cpp @@ -45,7 +45,9 @@ void message_reaction_remove_all::handle(discord_client* client, json &j, const mrra.reacting_channel = dpp::find_channel(mrra.channel_id); mrra.message_id = snowflake_not_null(&d, "message_id"); if (mrra.channel_id && mrra.message_id) { - client->creator->on_message_reaction_remove_all.call(mrra); + client->creator->queue_work(1, [client, mrra]() { + client->creator->on_message_reaction_remove_all.call(mrra); + }); } } } diff --git a/src/dpp/events/message_reaction_remove_emoji.cpp b/src/dpp/events/message_reaction_remove_emoji.cpp index 97002b4992..4de8ae7326 100644 --- a/src/dpp/events/message_reaction_remove_emoji.cpp +++ b/src/dpp/events/message_reaction_remove_emoji.cpp @@ -46,7 +46,9 @@ void message_reaction_remove_emoji::handle(discord_client* client, json &j, cons mrre.message_id = snowflake_not_null(&d, "message_id"); mrre.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mrre.channel_id && mrre.message_id) { - client->creator->on_message_reaction_remove_emoji.call(mrre); + client->creator->queue_work(1, [client, mrre]() { + client->creator->on_message_reaction_remove_emoji.call(mrre); + }); } } diff --git a/src/dpp/events/message_update.cpp b/src/dpp/events/message_update.cpp index 4b6490a1f2..9f3408dba0 100644 --- a/src/dpp/events/message_update.cpp +++ b/src/dpp/events/message_update.cpp @@ -43,7 +43,9 @@ void message_update::handle(discord_client* client, json &j, const std::string & dpp::message m(client->creator); m.fill_from_json(&d); msg.msg = m; - client->creator->on_message_update.call(msg); + client->creator->queue_work(1, [client, msg]() { + client->creator->on_message_update.call(msg); + }); } } diff --git a/src/dpp/events/presence_update.cpp b/src/dpp/events/presence_update.cpp index 4238efb378..24ab26d111 100644 --- a/src/dpp/events/presence_update.cpp +++ b/src/dpp/events/presence_update.cpp @@ -40,7 +40,9 @@ void presence_update::handle(discord_client* client, json &j, const std::string json& d = j["d"]; dpp::presence_update_t pu(client, raw); pu.rich_presence = dpp::presence().fill_from_json(&d); - client->creator->on_presence_update.call(pu); + client->creator->queue_work(1, [client, pu]() { + client->creator->on_presence_update.call(pu); + }); } } diff --git a/src/dpp/events/ready.cpp b/src/dpp/events/ready.cpp index 7990293d98..b319b91f69 100644 --- a/src/dpp/events/ready.cpp +++ b/src/dpp/events/ready.cpp @@ -75,7 +75,9 @@ void ready::handle(discord_client* client, json &j, const std::string &raw) { r.guilds.emplace_back(snowflake_not_null(&guild, "id")); } r.guild_count = r.guilds.size(); - client->creator->on_ready.call(r); + client->creator->queue_work(1, [client, r]() { + client->creator->on_ready.call(r); + }); } } diff --git a/src/dpp/events/resumed.cpp b/src/dpp/events/resumed.cpp index b70db335ef..4fbe77023a 100644 --- a/src/dpp/events/resumed.cpp +++ b/src/dpp/events/resumed.cpp @@ -44,7 +44,9 @@ void resumed::handle(discord_client* client, json &j, const std::string &raw) { dpp::resumed_t r(client, raw); r.session_id = client->sessionid; r.shard_id = client->shard_id; - client->creator->on_resumed.call(r); + client->creator->queue_work(1, [client, r]() { + client->creator->on_resumed.call(r); + }); } } diff --git a/src/dpp/events/stage_instance_create.cpp b/src/dpp/events/stage_instance_create.cpp index fbbc3099e5..47f50382a5 100644 --- a/src/dpp/events/stage_instance_create.cpp +++ b/src/dpp/events/stage_instance_create.cpp @@ -41,7 +41,9 @@ void stage_instance_create::handle(discord_client* client, json &j, const std::s json& d = j["d"]; dpp::stage_instance_create_t sic(client, raw); sic.created.fill_from_json(&d); - client->creator->on_stage_instance_create.call(sic); + client->creator->queue_work(1, [client, sic]() { + client->creator->on_stage_instance_create.call(sic); + }); } } diff --git a/src/dpp/events/stage_instance_delete.cpp b/src/dpp/events/stage_instance_delete.cpp index 54d8459dd5..c5f6dbb26e 100644 --- a/src/dpp/events/stage_instance_delete.cpp +++ b/src/dpp/events/stage_instance_delete.cpp @@ -39,7 +39,9 @@ void stage_instance_delete::handle(discord_client* client, json &j, const std::s json& d = j["d"]; dpp::stage_instance_delete_t sid(client, raw); sid.deleted.fill_from_json(&d); - client->creator->on_stage_instance_delete.call(sid); + client->creator->queue_work(1, [client, sid]() { + client->creator->on_stage_instance_delete.call(sid); + }); } } diff --git a/src/dpp/events/stage_instance_update.cpp b/src/dpp/events/stage_instance_update.cpp index 8b2188f998..29b4cf066d 100644 --- a/src/dpp/events/stage_instance_update.cpp +++ b/src/dpp/events/stage_instance_update.cpp @@ -41,7 +41,9 @@ void stage_instance_update::handle(discord_client* client, json &j, const std::s json& d = j["d"]; dpp::stage_instance_update_t siu(client, raw); siu.updated.fill_from_json(&d); - client->creator->on_stage_instance_update.call(siu); + client->creator->queue_work(1, [client, siu]() { + client->creator->on_stage_instance_update.call(siu); + }); } } diff --git a/src/dpp/events/thread_create.cpp b/src/dpp/events/thread_create.cpp index 7e1993a6ee..51a59779e5 100644 --- a/src/dpp/events/thread_create.cpp +++ b/src/dpp/events/thread_create.cpp @@ -42,7 +42,9 @@ void thread_create::handle(discord_client* client, json& j, const std::string& r dpp::thread_create_t tc(client, raw); tc.created = t; tc.creating_guild = g; - client->creator->on_thread_create.call(tc); + client->creator->queue_work(1, [client, tc]() { + client->creator->on_thread_create.call(tc); + }); } } }; diff --git a/src/dpp/events/thread_delete.cpp b/src/dpp/events/thread_delete.cpp index b4bfc45710..f9fd5cd2b8 100644 --- a/src/dpp/events/thread_delete.cpp +++ b/src/dpp/events/thread_delete.cpp @@ -42,7 +42,9 @@ void thread_delete::handle(discord_client* client, json& j, const std::string& r dpp::thread_delete_t td(client, raw); td.deleted = t; td.deleting_guild = g; - client->creator->on_thread_delete.call(td); + client->creator->queue_work(1, [client, td]() { + client->creator->on_thread_delete.call(td); + }); } } }; diff --git a/src/dpp/events/thread_list_sync.cpp b/src/dpp/events/thread_list_sync.cpp index 3f850f7950..45dbbd1b9e 100644 --- a/src/dpp/events/thread_list_sync.cpp +++ b/src/dpp/events/thread_list_sync.cpp @@ -52,7 +52,9 @@ void thread_list_sync::handle(discord_client* client, json& j, const std::string tls.members.push_back(thread_member().fill_from_json(&tm)); } } - client->creator->on_thread_list_sync.call(tls); + client->creator->queue_work(1, [client, tls]() { + client->creator->on_thread_list_sync.call(tls); + }); } } }; diff --git a/src/dpp/events/thread_member_update.cpp b/src/dpp/events/thread_member_update.cpp index e4407b425b..84a97e5603 100644 --- a/src/dpp/events/thread_member_update.cpp +++ b/src/dpp/events/thread_member_update.cpp @@ -34,7 +34,9 @@ void thread_member_update::handle(discord_client* client, json& j, const std::st json& d = j["d"]; dpp::thread_member_update_t tm(client, raw); tm.updated = thread_member().fill_from_json(&d); - client->creator->on_thread_member_update.call(tm); + client->creator->queue_work(1, [client, tm]() { + client->creator->on_thread_member_update.call(tm); + }); } } }; diff --git a/src/dpp/events/thread_members_update.cpp b/src/dpp/events/thread_members_update.cpp index 90d2fa5cb1..aba9bedf98 100644 --- a/src/dpp/events/thread_members_update.cpp +++ b/src/dpp/events/thread_members_update.cpp @@ -52,7 +52,9 @@ void thread_members_update::handle(discord_client* client, json& j, const std::s client->creator->log(dpp::ll_error, std::string("thread_members_update: {}") + e.what()); } } - client->creator->on_thread_members_update.call(tms); + client->creator->queue_work(1, [client, tms]() { + client->creator->on_thread_members_update.call(tms); + }); } } }; diff --git a/src/dpp/events/thread_update.cpp b/src/dpp/events/thread_update.cpp index 7d92c9fd4e..e6fa3bec80 100644 --- a/src/dpp/events/thread_update.cpp +++ b/src/dpp/events/thread_update.cpp @@ -38,7 +38,9 @@ void thread_update::handle(discord_client* client, json& j, const std::string& r dpp::thread_update_t tu(client, raw); tu.updated = t; tu.updating_guild = g; - client->creator->on_thread_update.call(tu); + client->creator->queue_work(1, [client, tu]() { + client->creator->on_thread_update.call(tu); + }); } } }; diff --git a/src/dpp/events/typing_start.cpp b/src/dpp/events/typing_start.cpp index b5334c9f54..4ffc231e8f 100644 --- a/src/dpp/events/typing_start.cpp +++ b/src/dpp/events/typing_start.cpp @@ -43,7 +43,9 @@ void typing_start::handle(discord_client* client, json &j, const std::string &ra ts.user_id = snowflake_not_null(&d, "user_id"); ts.typing_user = dpp::find_user(ts.user_id); ts.timestamp = ts_not_null(&d, "timestamp"); - client->creator->on_typing_start.call(ts); + client->creator->queue_work(1, [client, ts]() { + client->creator->on_typing_start.call(ts); + }); } } diff --git a/src/dpp/events/user_update.cpp b/src/dpp/events/user_update.cpp index b954b55c27..d018825c87 100644 --- a/src/dpp/events/user_update.cpp +++ b/src/dpp/events/user_update.cpp @@ -49,7 +49,9 @@ void user_update::handle(discord_client* client, json &j, const std::string &raw if (!client->creator->on_user_update.empty()) { dpp::user_update_t uu(client, raw); uu.updated = *u; - client->creator->on_user_update.call(uu); + client->creator->queue_work(1, [client, uu]() { + client->creator->on_user_update.call(uu); + }); } } else { if (!client->creator->on_user_update.empty()) { @@ -57,7 +59,9 @@ void user_update::handle(discord_client* client, json &j, const std::string &raw u.fill_from_json(&d); dpp::user_update_t uu(client, raw); uu.updated = u; - client->creator->on_user_update.call(uu); + client->creator->queue_work(1, [client, uu]() { + client->creator->on_user_update.call(uu); + }); } } } diff --git a/src/dpp/events/voice_server_update.cpp b/src/dpp/events/voice_server_update.cpp index 14cf9f77b8..ea1c8e07d5 100644 --- a/src/dpp/events/voice_server_update.cpp +++ b/src/dpp/events/voice_server_update.cpp @@ -60,7 +60,9 @@ void voice_server_update::handle(discord_client* client, json &j, const std::str } if (!client->creator->on_voice_server_update.empty()) { - client->creator->on_voice_server_update.call(vsu); + client->creator->queue_work(1, [client, vsu]() { + client->creator->on_voice_server_update.call(vsu); + }); } } diff --git a/src/dpp/events/voice_state_update.cpp b/src/dpp/events/voice_state_update.cpp index 7be7b18515..7831564b7f 100644 --- a/src/dpp/events/voice_state_update.cpp +++ b/src/dpp/events/voice_state_update.cpp @@ -84,7 +84,9 @@ void voice_state_update::handle(discord_client* client, json &j, const std::stri } if (!client->creator->on_voice_state_update.empty()) { - client->creator->on_voice_state_update.call(vsu); + client->creator->queue_work(1, [client, vsu]() { + client->creator->on_voice_state_update.call(vsu); + }); } } diff --git a/src/dpp/events/webhooks_update.cpp b/src/dpp/events/webhooks_update.cpp index 4f676eeade..bf4ea4ae96 100644 --- a/src/dpp/events/webhooks_update.cpp +++ b/src/dpp/events/webhooks_update.cpp @@ -41,7 +41,9 @@ void webhooks_update::handle(discord_client* client, json &j, const std::string dpp::webhooks_update_t wu(client, raw); wu.webhook_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); wu.webhook_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); - client->creator->on_webhooks_update.call(wu); + client->creator->queue_work(1, [client, wu]() { + client->creator->on_webhooks_update.call(wu); + }); } } From 2c3e701e2db98ee01526995d2c1cdf9a63c4d645 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 14:16:05 +0000 Subject: [PATCH 028/112] make poll ctor not protected --- src/dpp/socketengines/poll.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 10c7e85df3..f192c40a33 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -145,6 +145,8 @@ struct socket_engine_poll : public socket_engine_base { return r; } + socket_engine_poll(cluster* creator) : socket_engine_base(creator) { }; + protected: bool remove_socket(dpp::socket fd) final { @@ -159,8 +161,6 @@ struct socket_engine_poll : public socket_engine_base { } return false; } - - socket_engine_poll(cluster* creator) : socket_engine_base(creator) { }; }; std::unique_ptr create_socket_engine(cluster* creator) { From 7994a4a8d9c4ca460479723b7e8922fdff8162a5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 15:03:01 +0000 Subject: [PATCH 029/112] unit test no longer runs forever --- include/dpp/cluster.h | 13 +++++++++++-- src/dpp/cluster.cpp | 22 +++++++++++----------- src/unittest/test.cpp | 6 ------ src/unittest/test.h | 1 - 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 44a7f521f2..aad6d024e3 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -178,6 +178,11 @@ class DPP_EXPORT cluster { */ std::unique_ptr pool{nullptr}; + /** + * @brief Used to spawn the socket engine into its own thread if + * the cluster is started with dpp::st_return. It is unused otherwise. + */ + std::unique_ptr engine_thread{nullptr}; public: /** @@ -233,9 +238,13 @@ class DPP_EXPORT cluster { websocket_protocol_t ws_mode; /** - * @brief Condition variable notified when the cluster is terminating. + * @brief Atomic bool to set to true when the cluster is terminating. + * + * D++ itself does not set this value, it is for library users to set if they want + * the cluster to terminate outside of a flow where they may have simple access to + * destruct the cluster object. */ - std::condition_variable terminating; + std::atomic_bool terminating{false}; /** * @brief The time (in seconds) that a request is allowed to take. diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 9c804a9075..a7fdf7d975 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -202,10 +202,10 @@ dpp::utility::uptime cluster::uptime() void cluster::start(bool return_after) { - auto block_calling_thread = [this]() { - std::mutex thread_mutex; - std::unique_lock thread_lock(thread_mutex); - this->terminating.wait(thread_lock); + auto event_loop = [this]() -> void { + do { + socketengine->process_events(); + } while (!this->terminating); }; if (on_guild_member_add && !(intents & dpp::i_guild_members)) { @@ -300,13 +300,13 @@ void cluster::start(bool return_after) { log(ll_debug, "Shards started."); }); - do { - // TODO: Thread this - socketengine->process_events(); - } while (true); - - if (!return_after) { - block_calling_thread(); + if (return_after) { + engine_thread = std::make_unique(std::jthread([event_loop]() { + dpp::utility::set_thread_name("event_loop"); + event_loop(); + })); + } else { + event_loop(); } } diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 5c586da5e9..6c89f89358 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -1163,12 +1163,6 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - set_test(SYNC, false); - if (!offline) { - dpp::message m = dpp::sync(&bot, &dpp::cluster::message_create, dpp::message(TEST_TEXT_CHANNEL_ID, "TEST")); - set_test(SYNC, m.content == "TEST"); - } - bot.on_guild_create([&](const dpp::guild_create_t & event) { if (event.created->id == TEST_GUILD_ID) { set_test(GUILDCREATE, true); diff --git a/src/unittest/test.h b/src/unittest/test.h index 97637b7711..a687287623 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -144,7 +144,6 @@ DPP_TEST(MD_ESC_1, "Markdown escaping (ignore code block contents)", tf_offline) DPP_TEST(MD_ESC_2, "Markdown escaping (escape code block contents)", tf_offline); DPP_TEST(URLENC, "URL encoding", tf_offline); DPP_TEST(BASE64ENC, "Base 64 encoding", tf_offline); -DPP_TEST(SYNC, "sync()", tf_online); DPP_TEST(COMPARISON, "manged object comparison", tf_offline); DPP_TEST(CHANNELCACHE, "find_channel()", tf_online); DPP_TEST(CHANNELTYPES, "channel type flags", tf_online); From 9c2e71dbbc5ea502fef306d0215593118f590b86 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 17:28:08 +0000 Subject: [PATCH 030/112] fix some httpsclient stuff --- include/dpp/cluster.h | 2 +- src/dpp/cluster.cpp | 10 ++- src/dpp/httpsclient.cpp | 3 +- src/dpp/queues.cpp | 9 +-- src/unittest/test.cpp | 137 +++++++++++++++++++++++----------------- 5 files changed, 92 insertions(+), 69 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index aad6d024e3..ceb3435458 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -249,7 +249,7 @@ class DPP_EXPORT cluster { /** * @brief The time (in seconds) that a request is allowed to take. */ - uint16_t request_timeout = 20; + uint16_t request_timeout = 60; /** * @brief Socket engine instance diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index a7fdf7d975..77db08cddb 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -203,9 +203,9 @@ dpp::utility::uptime cluster::uptime() void cluster::start(bool return_after) { auto event_loop = [this]() -> void { - do { + while (!this->terminating && socketengine.get()) { socketengine->process_events(); - } while (!this->terminating); + } }; if (on_guild_member_add && !(intents & dpp::i_guild_members)) { @@ -311,7 +311,8 @@ void cluster::start(bool return_after) { } void cluster::shutdown() { - /* Signal condition variable to terminate */ + /* Signal termination */ + terminating = true; terminating.notify_all(); /* Free memory for active timers */ for (auto & t : timer_list) { @@ -324,6 +325,9 @@ void cluster::shutdown() { log(ll_info, "Terminating shard id " + std::to_string(sh.second->shard_id)); delete sh.second; } + if (engine_thread) { + engine_thread->join(); + } shards.clear(); } diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index dc7f222e08..9ece547af9 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -40,12 +40,11 @@ https_client::https_client(cluster* creator, const std::string &hostname, uint16 request_headers(extra_headers), status(0), http_protocol(protocol), - timeout(request_timeout), + timeout(time(nullptr) + request_timeout), timed_out(false), completed(done) { nonblocking = false; - timeout = time(nullptr) + request_timeout; https_client::connect(); } diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 1a2f22e3d5..df5a043253 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -226,18 +226,15 @@ http_request_completion_t http_request::run(in_thread* processor, cluster* owner !hci.is_ssl, owner->request_timeout, protocol, - [processor, rv, hci, this, owner, start, _url](https_client* client) { + [processor, rv, hci, this, owner, start, _url, &request_verb](https_client* client) { http_request_completion_t result{rv}; result.latency = dpp::utility::time_f() - start; if (client->timed_out) { result.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Timed out while waiting for the response"); + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Timed out while waiting for the response"); } else if (cli->get_status() < 100) { result.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Malformed HTTP response"); - } else if (cli->get_status() >= 400) { - owner->log(ll_error, "HTTP(S) error " + std::to_string(cli->get_status()) + " on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": " + cli->get_content()); - result.error = h_connection; + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Malformed HTTP response"); } else { populate_result(_url, owner, result, *client); } diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 6c89f89358..5a42138606 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -282,53 +282,6 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(HOSTINFO, hci_test); - set_test(HTTPS, false); - if (!offline) { - dpp::multipart_content multipart = dpp::https_client::build_multipart( - "{\"content\":\"test\"}", {"test.txt", "blob.blob"}, {"ABCDEFGHI", "BLOB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}, {"text/plain", "application/octet-stream"} - ); - try { - /*dpp::https_client c("discord.com", 443, "/api/channels/" + std::to_string(TEST_TEXT_CHANNEL_ID) + "/messages", "POST", multipart.body, - { - {"Content-Type", multipart.mimetype}, - {"Authorization", "Bot " + token} - } - ); - std::string hdr1 = c.get_header("server"); - std::string content1 = c.get_content(); - set_test(HTTPS, hdr1 == "cloudflare" && c.get_status() == 200);*/ - } - catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(HTTPS, false); - } - } - - set_test(HTTP, false); - try { - /*dpp::https_client c2("github.com", 80, "/", "GET", "", {}, true); - std::string hdr2 = c2.get_header("location"); - std::string content2 = c2.get_content(); - set_test(HTTP, hdr2 == "https://github.com/" && c2.get_status() == 301);*/ - } - catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(HTTP, false); - } - - set_test(MULTIHEADER, false); - try { - /*dpp::https_client c2("dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}); - size_t count = c2.get_header_count("set-cookie"); - size_t count_list = c2.get_header_list("set-cookie").size(); - // Google sets a bunch of cookies when we start accessing it. - set_test(MULTIHEADER, c2.get_status() == 200 && count > 1 && count == count_list);*/ - } - catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(MULTIHEADER, false); - } - std::vector testaudio = load_test_audio(); set_test(READFILE, false); @@ -2288,16 +2241,21 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b createdRole.name = "Test-Role-Edited"; createdRole.colour = dpp::colors::light_sea_green; try { - dpp::role edited = dpp::sync(&bot, &dpp::cluster::role_edit, createdRole); - if (createdRole.id == edited.id && edited.name == "Test-Role-Edited") { - set_test(ROLE_EDIT, true); - } + bot.role_delete(TEST_GUILD_ID, createdRole.id, [createdRole](const auto& e) { + if (e.is_error()) { + set_test(ROLE_EDIT, false); + return; + } + dpp::role edited = std::get(e.value); + set_test(ROLE_EDIT, (createdRole.id == edited.id && edited.name == "Test-Role-Edited")); + }); } catch (dpp::rest_exception &exception) { set_test(ROLE_EDIT, false); } try { - dpp::sync(&bot, &dpp::cluster::role_delete, TEST_GUILD_ID, createdRole.id); - set_test(ROLE_DELETE, true); + bot.role_delete(TEST_GUILD_ID, createdRole.id, [](const auto& e) { + set_test(ROLE_DELETE, !e.is_error()); + }); } catch (dpp::rest_exception &exception) { bot.log(dpp::ll_warning, "Exception: " + std::string(exception.what())); set_test(ROLE_DELETE, false); @@ -2316,6 +2274,59 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(BOTSTART, false); } + set_test(HTTPS, false); + if (!offline) { + dpp::multipart_content multipart = dpp::https_client::build_multipart( + "{\"content\":\"test\"}", {"test.txt", "blob.blob"}, {"ABCDEFGHI", "BLOB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}, {"text/plain", "application/octet-stream"} + ); + try { + dpp::https_client c(&bot, "discord.com", 443, "/api/channels/" + std::to_string(TEST_TEXT_CHANNEL_ID) + "/messages", "POST", multipart.body, + { + {"Content-Type", multipart.mimetype}, + {"Authorization", "Bot " + token} + }, false, 5, "1.1", [](dpp::https_client* c) { + std::string hdr1 = c->get_header("server"); + std::string content1 = c->get_content(); + set_test(HTTPS, hdr1 == "cloudflare" && c->get_status() == 200); + } + ); + sleep(6); + } + catch (const dpp::exception& e) { + std::cout << e.what() << "\n"; + set_test(HTTPS, false); + } + } + + set_test(HTTP, false); + try { + dpp::https_client c2(&bot, "github.com", 80, "/", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { + std::string hdr2 = c2->get_header("location"); + std::string content2 = c2->get_content(); + set_test(HTTP, hdr2 == "https://github.com/" && c2->get_status() == 301); + }); + sleep(6); + } + catch (const dpp::exception& e) { + std::cout << e.what() << "\n"; + set_test(HTTP, false); + } + + set_test(MULTIHEADER, false); + try { + dpp::https_client c2(&bot, "dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { + size_t count = c2->get_header_count("set-cookie"); + size_t count_list = c2->get_header_list("set-cookie").size(); + // Google sets a bunch of cookies when we start accessing it. + set_test(MULTIHEADER, c2->get_status() == 200 && count > 1 && count == count_list); + }); + sleep(6); + } + catch (const dpp::exception& e) { + std::cout << e.what() << "\n"; + set_test(MULTIHEADER, false); + } + set_test(TIMERSTART, false); uint32_t ticks = 0; dpp::timer th = bot.start_timer([&](dpp::timer timer_handle) { @@ -2330,8 +2341,14 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(USER_GET_CACHED_PRESENT, false); try { - dpp::user_identified u = dpp::sync(&bot, &dpp::cluster::user_get_cached, TEST_USER_ID); - set_test(USER_GET_CACHED_PRESENT, (u.id == TEST_USER_ID)); + bot.user_get_cached(TEST_USER_ID, [](const auto &e) { + if (e.is_error()) { + set_test(USER_GET_CACHED_PRESENT, false); + return; + } + dpp::user_identified u = std::get(e.value); + set_test(USER_GET_CACHED_PRESENT, (u.id == TEST_USER_ID)); + }); } catch (const std::exception&) { set_test(USER_GET_CACHED_PRESENT, false); @@ -2345,8 +2362,14 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b * If this becomes not true any more, we'll pick another well known * user ID. */ - dpp::user_identified u = dpp::sync(&bot, &dpp::cluster::user_get_cached, 90339695967350784); - set_test(USER_GET_CACHED_ABSENT, (u.id == dpp::snowflake(90339695967350784))); + bot.user_get_cached(90339695967350784, [](const auto &e) { + if (e.is_error()) { + set_test(USER_GET_CACHED_ABSENT, false); + return; + } + dpp::user_identified u = std::get(e.value); + set_test(USER_GET_CACHED_ABSENT, (u.id == 90339695967350784)); + }); } catch (const std::exception&) { set_test(USER_GET_CACHED_ABSENT, false); From d286958c353e01e5fb3025870492916f775085d9 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 17:58:01 +0000 Subject: [PATCH 031/112] remove jthread, c++20 only --- include/dpp/cluster.h | 3 ++- src/dpp/cluster.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index ceb3435458..baf1f98a6b 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -182,7 +183,7 @@ class DPP_EXPORT cluster { * @brief Used to spawn the socket engine into its own thread if * the cluster is started with dpp::st_return. It is unused otherwise. */ - std::unique_ptr engine_thread{nullptr}; + std::unique_ptr engine_thread{nullptr}; public: /** diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 77db08cddb..88605ce256 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -301,10 +301,10 @@ void cluster::start(bool return_after) { }); if (return_after) { - engine_thread = std::make_unique(std::jthread([event_loop]() { + engine_thread = std::make_unique([event_loop]() { dpp::utility::set_thread_name("event_loop"); event_loop(); - })); + }); } else { event_loop(); } From f76f2d1a12338ab2a18f144edcf4ababd3147ac8 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 18:03:09 +0000 Subject: [PATCH 032/112] terminating.notify_all is C++20 --- src/dpp/cluster.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 88605ce256..7daa0735fa 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -313,7 +313,6 @@ void cluster::start(bool return_after) { void cluster::shutdown() { /* Signal termination */ terminating = true; - terminating.notify_all(); /* Free memory for active timers */ for (auto & t : timer_list) { delete t.second; From f53a5823e71d787348d56f7c23eff08feb32918c Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 18:18:47 +0000 Subject: [PATCH 033/112] sleep() shouldnt be used, instead use std::this_thread::sleep_for(std::chrono::seconds(6)) --- src/unittest/test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 5a42138606..c108c4068c 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -2290,7 +2290,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(HTTPS, hdr1 == "cloudflare" && c->get_status() == 200); } ); - sleep(6); + std::this_thread::sleep_for(std::chrono::seconds(6)); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; @@ -2305,7 +2305,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b std::string content2 = c2->get_content(); set_test(HTTP, hdr2 == "https://github.com/" && c2->get_status() == 301); }); - sleep(6); + std::this_thread::sleep_for(std::chrono::seconds(6)); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; @@ -2320,7 +2320,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b // Google sets a bunch of cookies when we start accessing it. set_test(MULTIHEADER, c2->get_status() == 200 && count > 1 && count == count_list); }); - sleep(6); + std::this_thread::sleep_for(std::chrono::seconds(6)); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; From 057db5e037fde7355a85dbf736ef06612cc69dbb Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 18:39:20 +0000 Subject: [PATCH 034/112] dont let request_verb go out of scope --- src/dpp/queues.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index df5a043253..d4c315bfb8 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -31,6 +31,14 @@ namespace dpp { +constexpr std::array request_verb { + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" +}; + namespace { @@ -196,14 +204,6 @@ http_request_completion_t http_request::run(in_thread* processor, cluster* owner } } - constexpr std::array request_verb { - "GET", - "POST", - "PUT", - "PATCH", - "DELETE" - }; - multipart_content multipart; if (non_discord) { multipart = { postdata, mimetype }; @@ -226,7 +226,7 @@ http_request_completion_t http_request::run(in_thread* processor, cluster* owner !hci.is_ssl, owner->request_timeout, protocol, - [processor, rv, hci, this, owner, start, _url, &request_verb](https_client* client) { + [processor, rv, hci, this, owner, start, _url](https_client* client) { http_request_completion_t result{rv}; result.latency = dpp::utility::time_f() - start; if (client->timed_out) { From 4b59c6543106888fe0c82aa9c34e7d888a63e1e5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 18:48:46 +0000 Subject: [PATCH 035/112] always populate even on timeout --- src/dpp/queues.cpp | 3 +-- src/unittest/test.cpp | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index d4c315bfb8..b7ff3c3c94 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -235,9 +235,8 @@ http_request_completion_t http_request::run(in_thread* processor, cluster* owner } else if (cli->get_status() < 100) { result.error = h_connection; owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Malformed HTTP response"); - } else { - populate_result(_url, owner, result, *client); } + populate_result(_url, owner, result, *client); /* Set completion flag */ completed = true; diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index c108c4068c..6899cd8b0f 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -1153,9 +1153,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (files_tested == std::array{true, true, true} && pin_tested && thread_tested) { set_test(MESSAGEDELETE, false); bot.message_delete(message_id, channel_id, [](const dpp::confirmation_callback_t &callback) { - if (!callback.is_error()) { - set_test(MESSAGEDELETE, true); - } + set_test(MESSAGEDELETE, !callback.is_error()); }); } } From b0c3b3c6a3f4887c0d39a659dafe91ba18ead1e8 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 19:56:35 +0000 Subject: [PATCH 036/112] some unit tests improvements --- src/unittest/test.cpp | 49 ++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 6899cd8b0f..83722a7aaa 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -2223,41 +2223,32 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b r.permissions.add(dpp::p_move_members); r.set_flags(dpp::r_mentionable); r.colour = dpp::colors::moon_yellow; - dpp::role createdRole; - try { - createdRole = dpp::sync(&bot, &dpp::cluster::role_create, r); + bot.role_create(r, [&bot, r](const auto cc) { + if (cc.is_error()) { + set_test(ROLE_CREATE, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); + return; + } + dpp::role createdRole = std::get(cc.value); if (createdRole.name == r.name && createdRole.has_move_members() && createdRole.flags & dpp::r_mentionable && createdRole.colour == r.colour) { set_test(ROLE_CREATE, true); - } - } catch (dpp::rest_exception &exception) { - set_test(ROLE_CREATE, false); - } - createdRole.guild_id = TEST_GUILD_ID; - createdRole.name = "Test-Role-Edited"; - createdRole.colour = dpp::colors::light_sea_green; - try { - bot.role_delete(TEST_GUILD_ID, createdRole.id, [createdRole](const auto& e) { - if (e.is_error()) { + bot.role_edit(createdRole, [&bot, createdRole](const auto& e) { + if (e.is_error()) { set_test(ROLE_EDIT, false); - return; - } - dpp::role edited = std::get(e.value); - set_test(ROLE_EDIT, (createdRole.id == edited.id && edited.name == "Test-Role-Edited")); - }); - } catch (dpp::rest_exception &exception) { - set_test(ROLE_EDIT, false); - } - try { - bot.role_delete(TEST_GUILD_ID, createdRole.id, [](const auto& e) { - set_test(ROLE_DELETE, !e.is_error()); - }); - } catch (dpp::rest_exception &exception) { - bot.log(dpp::ll_warning, "Exception: " + std::string(exception.what())); - set_test(ROLE_DELETE, false); - } + return; + } + dpp::role edited = std::get(e.value); + set_test(ROLE_EDIT, (createdRole.id == edited.id && edited.name == "Test-Role-Edited")); + bot.role_delete(TEST_GUILD_ID, createdRole.id, [](const auto& e) { + set_test(ROLE_DELETE, !e.is_error()); + }); + }); + } + }); } }; From 80144d18caad14360a26938fc70cc999d2a563b0 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 16 Nov 2024 23:50:06 +0000 Subject: [PATCH 037/112] fix role edit/role delete unit tests --- src/unittest/test.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 620c915edf..cc4e6ceb60 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -2248,14 +2248,17 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b return; } dpp::role createdRole = std::get(cc.value); + createdRole.guild_id = TEST_GUILD_ID; if (createdRole.name == r.name && createdRole.has_move_members() && createdRole.flags & dpp::r_mentionable && createdRole.colour == r.colour) { + createdRole.name = "Test-Role-Edited"; set_test(ROLE_CREATE, true); bot.role_edit(createdRole, [&bot, createdRole](const auto& e) { if (e.is_error()) { - set_test(ROLE_EDIT, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); return; } dpp::role edited = std::get(e.value); @@ -2264,6 +2267,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(ROLE_DELETE, !e.is_error()); }); }); + } else { + set_test(ROLE_CREATE, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); } }); } @@ -2272,7 +2279,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(BOTSTART, false); try { if (!offline) { - bot.start(true); + bot.start(dpp::st_return); set_test(BOTSTART, true); } } @@ -2323,7 +2330,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b dpp::https_client c2(&bot, "dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { size_t count = c2->get_header_count("set-cookie"); size_t count_list = c2->get_header_list("set-cookie").size(); - // Google sets a bunch of cookies when we start accessing it. + // This test script sets a bunch of cookies when we request it. set_test(MULTIHEADER, c2->get_status() == 200 && count > 1 && count == count_list); }); std::this_thread::sleep_for(std::chrono::seconds(6)); From 5ae9b8b639a623fffe0bb1e11cc5bba2a05a6a0a Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sun, 17 Nov 2024 00:02:27 +0000 Subject: [PATCH 038/112] some codacy fixes --- src/dpp/socketengines/epoll.cpp | 2 +- src/dpp/socketengines/kqueue.cpp | 2 +- src/dpp/socketengines/poll.cpp | 2 +- src/sockettest/socket.cpp | 9 +++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index e092a7ba70..c38439cdf0 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -60,7 +60,7 @@ struct socket_engine_epoll : public socket_engine_base { socket_engine_epoll& operator=(const socket_engine_epoll&) = delete; socket_engine_epoll& operator=(socket_engine_epoll&&) = delete; - socket_engine_epoll(cluster* creator) : socket_engine_base(creator), epoll_handle(epoll_create(socket_engine_epoll::epoll_hint)) { + explicit socket_engine_epoll(cluster* creator) : socket_engine_base(creator), epoll_handle(epoll_create(socket_engine_epoll::epoll_hint)) { events.resize(socket_engine_epoll::epoll_hint); if (epoll_handle == -1) { throw dpp::connection_exception("Failed to initialise epoll()"); diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 4a6e9a8d9e..8d75ad4028 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -49,7 +49,7 @@ struct socket_engine_kqueue : public socket_engine_base { socket_engine_kqueue& operator=(const socket_engine_kqueue&) = default; socket_engine_kqueue& operator=(socket_engine_kqueue&&) = default; - socket_engine_kqueue(cluster* creator) : socket_engine_base(creator), kqueue_handle(kqueue()) { + explicit socket_engine_kqueue(cluster* creator) : socket_engine_base(creator), kqueue_handle(kqueue()) { change_list.resize(8); ke_list.resize(16); if (kqueue_handle == -1) { diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index f192c40a33..6b9b09eb4f 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -145,7 +145,7 @@ struct socket_engine_poll : public socket_engine_base { return r; } - socket_engine_poll(cluster* creator) : socket_engine_base(creator) { }; + explicit socket_engine_poll(cluster* creator) : socket_engine_base(creator) { }; protected: diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp index e6e19e652d..08ce6b12da 100644 --- a/src/sockettest/socket.cpp +++ b/src/sockettest/socket.cpp @@ -22,10 +22,9 @@ #include #include -#include #include #include -#include +#include int main() { dpp::cluster cl("no-token"); @@ -51,7 +50,7 @@ int main() { int r = 0; do { char buf[128]{0}; - r = ::read(e.fd, buf, 127); + r = ::read(e.fd, buf, sizeof(buf)); if (r > 0) { buf[127] = 0; std::cout << buf; @@ -65,7 +64,9 @@ int main() { }, [](dpp::socket fd, const struct dpp::socket_events& e) { std::cout << "WANT_WRITE event on socket " << fd << "\n"; - ::write(e.fd, "GET / HTTP/1.0\r\nConnection: close\r\n\r\n", strlen("GET / HTTP/1.0\r\nConnection: close\r\n\r\n")); + constexpr std::string_view request{"GET / HTTP/1.0\r\nConnection: close\r\n\r\n"}; + auto written = ::write(e.fd, request.data(), request.length()); + std::cout << "Written: " << written << "\n"; }, [](dpp::socket fd, const struct dpp::socket_events&, int error_code) { std::cout << "WANT_ERROR event on socket " << fd << " with code " << error_code << "\n"; From 192b8ae2e751657fc8214e9ecc0f97858a24e50d Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sun, 17 Nov 2024 02:18:05 +0000 Subject: [PATCH 039/112] fix http(s) requests with 0 content length timing out --- src/dpp/httpsclient.cpp | 6 ---- src/dpp/socketengines/epoll.cpp | 33 ++++++--------------- src/dpp/socketengines/kqueue.cpp | 8 ++--- src/dpp/sslclient.cpp | 28 +++++++----------- src/soaktest/soak.cpp | 8 ++++- src/unittest/test.cpp | 50 ++++++++++++++++---------------- src/unittest/test.h | 18 ++++++------ 7 files changed, 64 insertions(+), 87 deletions(-) diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 9ece547af9..2428e030de 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -192,10 +192,6 @@ bool https_client::handle_buffer(std::string &buffer) } else { content_length = ULLONG_MAX; } - auto it_conn = response_headers.find("connection"); - if (it_conn != response_headers.end() && it_conn->second == "close") { - keepalive = false; - } chunked = false; auto it_txenc = response_headers.find("transfer-encoding"); if (it_txenc != response_headers.end()) { @@ -218,11 +214,9 @@ bool https_client::handle_buffer(std::string &buffer) return true; } else { /* Non-HTTP-like response with invalid headers. Go no further. */ - keepalive = false; return false; } } else { - keepalive = false; return false; } diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index c38439cdf0..9bf3b1cc76 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -87,42 +87,27 @@ struct socket_engine_epoll : public socket_engine_base { } if ((ev.events & EPOLLHUP) != 0U) { - //eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_ERROR); - //pool->enqueue([this, eh, fd]() { - eh->on_error(fd, *eh, 0); - // eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_ERROR); - //}); + eh->on_error(fd, *eh, EPIPE); continue; } if ((ev.events & EPOLLERR) != 0U) { - /* Get error number */ - //eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_ERROR); - //pool->enqueue([this, eh, fd]() { - socklen_t codesize = sizeof(int); - int errcode{}; - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { - errcode = errno; - } - eh->on_error(fd, *eh, errcode); - // eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_ERROR); - //}); + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { + errcode = errno; + } + eh->on_error(fd, *eh, errcode); continue; } if ((ev.events & EPOLLOUT) != 0U) { eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_WRITE); - //pool->enqueue([eh, fd]() { - eh->on_write(fd, *eh); - //}); + eh->on_write(fd, *eh); } if ((ev.events & EPOLLIN) != 0U) { - //eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_READ); - //pool->enqueue([this, eh, fd]() { - eh->on_read(fd, *eh); - // eh->flags = modify_event(epoll_handle, eh, eh->flags | WANT_READ); - //}); + eh->on_read(fd, *eh); } } prune(); diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 8d75ad4028..05949a9388 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -44,10 +44,10 @@ struct socket_engine_kqueue : public socket_engine_base { std::vector change_list; std::vector ke_list; - socket_engine_kqueue(const socket_engine_kqueue&) = default; - socket_engine_kqueue(socket_engine_kqueue&&) = default; - socket_engine_kqueue& operator=(const socket_engine_kqueue&) = default; - socket_engine_kqueue& operator=(socket_engine_kqueue&&) = default; + socket_engine_kqueue(const socket_engine_kqueue&) = delete; + socket_engine_kqueue(socket_engine_kqueue&&) = delete; + socket_engine_kqueue& operator=(const socket_engine_kqueue&) = delete; + socket_engine_kqueue& operator=(socket_engine_kqueue&&) = delete; explicit socket_engine_kqueue(cluster* creator) : socket_engine_base(creator), kqueue_handle(kqueue()) { change_list.resize(8); diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 3365b342db..3d21ade6f2 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -26,17 +26,8 @@ #include #include #include - /* Windows doesn't have standard poll(), it has WSAPoll. - * It's the same thing with different symbol names. - * Microsoft gotta be different. - */ - #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) - #define pollfd WSAPOLLFD - /* Windows sockets library */ - #pragma comment(lib, "ws2_32") #else - /* Anyting other than Windows (e.g. sane OSes) */ - #include + /* Anything other than Windows (e.g. sane OSes) */ #include #include #endif @@ -110,11 +101,6 @@ class openssl_context_deleter { */ thread_local std::unique_ptr openssl_context; -/** - * @brief Keepalive sessions, per-thread - */ -thread_local std::unordered_map keepalives; - bool close_socket(dpp::socket sfd) { /* close_socket on an error socket is a non-op */ @@ -158,7 +144,7 @@ bool set_nonblocking(dpp::socket sockfd, bool non_blocking) * @param addr address to connect to * @param addrlen address length * @param timeout_ms timeout in milliseconds - * @return int -1 on error, 0 on succcess just like POSIX connect() + * @return int -1 on error, 0 on success just like POSIX connect() * @throw dpp::connection_exception on failure */ int start_connecting(dpp::socket sockfd, const struct sockaddr *addr, socklen_t addrlen, unsigned int timeout_ms) { @@ -246,7 +232,6 @@ void ssl_client::connect() void ssl_client::socket_write(const std::string_view data) { obuffer += data; - return; } void ssl_client::one_second_timer() @@ -307,6 +292,7 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { } buffer.append(server_to_client_buffer, r); if (!this->handle_buffer(buffer)) { + this->close(); return; } bytes_in += r; @@ -375,6 +361,12 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { } void ssl_client::on_write(socket fd, const struct socket_events& e) { + + if (!connected && plaintext) { + /* Plaintext sockets connect immediately on first write event */ + connected = true; + } + if (connected) { if (obuffer.length() && client_to_server_length == 0) { memcpy(&client_to_server_buffer, obuffer.data(), obuffer.length() > DPP_BUFSIZE ? DPP_BUFSIZE : obuffer.length()); @@ -395,7 +387,7 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { bytes_out += r; if (client_to_server_length > 0) { socket_events se{e}; - se.flags = WANT_READ | WANT_ERROR; + se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; owner->socketengine->update_socket(se); } } else { diff --git a/src/soaktest/soak.cpp b/src/soaktest/soak.cpp index 83621a6a41..de82ece80b 100644 --- a/src/soaktest/soak.cpp +++ b/src/soaktest/soak.cpp @@ -23,7 +23,6 @@ #include #include #include -#include int main() { using namespace std::chrono_literals; @@ -35,6 +34,13 @@ int main() { std::cout << "[" << dpp::utility::current_date_time() << "] " << dpp::utility::loglevel(log.severity) << ": " << log.message << std::endl; }); soak_test.start(dpp::st_return); + + dpp::https_client c2(&soak_test, "github.com", 80, "/", "GET", "", {}, true, 2, "1.1", [](dpp::https_client* c2) { + std::string hdr2 = c2->get_header("location"); + std::string content2 = c2->get_content(); + std::cout << "hdr2 == " << hdr2 << " ? https://github.com/ status = " << c2->get_status() << "\n"; + }); + while (true) { std::this_thread::sleep_for(60s); dpp::discord_client* dc = soak_test.get_shard(0); diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index cc4e6ceb60..f9cbe25e9f 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -2309,35 +2309,35 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b std::cout << e.what() << "\n"; set_test(HTTPS, false); } - } - set_test(HTTP, false); - try { - dpp::https_client c2(&bot, "github.com", 80, "/", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { - std::string hdr2 = c2->get_header("location"); - std::string content2 = c2->get_content(); - set_test(HTTP, hdr2 == "https://github.com/" && c2->get_status() == 301); - }); - std::this_thread::sleep_for(std::chrono::seconds(6)); - } - catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; set_test(HTTP, false); - } + try { + dpp::https_client c2(&bot, "github.com", 80, "/", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { + std::string hdr2 = c2->get_header("location"); + std::string content2 = c2->get_content(); + set_test(HTTP, hdr2 == "https://github.com/" && c2->get_status() == 301); + }); + std::this_thread::sleep_for(std::chrono::seconds(6)); + } + catch (const dpp::exception& e) { + std::cout << e.what() << "\n"; + set_test(HTTP, false); + } - set_test(MULTIHEADER, false); - try { - dpp::https_client c2(&bot, "dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { - size_t count = c2->get_header_count("set-cookie"); - size_t count_list = c2->get_header_list("set-cookie").size(); - // This test script sets a bunch of cookies when we request it. - set_test(MULTIHEADER, c2->get_status() == 200 && count > 1 && count == count_list); - }); - std::this_thread::sleep_for(std::chrono::seconds(6)); - } - catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; set_test(MULTIHEADER, false); + try { + dpp::https_client c2(&bot, "dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { + size_t count = c2->get_header_count("set-cookie"); + size_t count_list = c2->get_header_list("set-cookie").size(); + // This test script sets a bunch of cookies when we request it. + set_test(MULTIHEADER, c2->get_status() == 200 && count > 1 && count == count_list); + }); + std::this_thread::sleep_for(std::chrono::seconds(6)); + } + catch (const dpp::exception& e) { + std::cout << e.what() << "\n"; + set_test(MULTIHEADER, false); + } } set_test(TIMERSTART, false); diff --git a/src/unittest/test.h b/src/unittest/test.h index 687edd17a0..56e8d2cbb7 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -137,7 +137,7 @@ DPP_TEST(OPTCHOICE_SNOWFLAKE, "command_option_choice::fill_from_json: snowflake" DPP_TEST(OPTCHOICE_STRING, "command_option_choice::fill_from_json: string", tf_offline); DPP_TEST(HOSTINFO, "https_client::get_host_info()", tf_offline); DPP_TEST(HTTPS, "https_client HTTPS request", tf_online); -DPP_TEST(HTTP, "https_client HTTP request", tf_offline); +DPP_TEST(HTTP, "https_client HTTP request", tf_online); DPP_TEST(RUNONCE, "run_once", tf_offline); DPP_TEST(WEBHOOK, "webhook construct from URL", tf_offline); DPP_TEST(MD_ESC_1, "Markdown escaping (ignore code block contents)", tf_offline); @@ -228,9 +228,9 @@ DPP_TEST(INVITE_CREATE, "cluster::channel_invite_create", tf_online); DPP_TEST(INVITE_GET, "cluster::invite_get", tf_online); DPP_TEST(INVITE_DELETE, "cluster::invite_delete", tf_online); -/* Extended set -- Less important, skipped on the master branch due to rate limits and GitHub actions limitations*/ +/* Extended set -- Less important, skipped on the master branch due to rate limits and GitHub actions limitations */ /* To execute, run unittests with "full" command line argument */ -DPP_TEST(MULTIHEADER, "multiheader cookie test", tf_offline | tf_extended); // Fails in the EU as cookies are not sent without acceptance +DPP_TEST(MULTIHEADER, "multiheader cookie test", tf_online | tf_extended); DPP_TEST(VOICECONN, "Connect to voice channel", tf_online | tf_extended); DPP_TEST(VOICESEND, "Send audio to voice channel", tf_online | tf_extended); // udp unreliable on gitbub @@ -290,7 +290,7 @@ extern dpp::snowflake TEST_EVENT_ID; /* True if we skip tt_online tests */ extern bool offline; -/* True if we skip tt_extended tests*/ +/* True if we skip tt_extended tests */ extern bool extended; #ifdef DPP_CORO inline constexpr bool coro = true; @@ -576,9 +576,9 @@ inline constexpr auto is_owner = [](auto &&user) noexcept { #endif #define DPP_CHECK_CONSTRUCT_ASSIGN(test, type, var) do { \ - DPP_CHECK(test, std::is_default_constructible_v, var); \ - DPP_CHECK(test, std::is_copy_constructible_v, var); \ - DPP_CHECK(test, std::is_move_constructible_v, var); \ - DPP_CHECK(test, std::is_copy_assignable_v, var); \ + DPP_CHECK(test, std::is_default_constructible_v, var); \ + DPP_CHECK(test, std::is_copy_constructible_v, var); \ + DPP_CHECK(test, std::is_move_constructible_v, var); \ + DPP_CHECK(test, std::is_copy_assignable_v, var); \ DPP_CHECK(test, std::is_move_assignable_v, var); \ - } while(0) +} while(0) From c34c2d88adaaaa1a23ee3a317f9cff7e6f86547b Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sun, 17 Nov 2024 03:06:19 +0000 Subject: [PATCH 040/112] errors were missing part of the url --- src/dpp/queues.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index b7ff3c3c94..f1f34f4cba 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -23,7 +23,6 @@ #ifdef _WIN32 /* Central point for forcing inclusion of winsock library for all socket code */ #include -#pragma comment(lib,"ws2_32") #endif #include #include @@ -231,10 +230,10 @@ http_request_completion_t http_request::run(in_thread* processor, cluster* owner result.latency = dpp::utility::time_f() - start; if (client->timed_out) { result.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Timed out while waiting for the response"); + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": Timed out while waiting for the response"); } else if (cli->get_status() < 100) { result.error = h_connection; - owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + endpoint + ": Malformed HTTP response"); + owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": Malformed HTTP response"); } populate_result(_url, owner, result, *client); /* Set completion flag */ From 8542b08f2d861701906f0d29f1d7b425358ba4a5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 18 Nov 2024 15:38:40 +0000 Subject: [PATCH 041/112] fix: add auto retry to failed connect, fixes failed unit tests --- include/dpp/httpsclient.h | 12 +-- include/dpp/sslclient.h | 31 +++++++ src/dpp/events/message_create.cpp | 12 +-- src/dpp/httpsclient.cpp | 34 +++++--- src/dpp/message.cpp | 9 +- src/dpp/sslclient.cpp | 136 ++++++++++++++++++------------ src/unittest/test.cpp | 46 +++++----- 7 files changed, 177 insertions(+), 103 deletions(-) diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h index fdb2336aea..46e3b73901 100644 --- a/include/dpp/httpsclient.h +++ b/include/dpp/httpsclient.h @@ -137,10 +137,6 @@ using https_client_completion_event = std::function; * @note plaintext HTTP without SSL is also supported via a "downgrade" setting */ class DPP_EXPORT https_client : public ssl_client { - /** - * @brief Current connection state - */ - http_state state; /** * @brief The type of the request, e.g. GET, POST @@ -241,7 +237,12 @@ class DPP_EXPORT https_client : public ssl_client { * @brief Function to call when HTTP request is completed */ https_client_completion_event completed; - + + /** + * @brief Current connection state + */ + http_state state; + /** * @brief Connect to a specific HTTP(S) server and complete a request. * @@ -361,7 +362,6 @@ class DPP_EXPORT https_client : public ssl_client { * @return Split URL */ static http_connect_info get_host_info(std::string url); - }; } diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 7d97e26945..5266e70ab0 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -147,6 +147,16 @@ class DPP_EXPORT ssl_client */ time_t last_tick; + /** + * @brief Start time of connection + */ + time_t start; + + /** + * @brief How many times we retried connect() + */ + uint8_t connect_retries{0}; + /** * @brief Hostname connected to */ @@ -177,11 +187,24 @@ class DPP_EXPORT ssl_client */ bool connected{false}; + /** + * @brief True if tcp connect() succeeded + */ + bool tcp_connect_done{false}; + /** * @brief Timer handle for one second timer */ timer timer_handle; + /** + * @brief Unique ID of socket used as a nonce + * You can use this to identify requests vs reply + * if you want. D++ itself only sets this, and does + * not use it in any logic. It starts at 1 and increments + * for each request made. + */ + uint64_t unique_id; /** * @brief Called every second @@ -207,6 +230,14 @@ class DPP_EXPORT ssl_client */ uint64_t get_bytes_in(); + /** + * @brief Every request made has a unique ID. This increments + * for every request, starting at 1. You can use this for statistics, + * or to associate requests and replies in external event loops. + * @return Unique ID + */ + uint64_t get_unique_id() const; + /** * @brief Get SSL cipher name * @return std::string ssl cipher name diff --git a/src/dpp/events/message_create.cpp b/src/dpp/events/message_create.cpp index e017438ab8..9c79d11d57 100644 --- a/src/dpp/events/message_create.cpp +++ b/src/dpp/events/message_create.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include @@ -39,11 +38,12 @@ namespace dpp::events { void message_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_create.empty()) { - json d = j["d"]; - dpp::message_create_t msg(client, raw); - msg.msg.fill_from_json(&d, client->creator->cache_policy); - msg.msg.owner = client->creator; - client->creator->queue_work(1, [client, msg]() { + json js = j; + client->creator->queue_work(1, [client, js, raw]() { + json d = js["d"]; + dpp::message_create_t msg(client, raw); + msg.msg = message(client->owner).fill_from_json(&d, client->creator->cache_policy); + msg.msg.owner = client->creator; client->creator->on_message_create.call(msg); }); } diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 2428e030de..30835f3980 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -32,7 +32,6 @@ namespace dpp { https_client::https_client(cluster* creator, const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout, const std::string &protocol, https_client_completion_event done) : ssl_client(creator, hostname, std::to_string(port), plaintext_connection, false), - state(HTTPS_HEADERS), request_type(verb), path(urlpath), request_body(req_body), @@ -42,7 +41,8 @@ https_client::https_client(cluster* creator, const std::string &hostname, uint16 http_protocol(protocol), timeout(time(nullptr) + request_timeout), timed_out(false), - completed(done) + completed(done), + state(HTTPS_HEADERS) { nonblocking = false; https_client::connect(); @@ -157,6 +157,10 @@ bool https_client::handle_buffer(std::string &buffer) switch (state) { case HTTPS_HEADERS: if (buffer.find("\r\n\r\n") != std::string::npos) { + + /* Add 10 seconds to retrieve body */ + timeout += 10; + /* Got all headers, proceed to new state */ std::string unparsed = buffer; @@ -211,6 +215,10 @@ bool https_client::handle_buffer(std::string &buffer) state_changed = true; continue; } + if (!buffer.empty()) { + /* Got a bit of body content in the same read as the headers */ + continue; + } return true; } else { /* Non-HTTP-like response with invalid headers. Go no further. */ @@ -242,11 +250,11 @@ bool https_client::handle_buffer(std::string &buffer) case HTTPS_CHUNK_TRAILER: if (buffer.length() >= 2 && buffer.substr(0, 2) == "\r\n") { if (state == HTTPS_CHUNK_LAST) { - state = HTTPS_DONE; if (completed) { completed(this); completed = {}; } + state = HTTPS_DONE; this->close(); return false; } else { @@ -281,11 +289,11 @@ bool https_client::handle_buffer(std::string &buffer) body += buffer; buffer.clear(); if (content_length == ULLONG_MAX || body.length() >= content_length) { - state = HTTPS_DONE; if (completed) { completed(this); completed = {}; } + state = HTTPS_DONE; this->close(); return false; } @@ -317,24 +325,26 @@ http_state https_client::get_state() { } void https_client::one_second_timer() { - if ((this->sfd == SOCKET_ERROR || time(nullptr) >= timeout) && this->state != HTTPS_DONE) { - /* if and only if response is timed out */ - if (this->sfd != SOCKET_ERROR) { - timed_out = true; - } - keepalive = false; + if (!tcp_connect_done && time(nullptr) >= timeout) { + timed_out = true; + this->close(); + } else if (tcp_connect_done && !connected && time(nullptr) >= timeout && this->state != HTTPS_DONE) { this->close(); + timed_out = true; + } else if (time(nullptr) >= timeout && this->state != HTTPS_DONE) { + this->close(); + timed_out = true; } } void https_client::close() { if (state != HTTPS_DONE) { - state = HTTPS_DONE; - ssl_client::close(); if (completed) { completed(this); completed = {}; } + state = HTTPS_DONE; + ssl_client::close(); } } diff --git a/src/dpp/message.cpp b/src/dpp/message.cpp index 76c14495c4..9afa80009a 100644 --- a/src/dpp/message.cpp +++ b/src/dpp/message.cpp @@ -663,8 +663,9 @@ embed::embed() : timestamp(0) { } message::message() : managed(0), channel_id(0), guild_id(0), sent(0), edited(0), webhook_id(0), interaction_metadata{}, - owner(nullptr), type(mt_default), flags(0), pinned(false), tts(false), mention_everyone(false) + type(mt_default), flags(0), pinned(false), tts(false), mention_everyone(false) { + owner = nullptr; message_reference.channel_id = 0; message_reference.guild_id = 0; message_reference.message_id = 0; @@ -1059,8 +1060,10 @@ attachment::attachment(struct message* o, json *j) : attachment(o) { void attachment::download(http_completion_event callback) const { /* Download attachment if there is one attached to this object */ - if (owner == nullptr || owner->owner == nullptr) { - throw dpp::logic_exception(err_no_owning_message, "attachment has no owning message/cluster"); + if (owner == nullptr) { + throw dpp::logic_exception(err_no_owning_message, "attachment has no owning message"); + } else if (owner->owner == nullptr) { + throw dpp::logic_exception(err_no_owning_message, "attachment has no owning cluster"); } if (callback && this->id && !this->url.empty()) { owner->owner->request(this->url, dpp::m_get, callback); diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 3d21ade6f2..934e1e0103 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -64,6 +63,8 @@ constexpr uint16_t SOCKET_OP_TIMEOUT{5000}; namespace dpp { +uint64_t last_unique_id{1}; + /** * @brief This is an opaque class containing openssl library specific structures. * We define it this way so that the public facing D++ library doesn't require @@ -182,17 +183,23 @@ void set_signal_handler(int signal) } #endif +uint64_t ssl_client::get_unique_id() const { + return unique_id; +} + ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std::string &_port, bool plaintext_downgrade, bool reuse) : nonblocking(false), sfd(INVALID_SOCKET), ssl(nullptr), last_tick(time(nullptr)), + start(time(nullptr)), hostname(_hostname), port(_port), bytes_out(0), bytes_in(0), plaintext(plaintext_downgrade), timer_handle(0), + unique_id(last_unique_id++), keepalive(reuse), owner(creator) { @@ -254,6 +261,9 @@ void ssl_client::complete_handshake(const socket_events* ev) switch (code) { case SSL_ERROR_NONE: { connected = true; + socket_events se{*ev}; + se.flags = dpp::WANT_READ | dpp::WANT_WRITE | dpp::WANT_ERROR; + owner->socketengine->update_socket(se); break; } case SSL_ERROR_WANT_WRITE: { @@ -297,7 +307,7 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { } bytes_in += r; } else if (!plaintext && connected) { - int r = SSL_read(ssl->ssl,server_to_client_buffer,DPP_BUFSIZE); + int r = SSL_read(ssl->ssl, server_to_client_buffer, DPP_BUFSIZE); int e = SSL_get_error(ssl->ssl,r); switch (e) { @@ -307,6 +317,7 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { buffer.append(server_to_client_buffer, r); if (!this->handle_buffer(buffer)) { + this->close(); return; } else { socket_events se{ev}; @@ -320,16 +331,15 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { /* End of data */ SSL_shutdown(ssl->ssl); return; - break; case SSL_ERROR_WANT_READ: { socket_events se{ev}; se.flags = WANT_READ | WANT_ERROR; owner->socketengine->update_socket(se); break; } - /* We get a WANT_WRITE if we're trying to rehandshake, and we block on a write during that rehandshake. - * We need to wait on the socket to be writeable but initiate the read when it is - */ + /* We get a WANT_WRITE if we're trying to rehandshake, and we block on a write during that rehandshake. + * We need to wait on the socket to be writeable but initiate the read when it is + */ case SSL_ERROR_WANT_WRITE: { socket_events se{ev}; se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; @@ -362,9 +372,49 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { void ssl_client::on_write(socket fd, const struct socket_events& e) { + if (!tcp_connect_done) { + tcp_connect_done = true; + } if (!connected && plaintext) { /* Plaintext sockets connect immediately on first write event */ connected = true; + } else if (!connected) { + /* SSL handshake and session setup */ + + /* Each thread needs a context, but we don't need to make a new one for each connection */ + if (!openssl_context) { + /* We're good to go - hand the fd over to openssl */ + const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */ + + /* Create SSL context */ + openssl_context.reset(SSL_CTX_new(method)); + if (!openssl_context) { + throw dpp::connection_exception(err_ssl_context, "Failed to create SSL client context!"); + } + + /* Do not allow SSL 3.0, TLS 1.0 or 1.1 + * https://www.packetlabs.net/posts/tls-1-1-no-longer-secure/ + */ + if (!SSL_CTX_set_min_proto_version(openssl_context.get(), TLS1_2_VERSION)) { + throw dpp::connection_exception(err_ssl_version, "Failed to set minimum SSL version!"); + } + } + if (!ssl->ssl) { + /* Create SSL session */ + ssl->ssl = SSL_new(openssl_context.get()); + if (ssl->ssl == nullptr) { + throw dpp::connection_exception(err_ssl_new, "SSL_new failed!"); + } + + SSL_set_fd(ssl->ssl, (int) sfd); + SSL_set_connect_state(ssl->ssl); + + /* Server name identification (SNI) */ + SSL_set_tlsext_host_name(ssl->ssl, hostname.c_str()); + } + + /* If this completes, we fall straight through into if (connected) */ + complete_handshake(&e); } if (connected) { @@ -426,44 +476,6 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { } } } - } else { - if (!plaintext) { - /* Each thread needs a context, but we don't need to make a new one for each connection */ - if (!openssl_context) { - /* We're good to go - hand the fd over to openssl */ - const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */ - - /* Create SSL context */ - openssl_context.reset(SSL_CTX_new(method)); - if (!openssl_context) { - throw dpp::connection_exception(err_ssl_context, "Failed to create SSL client context!"); - } - - /* Do not allow SSL 3.0, TLS 1.0 or 1.1 - * https://www.packetlabs.net/posts/tls-1-1-no-longer-secure/ - */ - if (!SSL_CTX_set_min_proto_version(openssl_context.get(), TLS1_2_VERSION)) { - throw dpp::connection_exception(err_ssl_version, "Failed to set minimum SSL version!"); - } - } - if (!ssl->ssl) { - /* Create SSL session */ - ssl->ssl = SSL_new(openssl_context.get()); - if (ssl->ssl == nullptr) { - throw dpp::connection_exception(err_ssl_new, "SSL_new failed!"); - } - - SSL_set_fd(ssl->ssl, (int) sfd); - SSL_set_connect_state(ssl->ssl); - - /* Server name identification (SNI) */ - SSL_set_tlsext_host_name(ssl->ssl, hostname.c_str()); - } - } - } - - if (!connected && !plaintext) { - complete_handshake(&e); } } @@ -473,16 +485,34 @@ void ssl_client::on_error(socket fd, const struct socket_events&, int error_code void ssl_client::read_loop() { - dpp::socket_events events( - sfd, - WANT_READ | WANT_WRITE | WANT_ERROR, - [this](socket fd, const struct socket_events& e) { on_read(fd, e); }, - [this](socket fd, const struct socket_events& e) { on_write(fd, e); }, - [this](socket fd, const struct socket_events& e, int error_code) { on_error(fd, e, error_code); } - ); - owner->socketengine->register_socket(events); - timer_handle = owner->start_timer([this](auto handle) { + auto setup_events = [this]() { + dpp::socket_events events( + sfd, + WANT_READ | WANT_WRITE | WANT_ERROR, + [this](socket fd, const struct socket_events &e) { on_read(fd, e); }, + [this](socket fd, const struct socket_events &e) { on_write(fd, e); }, + [this](socket fd, const struct socket_events &e, int error_code) { on_error(fd, e, error_code); } + ); + owner->socketengine->register_socket(events); + }; + setup_events(); + timer_handle = owner->start_timer([this, setup_events](auto handle) { one_second_timer(); + if (!tcp_connect_done && time(nullptr) > start + 2 && connect_retries < 3) { + /* Retry failed connect(). This can happen even in the best situation with bullet-proof hosting. + * Previously with blocking connect() there was some leniency in this, but now we have to do this + * ourselves. + * + * Retry up to 3 times, 2 seconds between retries. After this, give up and let timeout code + * take the wheel (will likely end with an exception). + */ + close_socket(sfd); + owner->socketengine->delete_socket(sfd); + ssl_client::connect(); + setup_events(); + start = time(nullptr) + 2; + connect_retries++; + } }, 1); } diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index f9cbe25e9f..b2cafa8cc2 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -1144,8 +1144,8 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (g) { set_test(CACHE, true); set_test(VOICECONN, false); - dpp::discord_client* s = bot.get_shard(0); - s->connect_voice(g->id, TEST_VC_ID, false, false); + //dpp::discord_client* s = bot.get_shard(0); + //s->connect_voice(g->id, TEST_VC_ID, false, false); } else { set_test(CACHE, false); @@ -1602,7 +1602,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); bool message_tested = false; - bot.on_message_create([&](const dpp::message_create_t & event) { + bot.on_message_create([&message_tested,&bot,&message_helper,&thread_helper](const dpp::message_create_t & event) { if (event.msg.author.id == bot.me.id) { if (event.msg.content == "test message" && !message_tested) { message_tested = true; @@ -1652,7 +1652,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_message_reaction_add([&](const dpp::message_reaction_add_t & event) { + bot.on_message_reaction_add([&bot,&thread_helper](const dpp::message_reaction_add_t & event) { if (event.reacting_user.id == bot.me.id) { if (event.reacting_emoji.name == "😄") { set_test(REACTEVENT, true); @@ -1664,7 +1664,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_message_reaction_remove([&](const dpp::message_reaction_remove_t & event) { + bot.on_message_reaction_remove([&bot,&thread_helper](const dpp::message_reaction_remove_t & event) { if (event.reacting_user_id == bot.me.id) { if (event.channel_id == thread_helper.thread_id && event.reacting_emoji.name == dpp::unicode_emoji::thread) { set_test(THREAD_MESSAGE_REACT_REMOVE_EVENT, true); @@ -1673,7 +1673,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_message_delete([&](const dpp::message_delete_t & event) { + bot.on_message_delete([&bot,&thread_helper](const dpp::message_delete_t & event) { if (event.channel_id == thread_helper.thread_id) { set_test(THREAD_MESSAGE_DELETE_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_DELETE); @@ -1681,7 +1681,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); bool message_edit_tested = false; - bot.on_message_update([&](const dpp::message_update_t &event) { + bot.on_message_update([&bot,&thread_helper,&message_edit_tested](const dpp::message_update_t &event) { if (event.msg.author == bot.me.id) { if (event.msg.content == "test edit" && !message_edit_tested) { message_edit_tested = true; @@ -1974,22 +1974,22 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b // testing all user flags from https://discord.com/developers/docs/resources/user#user-object-user-flags // they're manually set here because the dpp::user_flags don't match to the discord API, so we can't use them to compare with the raw flags! if ( - u.is_discord_employee() == ((raw_flags & (1 << 0)) != 0) && - u.is_partnered_owner() == ((raw_flags & (1 << 1)) != 0) && - u.has_hypesquad_events() == ((raw_flags & (1 << 2)) != 0) && - u.is_bughunter_1() == ((raw_flags & (1 << 3)) != 0) && - u.is_house_bravery() == ((raw_flags & (1 << 6)) != 0) && - u.is_house_brilliance() == ((raw_flags & (1 << 7)) != 0) && - u.is_house_balance() == ((raw_flags & (1 << 8)) != 0) && - u.is_early_supporter() == ((raw_flags & (1 << 9)) != 0) && - u.is_team_user() == ((raw_flags & (1 << 10)) != 0) && - u.is_bughunter_2() == ((raw_flags & (1 << 14)) != 0) && - u.is_verified_bot() == ((raw_flags & (1 << 16)) != 0) && - u.is_verified_bot_dev() == ((raw_flags & (1 << 17)) != 0) && - u.is_certified_moderator() == ((raw_flags & (1 << 18)) != 0) && - u.is_bot_http_interactions() == ((raw_flags & (1 << 19)) != 0) && - u.is_active_developer() == ((raw_flags & (1 << 22)) != 0) - ) { + u.is_discord_employee() == ((raw_flags & (1 << 0)) != 0) && + u.is_partnered_owner() == ((raw_flags & (1 << 1)) != 0) && + u.has_hypesquad_events() == ((raw_flags & (1 << 2)) != 0) && + u.is_bughunter_1() == ((raw_flags & (1 << 3)) != 0) && + u.is_house_bravery() == ((raw_flags & (1 << 6)) != 0) && + u.is_house_brilliance() == ((raw_flags & (1 << 7)) != 0) && + u.is_house_balance() == ((raw_flags & (1 << 8)) != 0) && + u.is_early_supporter() == ((raw_flags & (1 << 9)) != 0) && + u.is_team_user() == ((raw_flags & (1 << 10)) != 0) && + u.is_bughunter_2() == ((raw_flags & (1 << 14)) != 0) && + u.is_verified_bot() == ((raw_flags & (1 << 16)) != 0) && + u.is_verified_bot_dev() == ((raw_flags & (1 << 17)) != 0) && + u.is_certified_moderator() == ((raw_flags & (1 << 18)) != 0) && + u.is_bot_http_interactions() == ((raw_flags & (1 << 19)) != 0) && + u.is_active_developer() == ((raw_flags & (1 << 22)) != 0) + ) { set_test(USER_GET_FLAGS, true); } else { set_test(USER_GET_FLAGS, false); From 1169268bb63165be25b164d782962d831844743f Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 18 Nov 2024 18:00:45 +0000 Subject: [PATCH 042/112] remove dpp::sync from unit tests, we no longer test it --- src/unittest/test.cpp | 70 ++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index b2cafa8cc2..19dd1970c9 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -2011,48 +2011,42 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b .set_name("voice1") .add_permission_overwrite(TEST_GUILD_ID, dpp::ot_role, 0, dpp::p_view_channel) .set_user_limit(99); - dpp::channel createdChannel; - try { - createdChannel = dpp::sync(&bot, &dpp::cluster::channel_create, channel1); - } catch (dpp::rest_exception &exception) { - set_test(VOICE_CHANNEL_CREATE, false); - } - if (createdChannel.name == channel1.name && - createdChannel.user_limit == 99 && - createdChannel.name == "voice1") { - for (auto overwrite: createdChannel.permission_overwrites) { - if (overwrite.id == TEST_GUILD_ID && overwrite.type == dpp::ot_role && overwrite.deny == dpp::p_view_channel) { - set_test(VOICE_CHANNEL_CREATE, true); - } - } - - // edit the voice channel - createdChannel.set_name("foobar2"); - createdChannel.set_user_limit(2); - for (auto overwrite: createdChannel.permission_overwrites) { - if (overwrite.id == TEST_GUILD_ID) { - overwrite.deny.set(0); - overwrite.allow.set(dpp::p_view_channel); - } + bot.channel_create(channel1, [&bot,channel1](const auto& response) { + if (response.is_error()) { + set_test(VOICE_CHANNEL_CREATE, false); + return; } - try { - dpp::channel edited = dpp::sync(&bot, &dpp::cluster::channel_edit, createdChannel); - if (edited.name == "foobar2" && edited.user_limit == 2) { - set_test(VOICE_CHANNEL_EDIT, true); + dpp::channel createdChannel = std::get(response.value); + if (createdChannel.name == channel1.name && createdChannel.user_limit == 99 && createdChannel.name == "voice1") { + for (auto overwrite: createdChannel.permission_overwrites) { + if (overwrite.id == TEST_GUILD_ID && overwrite.type == dpp::ot_role && overwrite.deny == dpp::p_view_channel) { + set_test(VOICE_CHANNEL_CREATE, true); + break; + } } - } catch (dpp::rest_exception &exception) { - set_test(VOICE_CHANNEL_EDIT, false); - } - // delete the voice channel - try { - dpp::sync(&bot, &dpp::cluster::channel_delete, createdChannel.id); - set_test(VOICE_CHANNEL_DELETE, true); - } catch (dpp::rest_exception &exception) { - bot.log(dpp::ll_warning, "Exception: " + std::string(exception.what())); - set_test(VOICE_CHANNEL_DELETE, false); + // edit the voice channel + createdChannel.set_name("foobar2"); + createdChannel.set_user_limit(2); + for (auto overwrite: createdChannel.permission_overwrites) { + if (overwrite.id == TEST_GUILD_ID) { + overwrite.deny.set(0); + overwrite.allow.set(dpp::p_view_channel); + } + } + bot.channel_edit(createdChannel, [&bot,createdChannel](const auto& response) { + if (response.is_error()) { + set_test(VOICE_CHANNEL_EDIT, false); + return; + } + dpp::channel edited = std::get(response.value); + set_test(VOICE_CHANNEL_EDIT, (edited.name == "foobar2" && edited.user_limit == 2)); + bot.channel_delete(createdChannel.id,[](const auto& response) { + set_test(VOICE_CHANNEL_DELETE, !response.is_error()); + }); + }); } - } + }); } set_test(FORUM_CREATION, false); From dc8bb5d3f7454c948c622e51698e07afedde2fc3 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 01:46:52 +0000 Subject: [PATCH 043/112] fix kqueue to actually work on freebsd --- .cspell.json | 3 ++ src/dpp/socketengines/kqueue-facade.h | 69 +++++++++++++++++++++++++ src/dpp/socketengines/kqueue.cpp | 72 +++++++++------------------ 3 files changed, 96 insertions(+), 48 deletions(-) create mode 100644 src/dpp/socketengines/kqueue-facade.h diff --git a/.cspell.json b/.cspell.json index ebf8da3dab..82c89a6eb2 100644 --- a/.cspell.json +++ b/.cspell.json @@ -2,6 +2,9 @@ "version": "0.2", "language": "en-GB", "words": [ + "EVFILT", + "fflags", + "udata", "blurple", "featurable", "libdpp", diff --git a/src/dpp/socketengines/kqueue-facade.h b/src/dpp/socketengines/kqueue-facade.h new file mode 100644 index 0000000000..f11c62c0d2 --- /dev/null +++ b/src/dpp/socketengines/kqueue-facade.h @@ -0,0 +1,69 @@ +/************************************************************************************ + * + * 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 actual kqueue + */ +#include +#include + +#if defined __NetBSD__ && __NetBSD_Version__ <= 999001400 + #define CAST_TYPE intptr_t +#else + #define CAST_TYPE void* +#endif + +#ifndef EV_ONESHOT + +#include + +/** + * This is a facade for kqueue(), a freebsd-only event mechanism. + * It is not documented here and only exists so that when editing the file on Linux + * the linter does not go nuts. For actual documentation of kqueue, + * see the relevant man pages. + */ + +int kqueue(); + +int kevent(int kq, const struct kevent *changelist, int number_changes, struct kevent *event_list, std::size_t number_events, const struct timespec *timeout); + +#define EV_SET(kev, ident, filter, flags, fflags, data, udata) + +struct kevent { + uintptr_t ident; /* identifier for this event */ + short filter; /* filter for event */ + uint16_t flags; /* action flags for kqueue */ + uint32_t fflags; /* filter flag value */ + intptr_t data; /* filter data value */ + void *udata; /* opaque user data identifier */ +}; + +#define EV_ADD 0x0001 +#define EV_DELETE 0x0002 +#define EV_ONESHOT 0x0010 +#define EV_EOF 0x8000 +#define EV_ERROR 0x4000 +#define EVFILT_READ (-1) +#define EVFILT_WRITE (-2) + +#endif diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 05949a9388..635d97b598 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -24,24 +24,15 @@ #include #include #include -#include -#include #include #include - -#if defined __NetBSD__ && __NetBSD_Version__ <= 999001400 - #define CAST_TYPE intptr_t -#else - #define CAST_TYPE void* -#endif +#include "kqueue-facade.h" namespace dpp { struct socket_engine_kqueue : public socket_engine_base { int kqueue_handle{INVALID_SOCKET}; - unsigned int change_pos = 0; - std::vector change_list; std::vector ke_list; socket_engine_kqueue(const socket_engine_kqueue&) = delete; @@ -50,7 +41,6 @@ struct socket_engine_kqueue : public socket_engine_base { socket_engine_kqueue& operator=(socket_engine_kqueue&&) = delete; explicit socket_engine_kqueue(cluster* creator) : socket_engine_base(creator), kqueue_handle(kqueue()) { - change_list.resize(8); ke_list.resize(16); if (kqueue_handle == -1) { throw dpp::connection_exception("Failed to initialise kqueue()"); @@ -63,20 +53,12 @@ struct socket_engine_kqueue : public socket_engine_base { } } - struct kevent* get_change_kevent() - { - if (change_pos >= change_list.size()) { - change_list.resize(change_list.size() * 2); - } - return &change_list[change_pos++]; - } - void process_events() final { struct timespec ts{}; ts.tv_sec = 1; - int i = kevent(kqueue_handle, &change_list.front(), change_pos, &ke_list.front(), static_cast(ke_list.size()), &ts); - change_pos = 0; + //int i = kevent(kqueue_handle, &change_list.front(), change_pos, &ke_list.front(), static_cast(ke_list.size()), &ts); + int i = kevent(kqueue_handle, NULL, 0, &ke_list.front(), static_cast(ke_list.size()), &ts); if (i < 0) { return; @@ -90,7 +72,7 @@ struct socket_engine_kqueue : public socket_engine_base { } const short filter = kev.filter; - if (kev.flags & EV_EOF) { + if (kev.flags & EV_EOF || kev.flags & EV_ERROR) { eh->on_error(kev.ident, *eh, kev.fflags); continue; } @@ -105,29 +87,19 @@ struct socket_engine_kqueue : public socket_engine_base { } } - void set_event_write_flags(dpp::socket fd, socket_events* eh, uint8_t old_mask, uint8_t new_mask) - { - if (((new_mask & WANT_WRITE) != 0) && ((old_mask & WANT_WRITE) == 0)) - { - struct kevent* ke = get_change_kevent(); - EV_SET(ke, fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(eh)); - } - else if (((old_mask & WANT_WRITE) != 0) && ((new_mask & WANT_WRITE) == 0)) - { - struct kevent* ke = get_change_kevent(); - EV_SET(ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); - } - } - bool register_socket(const socket_events& e) final { bool r = socket_engine_base::register_socket(e); if (r) { - struct kevent* ke = get_change_kevent(); + struct kevent ke; socket_events* se = fds.find(e.fd)->second.get(); if ((se->flags & WANT_READ) != 0) { - EV_SET(ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); + EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); + int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); + } + if ((se->flags & WANT_WRITE) != 0) { + EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); + int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); } - set_event_write_flags(e.fd, se, 0, e.flags); if (fds.size() * 2 > ke_list.size()) { ke_list.resize(fds.size() * 2); } @@ -138,12 +110,16 @@ struct socket_engine_kqueue : public socket_engine_base { bool update_socket(const socket_events& e) final { bool r = socket_engine_base::update_socket(e); if (r) { - struct kevent* ke = get_change_kevent(); socket_events* se = fds.find(e.fd)->second.get(); - if ((se->flags & WANT_READ) != 0) { - EV_SET(ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); + struct kevent ke; + if ((e.flags & WANT_READ) != 0) { + EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); + int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); + } + if ((e.flags & WANT_WRITE) != 0) { + EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); + int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); } - set_event_write_flags(e.fd, se, 0, e.flags); if (fds.size() * 2 > ke_list.size()) { ke_list.resize(fds.size() * 2); } @@ -156,12 +132,12 @@ struct socket_engine_kqueue : public socket_engine_base { bool remove_socket(dpp::socket fd) final { bool r = socket_engine_base::remove_socket(fd); if (r) { - struct kevent* ke = get_change_kevent(); - EV_SET(ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); - + struct kevent ke; + EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); // Then remove the read filter. - ke = get_change_kevent(); - EV_SET(ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); } return r; } From 2dc084a7e13ba37a5338f5f01ff707f8b382f7b5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 02:19:04 +0000 Subject: [PATCH 044/112] kqueue tidyup --- src/dpp/socketengines/kqueue.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 635d97b598..188c4c30fa 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -57,9 +57,7 @@ struct socket_engine_kqueue : public socket_engine_base { struct timespec ts{}; ts.tv_sec = 1; - //int i = kevent(kqueue_handle, &change_list.front(), change_pos, &ke_list.front(), static_cast(ke_list.size()), &ts); int i = kevent(kqueue_handle, NULL, 0, &ke_list.front(), static_cast(ke_list.size()), &ts); - if (i < 0) { return; } @@ -94,14 +92,14 @@ struct socket_engine_kqueue : public socket_engine_base { socket_events* se = fds.find(e.fd)->second.get(); if ((se->flags & WANT_READ) != 0) { EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); - int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); + kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); } if ((se->flags & WANT_WRITE) != 0) { EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); - int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); + kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); } - if (fds.size() * 2 > ke_list.size()) { - ke_list.resize(fds.size() * 2); + if (fds.size() * 3 > ke_list.size()) { + ke_list.resize(fds.size() * 3); } } return r; @@ -114,14 +112,14 @@ struct socket_engine_kqueue : public socket_engine_base { struct kevent ke; if ((e.flags & WANT_READ) != 0) { EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); - int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); + kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); } if ((e.flags & WANT_WRITE) != 0) { EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); - int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); + kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); } - if (fds.size() * 2 > ke_list.size()) { - ke_list.resize(fds.size() * 2); + if (fds.size() * 3 > ke_list.size()) { + ke_list.resize(fds.size() * 3); } } return r; @@ -134,10 +132,9 @@ struct socket_engine_kqueue : public socket_engine_base { if (r) { struct kevent ke; EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); - int i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); - // Then remove the read filter. + kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - i = kevent(kqueue_handle, &ke, 1, 0, 0, NULL); + kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); } return r; } From e3e8870e0983d6f18fcd92afe1e8bab640e6d6e3 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 12:51:11 +0000 Subject: [PATCH 045/112] sanity checks --- src/dpp/sslclient.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 934e1e0103..3e9d24862b 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -255,6 +255,9 @@ void ssl_client::log(dpp::loglevel severity, const std::string &msg) const void ssl_client::complete_handshake(const socket_events* ev) { + if (!ssl || !ssl->ssl) { + return; + } auto status = SSL_do_handshake(ssl->ssl); if (status != 1) { auto code = SSL_get_error(ssl->ssl, status); @@ -306,7 +309,7 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { return; } bytes_in += r; - } else if (!plaintext && connected) { + } else if (!plaintext && connected && ssl && ssl->ssl) { int r = SSL_read(ssl->ssl, server_to_client_buffer, DPP_BUFSIZE); int e = SSL_get_error(ssl->ssl,r); @@ -440,7 +443,7 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { se.flags = WANT_READ | WANT_WRITE | WANT_ERROR; owner->socketengine->update_socket(se); } - } else { + } else if (ssl && ssl->ssl) { int r = SSL_write(ssl->ssl, client_to_server_buffer + client_to_server_offset, (int)client_to_server_length); int err = SSL_get_error(ssl->ssl, r); From d9b82a40bd1dc14dd10901e324664a6f8fa8a24f Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 13:16:34 +0000 Subject: [PATCH 046/112] safety check handlers --- src/dpp/socketengines/epoll.cpp | 20 ++++++++++++++++---- src/dpp/socketengines/kqueue.cpp | 12 +++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 9bf3b1cc76..90d4ed11a3 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -81,13 +81,19 @@ struct socket_engine_epoll : public socket_engine_base { epoll_event ev = events[j]; auto* const eh = static_cast(ev.data.ptr); + if (!eh) { + continue; + } + const int fd = eh->fd; if (fd == INVALID_SOCKET) { continue; } if ((ev.events & EPOLLHUP) != 0U) { - eh->on_error(fd, *eh, EPIPE); + if (eh->on_error) { + eh->on_error(fd, *eh, EPIPE); + } continue; } @@ -97,17 +103,23 @@ struct socket_engine_epoll : public socket_engine_base { if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { errcode = errno; } - eh->on_error(fd, *eh, errcode); + if (eh->on_error) { + eh->on_error(fd, *eh, errcode); + } continue; } if ((ev.events & EPOLLOUT) != 0U) { eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_WRITE); - eh->on_write(fd, *eh); + if (eh->on_write) { + eh->on_write(fd, *eh); + } } if ((ev.events & EPOLLIN) != 0U) { - eh->on_read(fd, *eh); + if (eh->on_read) { + eh->on_read(fd, *eh); + } } } prune(); diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 188c4c30fa..e3204c3732 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -71,16 +71,22 @@ struct socket_engine_kqueue : public socket_engine_base { const short filter = kev.filter; if (kev.flags & EV_EOF || kev.flags & EV_ERROR) { - eh->on_error(kev.ident, *eh, kev.fflags); + if (eh->on_error) { + eh->on_error(kev.ident, *eh, kev.fflags); + } continue; } if (filter == EVFILT_WRITE) { const int bits_to_clr = WANT_WRITE; eh->flags &= ~bits_to_clr; - eh->on_write(kev.ident, *eh); + if (eh->on_write) { + eh->on_write(kev.ident, *eh); + } } else if (filter == EVFILT_READ) { - eh->on_read(kev.ident, *eh); + if (eh->on_read) { + eh->on_read(kev.ident, *eh); + } } } } From 366f2dcb50a5327282e6537d48bf6cdc97686ac3 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 18:40:12 +0000 Subject: [PATCH 047/112] add prune --- src/dpp/socketengines/kqueue.cpp | 1 + src/dpp/socketengines/poll.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index e3204c3732..dda636b145 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -89,6 +89,7 @@ struct socket_engine_kqueue : public socket_engine_base { } } } + prune(); } bool register_socket(const socket_events& e) final { diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 6b9b09eb4f..f0dd308b85 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -99,6 +99,7 @@ struct socket_engine_poll : public socket_engine_base { eh->on_write(fd, *eh); } } + prune(); } bool register_socket(const socket_events& e) final { From a819c5fac3697ac0759c83a9b1c8e65360e94d17 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 19:01:08 +0000 Subject: [PATCH 048/112] error handling --- src/dpp/socketengine.cpp | 14 ++++++--- src/dpp/socketengines/epoll.cpp | 52 ++++++++++++++++++-------------- src/dpp/socketengines/kqueue.cpp | 36 +++++++++++++--------- src/dpp/socketengines/poll.cpp | 46 ++++++++++++++++------------ src/dpp/sslclient.cpp | 4 ++- 5 files changed, 88 insertions(+), 64 deletions(-) diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index bb611b5828..10301d4ec6 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -79,11 +79,15 @@ void socket_engine_base::prune() { to_delete_count = 0; } if (time(nullptr) != last_time) { - /* Every minute, rehash all cache containers. - * We do this from the socket engine now, not from - * shard 0, so no need to run shards to have timers! - */ - owner->tick_timers(); + try { + /* Every minute, rehash all cache containers. + * We do this from the socket engine now, not from + * shard 0, so no need to run shards to have timers! + */ + owner->tick_timers(); + } catch (const std::exception& e) { + owner->log(dpp::ll_error, "Uncaught exception in tick_timers: " + std::string(e.what())); + } if ((time(nullptr) % 60) == 0) { dpp::garbage_collection(); diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 90d4ed11a3..77b2b34c71 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -90,36 +90,42 @@ struct socket_engine_epoll : public socket_engine_base { continue; } - if ((ev.events & EPOLLHUP) != 0U) { - if (eh->on_error) { - eh->on_error(fd, *eh, EPIPE); - } - continue; - } + try { - if ((ev.events & EPOLLERR) != 0U) { - socklen_t codesize = sizeof(int); - int errcode{}; - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { - errcode = errno; + if ((ev.events & EPOLLHUP) != 0U) { + if (eh->on_error) { + eh->on_error(fd, *eh, EPIPE); + } + continue; } - if (eh->on_error) { - eh->on_error(fd, *eh, errcode); + + if ((ev.events & EPOLLERR) != 0U) { + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) { + errcode = errno; + } + if (eh->on_error) { + eh->on_error(fd, *eh, errcode); + } + continue; } - continue; - } - if ((ev.events & EPOLLOUT) != 0U) { - eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_WRITE); - if (eh->on_write) { - eh->on_write(fd, *eh); + if ((ev.events & EPOLLOUT) != 0U) { + eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_WRITE); + if (eh->on_write) { + eh->on_write(fd, *eh); + } } - } - if ((ev.events & EPOLLIN) != 0U) { - if (eh->on_read) { - eh->on_read(fd, *eh); + if ((ev.events & EPOLLIN) != 0U) { + if (eh->on_read) { + eh->on_read(fd, *eh); + } } + + } catch (const std::exception& e) { + eh->on_error(fd, *eh, 0); } } prune(); diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index dda636b145..0f162d3fc1 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -69,24 +69,30 @@ struct socket_engine_kqueue : public socket_engine_base { continue; } - const short filter = kev.filter; - if (kev.flags & EV_EOF || kev.flags & EV_ERROR) { - if (eh->on_error) { - eh->on_error(kev.ident, *eh, kev.fflags); + try { + + const short filter = kev.filter; + if (kev.flags & EV_EOF || kev.flags & EV_ERROR) { + if (eh->on_error) { + eh->on_error(kev.ident, *eh, kev.fflags); + } + continue; } - continue; - } - if (filter == EVFILT_WRITE) { - const int bits_to_clr = WANT_WRITE; - eh->flags &= ~bits_to_clr; - if (eh->on_write) { - eh->on_write(kev.ident, *eh); + if (filter == EVFILT_WRITE) { + const int bits_to_clr = WANT_WRITE; + eh->flags &= ~bits_to_clr; + if (eh->on_write) { + eh->on_write(kev.ident, *eh); + } } - } - else if (filter == EVFILT_READ) { - if (eh->on_read) { - eh->on_read(kev.ident, *eh); + else if (filter == EVFILT_READ) { + if (eh->on_read) { + eh->on_read(kev.ident, *eh); + } } + + } catch (const std::exception& e) { + eh->on_error(kev.ident, *eh, 0); } } prune(); diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index f0dd308b85..197fdf6640 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -73,30 +73,36 @@ struct socket_engine_poll : public socket_engine_base { } socket_events* eh = iter->second.get(); - if ((revents & POLLHUP) != 0) { - eh->on_error(fd, *eh, 0); - continue; - } + try { - if ((revents & POLLERR) != 0) { - socklen_t codesize = sizeof(int); - int errcode{}; - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&errcode, &codesize) < 0) { - errcode = errno; + if ((revents & POLLHUP) != 0) { + eh->on_error(fd, *eh, 0); + continue; } - eh->on_error(fd, *eh, errcode); - continue; - } - if ((revents & POLLIN) != 0) { - eh->on_read(fd, *eh); - } + if ((revents & POLLERR) != 0) { + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&errcode, &codesize) < 0) { + errcode = errno; + } + eh->on_error(fd, *eh, errcode); + continue; + } + + if ((revents & POLLIN) != 0) { + eh->on_read(fd, *eh); + } - if ((revents & POLLOUT) != 0) { - int mask = eh->flags; - mask &= ~WANT_WRITE; - eh->flags = mask; - eh->on_write(fd, *eh); + if ((revents & POLLOUT) != 0) { + int mask = eh->flags; + mask &= ~WANT_WRITE; + eh->flags = mask; + eh->on_write(fd, *eh); + } + + } catch (const std::exception& e) { + eh->on_error(fd, *eh, 0); } } prune(); diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 3e9d24862b..c13dc70db3 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -483,7 +483,9 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { } void ssl_client::on_error(socket fd, const struct socket_events&, int error_code) { - throw dpp::connection_exception(err_socket_error, strerror(errno)); + if (sfd != INVALID_SOCKET) { + this->close(); + } } void ssl_client::read_loop() From 23cf1236cf9b67d3696b2adb23384575f10bf2fd Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 19:10:49 +0000 Subject: [PATCH 049/112] error handling --- src/dpp/sslclient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index c13dc70db3..24ff575aca 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -484,7 +484,7 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { void ssl_client::on_error(socket fd, const struct socket_events&, int error_code) { if (sfd != INVALID_SOCKET) { - this->close(); + ssl_client::close(); } } From a2a21c9d95887062d9e27323b3d3d49331eaaeb5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 19:53:23 +0000 Subject: [PATCH 050/112] mutexing of timer list --- include/dpp/cluster.h | 5 +++++ src/dpp/cluster.cpp | 14 +++++++++----- src/dpp/cluster/timer.cpp | 1 - 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index ab43fad341..c30c378735 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -185,6 +185,11 @@ class DPP_EXPORT cluster { */ std::unique_ptr engine_thread{nullptr}; + /** + * @brief Protection mutex for timers + */ + std::mutex timer_guard; + public: /** * @brief Current bot token for all shards on this cluster and all commands sent via HTTP diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 7daa0735fa..0ff9bf7b2a 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -313,12 +313,16 @@ void cluster::start(bool return_after) { void cluster::shutdown() { /* Signal termination */ terminating = true; - /* Free memory for active timers */ - for (auto & t : timer_list) { - delete t.second; + { + std::lock_guard l(timer_guard); + log(ll_info, "Terminating " + std::to_string(timer_list.size()) + "timers"); + /* Free memory for active timers */ + for (auto &t: timer_list) { + delete t.second; + } + timer_list.clear(); + next_timer.clear(); } - timer_list.clear(); - next_timer.clear(); /* Terminate shards */ for (const auto& sh : shards) { log(ll_info, "Terminating shard id " + std::to_string(sh.second->shard_id)); diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index c255626d3b..961935c3a7 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -25,7 +25,6 @@ namespace dpp { timer lasthandle = 1; -std::mutex timer_guard; timer cluster::start_timer(timer_callback_t on_tick, uint64_t frequency, timer_callback_t on_stop) { std::lock_guard l(timer_guard); From 6a365146a4dbe35447701c9d4982cf200ebab05e Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 21:15:41 +0000 Subject: [PATCH 051/112] dont resize the ke_list --- src/dpp/socketengines/kqueue.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 0f162d3fc1..d15b2574f5 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -33,7 +34,7 @@ namespace dpp { struct socket_engine_kqueue : public socket_engine_base { int kqueue_handle{INVALID_SOCKET}; - std::vector ke_list; + std::array ke_list; socket_engine_kqueue(const socket_engine_kqueue&) = delete; socket_engine_kqueue(socket_engine_kqueue&&) = delete; @@ -41,7 +42,6 @@ struct socket_engine_kqueue : public socket_engine_base { socket_engine_kqueue& operator=(socket_engine_kqueue&&) = delete; explicit socket_engine_kqueue(cluster* creator) : socket_engine_base(creator), kqueue_handle(kqueue()) { - ke_list.resize(16); if (kqueue_handle == -1) { throw dpp::connection_exception("Failed to initialise kqueue()"); } @@ -57,7 +57,7 @@ struct socket_engine_kqueue : public socket_engine_base { struct timespec ts{}; ts.tv_sec = 1; - int i = kevent(kqueue_handle, NULL, 0, &ke_list.front(), static_cast(ke_list.size()), &ts); + int i = kevent(kqueue_handle, NULL, 0, ke_list.data(), static_cast(ke_list.size()), &ts); if (i < 0) { return; } @@ -111,9 +111,6 @@ struct socket_engine_kqueue : public socket_engine_base { EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); } - if (fds.size() * 3 > ke_list.size()) { - ke_list.resize(fds.size() * 3); - } } return r; } @@ -131,9 +128,6 @@ struct socket_engine_kqueue : public socket_engine_base { EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); } - if (fds.size() * 3 > ke_list.size()) { - ke_list.resize(fds.size() * 3); - } } return r; } From 276e2ffd5e37db68434448da3c24886ee7169560 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 22:22:07 +0000 Subject: [PATCH 052/112] fixes for cluster destructor on freebsd --- src/dpp/cluster.cpp | 9 ++------ src/dpp/socketengines/poll.cpp | 6 ++++++ src/dpp/sslclient.cpp | 38 ++++++++++++++++++---------------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 0ff9bf7b2a..a1024fb28c 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -152,12 +152,9 @@ cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, cluster::~cluster() { - this->shutdown(); delete rest; delete raw_rest; -#ifdef _WIN32 - WSACleanup(); -#endif + this->shutdown(); } request_queue* cluster::get_rest() { @@ -315,7 +312,6 @@ void cluster::shutdown() { terminating = true; { std::lock_guard l(timer_guard); - log(ll_info, "Terminating " + std::to_string(timer_list.size()) + "timers"); /* Free memory for active timers */ for (auto &t: timer_list) { delete t.second; @@ -325,13 +321,12 @@ void cluster::shutdown() { } /* Terminate shards */ for (const auto& sh : shards) { - log(ll_info, "Terminating shard id " + std::to_string(sh.second->shard_id)); delete sh.second; } + shards.clear(); if (engine_thread) { engine_thread->join(); } - shards.clear(); } snowflake cluster::get_dm_channel(snowflake user_id) { diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 197fdf6640..148e01a716 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -108,6 +108,12 @@ struct socket_engine_poll : public socket_engine_base { prune(); } +#if _WIN32 + ~socket_engine_poll() override { + WSACleanup(); + } +#endif + bool register_socket(const socket_events& e) final { bool r = socket_engine_base::register_socket(e); if (r) { diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 24ff575aca..a185e7b4b1 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -501,24 +501,26 @@ void ssl_client::read_loop() owner->socketengine->register_socket(events); }; setup_events(); - timer_handle = owner->start_timer([this, setup_events](auto handle) { - one_second_timer(); - if (!tcp_connect_done && time(nullptr) > start + 2 && connect_retries < 3) { - /* Retry failed connect(). This can happen even in the best situation with bullet-proof hosting. - * Previously with blocking connect() there was some leniency in this, but now we have to do this - * ourselves. - * - * Retry up to 3 times, 2 seconds between retries. After this, give up and let timeout code - * take the wheel (will likely end with an exception). - */ - close_socket(sfd); - owner->socketengine->delete_socket(sfd); - ssl_client::connect(); - setup_events(); - start = time(nullptr) + 2; - connect_retries++; - } - }, 1); + if (!timer_handle) { + timer_handle = owner->start_timer([this, setup_events](auto handle) { + one_second_timer(); + if (!tcp_connect_done && time(nullptr) > start + 2 && connect_retries < 3) { + /* Retry failed connect(). This can happen even in the best situation with bullet-proof hosting. + * Previously with blocking connect() there was some leniency in this, but now we have to do this + * ourselves. + * + * Retry up to 3 times, 2 seconds between retries. After this, give up and let timeout code + * take the wheel (will likely end with an exception). + */ + close_socket(sfd); + owner->socketengine->delete_socket(sfd); + ssl_client::connect(); + setup_events(); + start = time(nullptr) + 2; + connect_retries++; + } + }, 1); + } } uint64_t ssl_client::get_bytes_out() From 317717ee380e74679776fcb306e3296c40bc2f00 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 19 Nov 2024 22:50:07 +0000 Subject: [PATCH 053/112] destroy thread for socket loop before trying to destroy anything that may be within it --- src/dpp/cluster.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index a1024fb28c..c09616b9b1 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -310,6 +310,9 @@ void cluster::start(bool return_after) { void cluster::shutdown() { /* Signal termination */ terminating = true; + if (engine_thread) { + engine_thread->join(); + } { std::lock_guard l(timer_guard); /* Free memory for active timers */ @@ -324,9 +327,6 @@ void cluster::shutdown() { delete sh.second; } shards.clear(); - if (engine_thread) { - engine_thread->join(); - } } snowflake cluster::get_dm_channel(snowflake user_id) { From 93fc814ecd44c5a66690637342e4ef424621728a Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 20 Nov 2024 21:22:39 +0000 Subject: [PATCH 054/112] fixes for poll on windows --- doxygen-awesome-css | 2 +- include/dpp/dns.h | 6 +- include/dpp/socketengine.h | 6 +- include/dpp/sslclient.h | 4 +- include/dpp/thread_pool.h | 16 +++-- mlspp/include/namespace.h | 2 +- src/dpp/dns.cpp | 3 +- src/dpp/socketengine.cpp | 8 +-- src/dpp/socketengines/epoll.cpp | 4 +- src/dpp/socketengines/kqueue.cpp | 4 +- src/dpp/socketengines/poll.cpp | 92 ++++++++++++++------------- src/dpp/thread_pool.cpp | 12 ++-- src/dpp/voice/enabled/discover_ip.cpp | 4 ++ src/sockettest/socket.cpp | 20 +++++- 14 files changed, 106 insertions(+), 77 deletions(-) diff --git a/doxygen-awesome-css b/doxygen-awesome-css index af1d9030b3..c6568ebc70 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit af1d9030b3ffa7b483fa9997a7272fb12af6af4c +Subproject commit c6568ebc70adf9fb0fb6c1745737ae6945576813 diff --git a/include/dpp/dns.h b/include/dpp/dns.h index 48cebcd563..b1a6814065 100644 --- a/include/dpp/dns.h +++ b/include/dpp/dns.h @@ -40,7 +40,7 @@ namespace dpp { * @brief Represents a cached DNS result. * Used by the ssl_client class to store cached copies of dns lookups. */ - struct dns_cache_entry { + struct DPP_EXPORT dns_cache_entry { /** * @brief Resolved address metadata */ @@ -93,5 +93,5 @@ namespace dpp { * @return dns_cache_entry* First IP address associated with the hostname DNS record * @throw dpp::connection_exception On failure to resolve hostname */ - const dns_cache_entry* resolve_hostname(const std::string& hostname, const std::string& port); -} + DPP_EXPORT const dns_cache_entry *resolve_hostname(const std::string &hostname, const std::string &port); + } diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index b793cc098e..c7040845c4 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -83,7 +83,7 @@ using socket_error_event = std::function create_socket_engine(class cluster* creator); +DPP_EXPORT std::unique_ptr create_socket_engine(class cluster *creator); #ifndef _WIN32 void set_signal_handler(int signal); diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 5266e70ab0..0d1bb693e7 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -54,7 +54,7 @@ typedef std::function socket_notification_t; * @param sfd Socket to close * @return false on error, true on success */ -bool close_socket(dpp::socket sfd); +DPP_EXPORT bool close_socket(dpp::socket sfd); /** * @brief Set a socket to blocking or non-blocking IO @@ -63,7 +63,7 @@ bool close_socket(dpp::socket sfd); * @param non_blocking should socket be non-blocking? * @return false on error, true on success */ -bool set_nonblocking(dpp::socket sockfd, bool non_blocking); +DPP_EXPORT bool set_nonblocking(dpp::socket sockfd, bool non_blocking); /* You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each. * SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as diff --git a/include/dpp/thread_pool.h b/include/dpp/thread_pool.h index 76398d1daf..ef15ab33ea 100644 --- a/include/dpp/thread_pool.h +++ b/include/dpp/thread_pool.h @@ -28,27 +28,29 @@ #include #include +namespace dpp { + using work_unit = std::function; /** * A task within a thread pool. A simple lambda that accepts no parameters and returns void. */ -struct thread_pool_task { +struct DPP_EXPORT thread_pool_task { int priority; work_unit function; }; -struct thread_pool_task_comparator { - bool operator()(const thread_pool_task &a, const thread_pool_task &b) { - return a.priority < b.priority; - }; +struct DPP_EXPORT thread_pool_task_comparator { + bool operator()(const thread_pool_task &a, const thread_pool_task &b) { + return a.priority < b.priority; + }; }; /** * @brief A thread pool contains 1 or more worker threads which accept thread_pool_task lambadas * into a queue, which is processed in-order by whichever thread is free. */ -struct thread_pool { +struct DPP_EXPORT thread_pool { std::vector threads; std::priority_queue, thread_pool_task_comparator> tasks; std::mutex queue_mutex; @@ -59,3 +61,5 @@ struct thread_pool { ~thread_pool(); void enqueue(thread_pool_task task); }; + +} \ No newline at end of file diff --git a/mlspp/include/namespace.h b/mlspp/include/namespace.h index d07ba5ee94..43a5121ccb 100755 --- a/mlspp/include/namespace.h +++ b/mlspp/include/namespace.h @@ -1,4 +1,4 @@ #pragma once // Configurable top-level MLS namespace -#define MLS_NAMESPACE ../include/dpp/mlspp/mls +#define MLS_NAMESPACE mls diff --git a/src/dpp/dns.cpp b/src/dpp/dns.cpp index 42aed3ce8f..a927d68747 100644 --- a/src/dpp/dns.cpp +++ b/src/dpp/dns.cpp @@ -54,8 +54,7 @@ socket dns_cache_entry::make_connecting_socket() const { return ::socket(addr.ai_family, addr.ai_socktype, addr.ai_protocol); } -const dns_cache_entry* resolve_hostname(const std::string& hostname, const std::string& port) -{ +const dns_cache_entry *resolve_hostname(const std::string &hostname, const std::string &port) { addrinfo hints, *addrs; dns_cache_t::const_iterator iter; time_t now = time(nullptr); diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index 10301d4ec6..4cd7c7b8f6 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -31,7 +31,7 @@ namespace dpp { bool socket_engine_base::register_socket(const socket_events &e) { - if (e.fd > INVALID_SOCKET && fds.find(e.fd) == fds.end()) { + if (e.fd != INVALID_SOCKET && fds.find(e.fd) == fds.end()) { fds.emplace(e.fd, std::make_unique(e)); return true; } @@ -39,7 +39,7 @@ bool socket_engine_base::register_socket(const socket_events &e) { } bool socket_engine_base::update_socket(const socket_events &e) { - if (e.fd > INVALID_SOCKET && fds.find(e.fd) != fds.end()) { + if (e.fd != INVALID_SOCKET && fds.find(e.fd) != fds.end()) { auto iter = fds.find(e.fd); *(iter->second) = e; return true; @@ -48,7 +48,7 @@ bool socket_engine_base::update_socket(const socket_events &e) { } socket_engine_base::socket_engine_base(cluster* creator) : owner(creator) { -#ifndef WIN32 +#ifndef _WIN32 set_signal_handler(SIGALRM); set_signal_handler(SIGXFSZ); set_signal_handler(SIGCHLD); @@ -108,7 +108,7 @@ bool socket_engine_base::delete_socket(dpp::socket fd) { } bool socket_engine_base::remove_socket(dpp::socket fd) { - return false; + return true; } } diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 77b2b34c71..4b7ee1a7c7 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -49,7 +49,7 @@ int modify_event(int epoll_handle, socket_events* eh, int new_events) { return new_events; } -struct socket_engine_epoll : public socket_engine_base { +struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { int epoll_handle{INVALID_SOCKET}; static const int epoll_hint = 128; @@ -196,7 +196,7 @@ struct socket_engine_epoll : public socket_engine_base { } }; -std::unique_ptr create_socket_engine(cluster* creator) { +DPP_EXPORT std::unique_ptr create_socket_engine(cluster *creator) { return std::make_unique(creator); } diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index d15b2574f5..bc93272714 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -31,7 +31,7 @@ namespace dpp { -struct socket_engine_kqueue : public socket_engine_base { +struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { int kqueue_handle{INVALID_SOCKET}; std::array ke_list; @@ -147,7 +147,7 @@ struct socket_engine_kqueue : public socket_engine_base { } }; -std::unique_ptr create_socket_engine(cluster* creator) { +DPP_EXPORT std::unique_ptr create_socket_engine(cluster *creator) { return std::make_unique(creator); } diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index 148e01a716..d2e0da1a82 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -44,7 +44,7 @@ namespace dpp { -struct socket_engine_poll : public socket_engine_base { +struct DPP_EXPORT socket_engine_poll : public socket_engine_base { /* We store the pollfds as a vector. This means that insertion, deletion and updating * are comparatively slow O(n), but these operations don't happen too often. Obtaining the @@ -53,56 +53,68 @@ struct socket_engine_poll : public socket_engine_base { * anyway. */ std::vector poll_set; + pollfd out_set[FD_SETSIZE]{0}; void process_events() final { const int poll_delay = 1000; - int i = poll(poll_set.data(), static_cast(poll_set.size()), poll_delay); - int processed = 0; - for (size_t index = 0; index < poll_set.size() && processed < i; index++) { - const int fd = poll_set[index].fd; - const short revents = poll_set[index].revents; - - if (revents > 0) { - processed++; + if (poll_set.empty()) { + /* On many platforms, it is not possible to wait on an empty set */ + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } else { + if (poll_set.size() > FD_SETSIZE) { + throw dpp::connection_exception("poll() does not support more than FD_SETSIZE active sockets at once!"); } - auto iter = fds.find(fd); - if (iter == fds.end()) { - continue; - } - socket_events* eh = iter->second.get(); + std::copy(poll_set.begin(), poll_set.end(), out_set); - try { + int i = poll(out_set, static_cast(poll_set.size()), poll_delay); + int processed = 0; - if ((revents & POLLHUP) != 0) { - eh->on_error(fd, *eh, 0); - continue; + for (size_t index = 0; index < poll_set.size() && processed < i; index++) { + const int fd = out_set[index].fd; + const short revents = out_set[index].revents; + + if (revents > 0) { + processed++; } - if ((revents & POLLERR) != 0) { - socklen_t codesize = sizeof(int); - int errcode{}; - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&errcode, &codesize) < 0) { - errcode = errno; - } - eh->on_error(fd, *eh, errcode); + auto iter = fds.find(fd); + if (iter == fds.end()) { continue; } + socket_events *eh = iter->second.get(); - if ((revents & POLLIN) != 0) { - eh->on_read(fd, *eh); - } + try { - if ((revents & POLLOUT) != 0) { - int mask = eh->flags; - mask &= ~WANT_WRITE; - eh->flags = mask; - eh->on_write(fd, *eh); - } + if ((revents & POLLHUP) != 0) { + eh->on_error(fd, *eh, 0); + continue; + } - } catch (const std::exception& e) { - eh->on_error(fd, *eh, 0); + if ((revents & POLLERR) != 0) { + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &errcode, &codesize) < 0) { + errcode = errno; + } + eh->on_error(fd, *eh, errcode); + continue; + } + + if ((revents & POLLIN) != 0) { + eh->on_read(fd, *eh); + } + + if ((revents & POLLOUT) != 0) { + eh->flags &= ~WANT_WRITE; + update_socket(*eh); + eh->on_write(fd, *eh); + } + + } catch (const std::exception &e) { + eh->on_error(fd, *eh, 0); + } } } prune(); @@ -126,9 +138,6 @@ struct socket_engine_poll : public socket_engine_base { if ((e.flags & WANT_WRITE) != 0) { fd_info.events |= POLLOUT; } - if ((e.flags & WANT_ERROR) != 0) { - fd_info.events |= POLLERR; - } poll_set.push_back(fd_info); } return r; @@ -149,9 +158,6 @@ struct socket_engine_poll : public socket_engine_base { if ((e.flags & WANT_WRITE) != 0) { fd_info.events |= POLLOUT; } - if ((e.flags & WANT_ERROR) != 0) { - fd_info.events |= POLLERR; - } break; } } @@ -176,7 +182,7 @@ struct socket_engine_poll : public socket_engine_base { } }; -std::unique_ptr create_socket_engine(cluster* creator) { +DPP_EXPORT std::unique_ptr create_socket_engine(cluster* creator) { return std::make_unique(creator); } diff --git a/src/dpp/thread_pool.cpp b/src/dpp/thread_pool.cpp index 7d32baf19d..2ee5624117 100644 --- a/src/dpp/thread_pool.cpp +++ b/src/dpp/thread_pool.cpp @@ -24,6 +24,8 @@ #include #include +namespace dpp { + thread_pool::thread_pool(size_t num_threads) { for (size_t i = 0; i < num_threads; ++i) { threads.emplace_back([this, i]() { @@ -51,24 +53,24 @@ thread_pool::thread_pool(size_t num_threads) { } } -thread_pool::~thread_pool() -{ +thread_pool::~thread_pool() { { std::unique_lock lock(queue_mutex); stop = true; } cv.notify_all(); - for (auto& thread : threads) { + for (auto &thread: threads) { thread.join(); } } -void thread_pool::enqueue(thread_pool_task task) -{ +void thread_pool::enqueue(thread_pool_task task) { { std::unique_lock lock(queue_mutex); tasks.emplace(std::move(task)); } cv.notify_one(); } + +} \ No newline at end of file diff --git a/src/dpp/voice/enabled/discover_ip.cpp b/src/dpp/voice/enabled/discover_ip.cpp index 7061c20f08..96ab091a13 100644 --- a/src/dpp/voice/enabled/discover_ip.cpp +++ b/src/dpp/voice/enabled/discover_ip.cpp @@ -148,7 +148,11 @@ std::string discord_voice_client::discover_ip() { return ""; } address_t bind_port(this->ip, this->port); +#ifndef _WIN32 if (::connect(socket.fd, bind_port.get_socket_address(), bind_port.size()) < 0) { +#else + if (WSAConnect(socket.fd, bind_port.get_socket_address(), bind_port.size(), nullptr, nullptr, nullptr, nullptr) < 0) { +#endif log(ll_warning, "Could not connect socket for IP discovery"); return ""; } diff --git a/src/sockettest/socket.cpp b/src/sockettest/socket.cpp index 08ce6b12da..057b7b112e 100644 --- a/src/sockettest/socket.cpp +++ b/src/sockettest/socket.cpp @@ -22,9 +22,18 @@ #include #include -#include #include #include +#ifndef _WIN32 + #include +#else + /* Windows-specific sockets includes */ + #include + #include + #include + /* Windows sockets library */ + #pragma comment(lib, "ws2_32") +#endif int main() { dpp::cluster cl("no-token"); @@ -37,7 +46,11 @@ int main() { if (sfd == INVALID_SOCKET) { std::cerr << "Couldn't create outbound socket on port 80\n"; exit(1); +#ifndef _WIN32 } else if (::connect(sfd, destination.get_socket_address(), destination.size()) != 0) { +#else + } else if (::WSAConnect(sfd, destination.get_socket_address(), destination.size(), nullptr, nullptr, nullptr, nullptr) != 0) { +#endif dpp::close_socket(sfd); std::cerr << "Couldn't connect outbound socket on port 80\n"; exit(1); @@ -50,7 +63,7 @@ int main() { int r = 0; do { char buf[128]{0}; - r = ::read(e.fd, buf, sizeof(buf)); + r = ::recv(e.fd, buf, sizeof(buf), 0); if (r > 0) { buf[127] = 0; std::cout << buf; @@ -65,7 +78,8 @@ int main() { [](dpp::socket fd, const struct dpp::socket_events& e) { std::cout << "WANT_WRITE event on socket " << fd << "\n"; constexpr std::string_view request{"GET / HTTP/1.0\r\nConnection: close\r\n\r\n"}; - auto written = ::write(e.fd, request.data(), request.length()); + std::cout << "Writing: " << request.data() << "\n"; + auto written = ::send(e.fd, request.data(), request.length(), 0); std::cout << "Written: " << written << "\n"; }, [](dpp::socket fd, const struct dpp::socket_events&, int error_code) { From d119a3b9adfb0db173cbcd7970177bca9045d4ff Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 20 Nov 2024 21:46:43 +0000 Subject: [PATCH 055/112] [skip ci] we dont need to check for epoll removal failure, if it fails its already not in the set --- doxygen-awesome-css | 2 +- src/dpp/socketengines/epoll.cpp | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/doxygen-awesome-css b/doxygen-awesome-css index c6568ebc70..af1d9030b3 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit c6568ebc70adf9fb0fb6c1745737ae6945576813 +Subproject commit af1d9030b3ffa7b483fa9997a7272fb12af6af4c diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 4b7ee1a7c7..8852812b5f 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -186,11 +186,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { bool r = socket_engine_base::remove_socket(fd); if (r) { struct epoll_event ev{}; - int i = epoll_ctl(epoll_handle, EPOLL_CTL_DEL, fd, &ev); - if (i < 0) { - throw dpp::connection_exception("Failed to deregister socket with epoll_ctl()"); - } - + epoll_ctl(epoll_handle, EPOLL_CTL_DEL, fd, &ev); } return r; } From f80a97510a7f9d3a3b3822e6495de56975ef34c5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 21 Nov 2024 00:55:19 +0000 Subject: [PATCH 056/112] properly mutex everything --- doxygen-awesome-css | 2 +- include/dpp/socketengine.h | 7 +++ src/dpp/socketengine.cpp | 4 ++ src/dpp/socketengines/poll.cpp | 102 ++++++++++++++++++--------------- src/unittest/test.cpp | 75 ++++++++++++------------ src/unittest/test.h | 2 + src/unittest/unittest.cpp | 6 +- 7 files changed, 113 insertions(+), 85 deletions(-) diff --git a/doxygen-awesome-css b/doxygen-awesome-css index af1d9030b3..c6568ebc70 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit af1d9030b3ffa7b483fa9997a7272fb12af6af4c +Subproject commit c6568ebc70adf9fb0fb6c1745737ae6945576813 diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index c7040845c4..7d2d20e164 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace dpp { @@ -144,6 +145,12 @@ using socket_container = std::unordered_map(e)); return true; @@ -39,6 +40,7 @@ bool socket_engine_base::register_socket(const socket_events &e) { } bool socket_engine_base::update_socket(const socket_events &e) { + std::unique_lock lock(fds_mutex); if (e.fd != INVALID_SOCKET && fds.find(e.fd) != fds.end()) { auto iter = fds.find(e.fd); *(iter->second) = e; @@ -68,6 +70,7 @@ time_t last_time = time(nullptr); void socket_engine_base::prune() { if (to_delete_count > 0) { + std::unique_lock lock(fds_mutex); for (auto it = fds.cbegin(); it != fds.cend();) { if ((it->second->flags & WANT_DELETION) != 0L) { remove_socket(it->second->fd); @@ -98,6 +101,7 @@ void socket_engine_base::prune() { } bool socket_engine_base::delete_socket(dpp::socket fd) { + std::unique_lock lock(fds_mutex); auto iter = fds.find(fd); if (iter == fds.end() || ((iter->second->flags & WANT_DELETION) != 0L)) { return false; diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index d2e0da1a82..dd89c6efda 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -21,6 +21,7 @@ #include #include +#include #ifdef _WIN32 /* Windows-specific sockets includes */ #include @@ -54,70 +55,78 @@ struct DPP_EXPORT socket_engine_poll : public socket_engine_base { */ std::vector poll_set; pollfd out_set[FD_SETSIZE]{0}; + std::shared_mutex poll_set_mutex; void process_events() final { const int poll_delay = 1000; - if (poll_set.empty()) { - /* On many platforms, it is not possible to wait on an empty set */ - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } else { - if (poll_set.size() > FD_SETSIZE) { - throw dpp::connection_exception("poll() does not support more than FD_SETSIZE active sockets at once!"); + prune(); + { + std::shared_lock lock(poll_set_mutex); + if (poll_set.empty()) { + /* On many platforms, it is not possible to wait on an empty set */ + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return; + } else { + if (poll_set.size() > FD_SETSIZE) { + throw dpp::connection_exception("poll() does not support more than FD_SETSIZE active sockets at once!"); + } + /** + * We must make a copy of the poll_set, because it would cause thread locking/contention + * issues if we had it locked for read during poll/iteration of the returned set. + */ + std::copy(poll_set.begin(), poll_set.end(), out_set); } + } - std::copy(poll_set.begin(), poll_set.end(), out_set); + int i = poll(out_set, static_cast(poll_set.size()), poll_delay); + int processed = 0; - int i = poll(out_set, static_cast(poll_set.size()), poll_delay); - int processed = 0; + for (size_t index = 0; index < poll_set.size() && processed < i; index++) { + const int fd = out_set[index].fd; + const short revents = out_set[index].revents; - for (size_t index = 0; index < poll_set.size() && processed < i; index++) { - const int fd = out_set[index].fd; - const short revents = out_set[index].revents; + if (revents > 0) { + processed++; + } - if (revents > 0) { - processed++; - } + auto iter = fds.find(fd); + if (iter == fds.end()) { + continue; + } + socket_events *eh = iter->second.get(); + + try { - auto iter = fds.find(fd); - if (iter == fds.end()) { + if ((revents & POLLHUP) != 0) { + eh->on_error(fd, *eh, 0); continue; } - socket_events *eh = iter->second.get(); - - try { - - if ((revents & POLLHUP) != 0) { - eh->on_error(fd, *eh, 0); - continue; - } - - if ((revents & POLLERR) != 0) { - socklen_t codesize = sizeof(int); - int errcode{}; - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &errcode, &codesize) < 0) { - errcode = errno; - } - eh->on_error(fd, *eh, errcode); - continue; - } - if ((revents & POLLIN) != 0) { - eh->on_read(fd, *eh); + if ((revents & POLLERR) != 0) { + socklen_t codesize = sizeof(int); + int errcode{}; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &errcode, &codesize) < 0) { + errcode = errno; } + eh->on_error(fd, *eh, errcode); + continue; + } - if ((revents & POLLOUT) != 0) { - eh->flags &= ~WANT_WRITE; - update_socket(*eh); - eh->on_write(fd, *eh); - } + if ((revents & POLLIN) != 0) { + eh->on_read(fd, *eh); + } - } catch (const std::exception &e) { - eh->on_error(fd, *eh, 0); + if ((revents & POLLOUT) != 0) { + eh->flags &= ~WANT_WRITE; + update_socket(*eh); + eh->on_write(fd, *eh); } + + } catch (const std::exception &e) { + eh->on_error(fd, *eh, 0); } } - prune(); } #if _WIN32 @@ -129,6 +138,7 @@ struct DPP_EXPORT socket_engine_poll : public socket_engine_base { bool register_socket(const socket_events& e) final { bool r = socket_engine_base::register_socket(e); if (r) { + std::unique_lock lock(poll_set_mutex); pollfd fd_info{}; fd_info.fd = e.fd; fd_info.events = 0; @@ -146,6 +156,7 @@ struct DPP_EXPORT socket_engine_poll : public socket_engine_base { bool update_socket(const socket_events& e) final { bool r = socket_engine_base::update_socket(e); if (r) { + std::unique_lock lock(poll_set_mutex); /* We know this will succeed */ for (pollfd& fd_info : poll_set) { if (fd_info.fd != e.fd) { @@ -171,6 +182,7 @@ struct DPP_EXPORT socket_engine_poll : public socket_engine_base { bool remove_socket(dpp::socket fd) final { bool r = socket_engine_base::remove_socket(fd); if (r) { + std::unique_lock lock(poll_set_mutex); for (auto i = poll_set.begin(); i != poll_set.end(); ++i) { if (i->fd == fd) { poll_set.erase(i); diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 19dd1970c9..d0119e7b16 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -26,6 +26,11 @@ #include #include +/** + * @brief global lock for log output + */ +std::mutex loglock; + /** * @brief Type trait to check if a certain type has a build_json method * @@ -926,6 +931,9 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b coro_offline_tests(); } + std::promise ready_promise; + std::future ready_future = ready_promise.get_future(); + std::vector dpp_logo = load_data("DPP-Logo.png"); set_test(PRESENCE, false); @@ -971,9 +979,6 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b /* This ensures we test both protocols, as voice is json and shard is etf */ bot.set_websocket_protocol(dpp::ws_etf); - bot.on_form_submit([&](const dpp::form_submit_t & event) { - }); - /* This is near impossible to test without a 'clean room' voice channel. * We attach this event just so that the decoder events are fired while we * are sending audio later, this way if the audio receive code is plain unstable @@ -982,7 +987,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_voice_receive_combined([&](const auto& event) { }); - bot.on_guild_create([&](const dpp::guild_create_t& event) { + bot.on_guild_create([dpp_logo,&bot](const dpp::guild_create_t& event) { dpp::guild *g = event.created; if (g->id == TEST_GUILD_ID) { @@ -1016,9 +1021,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - std::promise ready_promise; - std::future ready_future = ready_promise.get_future(); - bot.on_ready([&](const dpp::ready_t & event) { + bot.on_ready([&ready_promise,&bot,dpp_logo](const dpp::ready_t & event) { set_test(CONNECTION, true); ready_promise.set_value(); @@ -1076,10 +1079,9 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); }); - std::mutex loglock; - bot.on_log([&](const dpp::log_t & event) { - std::lock_guard locker(loglock); + bot.on_log([](const dpp::log_t & event) { if (event.severity > dpp::ll_trace) { + std::lock_guard locker(loglock); std::cout << "[" << std::fixed << std::setprecision(3) << (dpp::utility::time_f() - get_start_time()) << "]: [\u001b[36m" << dpp::utility::loglevel(event.severity) << "\u001b[0m] " << event.message << "\n"; } if (event.message == "Test log message") { @@ -1096,7 +1098,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } set_test(RUNONCE, (runs == 1)); - bot.on_voice_ready([&](const dpp::voice_ready_t & event) { + bot.on_voice_ready([&testaudio](const dpp::voice_ready_t & event) { set_test(VOICECONN, true); dpp::discord_voice_client* v = event.voice_client; set_test(VOICESEND, false); @@ -1121,7 +1123,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_voice_buffer_send([&](const dpp::voice_buffer_send_t & event) { + bot.on_voice_buffer_send([](const dpp::voice_buffer_send_t & event) { static bool sent_some_data = false; if (event.buffer_size > 0) { @@ -1133,7 +1135,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_guild_create([&](const dpp::guild_create_t & event) { + bot.on_guild_create([](const dpp::guild_create_t & event) { if (event.created->id == TEST_GUILD_ID) { set_test(GUILDCREATE, true); if (event.presences.size() && event.presences.begin()->second.user_id > 0) { @@ -1230,21 +1232,21 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b return false; } }; - message.attachments[0].download([&](const dpp::http_request_completion_t &callback) { + message.attachments[0].download([this](const dpp::http_request_completion_t &callback) { std::lock_guard lock(mutex); if (callback.status == 200 && callback.body == "test") { files_success[0] = true; } set_file_tested(0); }); - message.attachments[1].download([&](const dpp::http_request_completion_t &callback) { + message.attachments[1].download([this](const dpp::http_request_completion_t &callback) { std::lock_guard lock(mutex); if (callback.status == 200 && check_mimetype(callback.headers, "text/plain") && callback.body == "test") { files_success[1] = true; } set_file_tested(1); }); - message.attachments[2].download([&](const dpp::http_request_completion_t &callback) { + message.attachments[2].download([this](const dpp::http_request_completion_t &callback) { std::lock_guard lock(mutex); // do not check the contents here because discord can change compression if (callback.status == 200 && check_mimetype(callback.headers, "image/png")) { @@ -1594,7 +1596,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b thread_test_helper thread_helper(bot); - bot.on_thread_create([&](const dpp::thread_create_t &event) { + bot.on_thread_create([&thread_helper](const dpp::thread_create_t &event) { if (event.created.name == "thread test") { set_test(THREAD_CREATE_EVENT, true); thread_helper.run(event.created); @@ -1694,7 +1696,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_thread_update([&](const dpp::thread_update_t &event) { + bot.on_thread_update([&thread_helper](const dpp::thread_update_t &event) { if (event.updating_guild->id == TEST_GUILD_ID && event.updated.id == thread_helper.thread_id && event.updated.name == "edited") { set_test(THREAD_UPDATE_EVENT, true); } @@ -2281,13 +2283,17 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(BOTSTART, false); } + dpp::https_client *c{}; + dpp::https_client *c2{}; + dpp::https_client *c3{}; + set_test(HTTPS, false); if (!offline) { dpp::multipart_content multipart = dpp::https_client::build_multipart( "{\"content\":\"test\"}", {"test.txt", "blob.blob"}, {"ABCDEFGHI", "BLOB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}, {"text/plain", "application/octet-stream"} ); try { - dpp::https_client c(&bot, "discord.com", 443, "/api/channels/" + std::to_string(TEST_TEXT_CHANNEL_ID) + "/messages", "POST", multipart.body, + c = new dpp::https_client(&bot, "discord.com", 443, "/api/channels/" + std::to_string(TEST_TEXT_CHANNEL_ID) + "/messages", "POST", multipart.body, { {"Content-Type", multipart.mimetype}, {"Authorization", "Bot " + token} @@ -2297,49 +2303,43 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(HTTPS, hdr1 == "cloudflare" && c->get_status() == 200); } ); - std::this_thread::sleep_for(std::chrono::seconds(6)); } catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(HTTPS, false); + set_status(HTTPS, ts_failed, e.what()); } set_test(HTTP, false); try { - dpp::https_client c2(&bot, "github.com", 80, "/", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { + c2 = new dpp::https_client(&bot, "github.com", 80, "/", "GET", "", {}, true, 5, "1.1", [](dpp::https_client *c2) { std::string hdr2 = c2->get_header("location"); std::string content2 = c2->get_content(); set_test(HTTP, hdr2 == "https://github.com/" && c2->get_status() == 301); }); - std::this_thread::sleep_for(std::chrono::seconds(6)); } catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(HTTP, false); + set_status(HTTP, ts_failed, e.what()); } set_test(MULTIHEADER, false); try { - dpp::https_client c2(&bot, "dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}, true, 5, "1.1", [](dpp::https_client* c2) { + c3 = new dpp::https_client(&bot, "dl.dpp.dev", 443, "/cookietest.php", "GET", "", {}, true, 5, "1.1", [](dpp::https_client *c2) { size_t count = c2->get_header_count("set-cookie"); size_t count_list = c2->get_header_list("set-cookie").size(); // This test script sets a bunch of cookies when we request it. set_test(MULTIHEADER, c2->get_status() == 200 && count > 1 && count == count_list); }); - std::this_thread::sleep_for(std::chrono::seconds(6)); } catch (const dpp::exception& e) { - std::cout << e.what() << "\n"; - set_test(MULTIHEADER, false); + set_status(MULTIHEADER, ts_failed, e.what()); } } set_test(TIMERSTART, false); - uint32_t ticks = 0; - dpp::timer th = bot.start_timer([&](dpp::timer timer_handle) { - if (ticks == 5) { + static uint32_t ticks = 0; + dpp::timer th = bot.start_timer([](dpp::timer timer_handle) { + if (ticks == 2) { /* The simple test timer ticks every second. - * If we get to 5 seconds, we know the timer is working. + * If we get to 2 seconds, we know the timer is working. */ set_test(TIMERSTART, true); } @@ -2451,10 +2451,13 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b wait_for_tests(); + delete c; + delete c2; + delete c3; + } catch (const std::exception &e) { - std::cout << e.what() << "\n"; - set_test(CLUSTER, false); + set_status(CLUSTER, ts_failed, e.what()); } /* Return value = number of failed tests, exit code 0 = success */ diff --git a/src/unittest/test.h b/src/unittest/test.h index 56e8d2cbb7..cf9f966a28 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -39,6 +39,8 @@ _Pragma("warning( disable : 5105 )"); // 4251 warns when we export classes or st using json = nlohmann::json; +extern std::mutex loglock; + enum test_flags_t { tf_offline = 0, /* A test that requires discord connectivity */ diff --git a/src/unittest/unittest.cpp b/src/unittest/unittest.cpp index 65059ad3fc..ec58d888c2 100644 --- a/src/unittest/unittest.cpp +++ b/src/unittest/unittest.cpp @@ -38,8 +38,7 @@ test_t::test_t(std::string_view testname, std::string_view testdesc, int testfla } void set_status(test_t &test, test_status_t newstatus, std::string_view message) { - static std::mutex m; - std::scoped_lock lock{m}; + std::lock_guard locker(loglock); if (is_skipped(test) || newstatus == test.status) { return; @@ -99,6 +98,7 @@ double get_time() { int test_summary() { /* Report on all test cases */ + std::lock_guard locker(loglock); int failed = 0, passed = 0, skipped = 0; std::cout << "\u001b[37;1m\n\nUNIT TEST SUMMARY\n==================\n\u001b[0m"; for (auto & t : tests) { @@ -193,7 +193,7 @@ void wait_for_tests() { } } if (finished == tests.size()) { - std::this_thread::sleep_for(std::chrono::seconds(10)); + std::this_thread::sleep_for(std::chrono::seconds(5)); return; } std::this_thread::sleep_for(std::chrono::seconds(1)); From e9ef7a7961336bb671e7bdfcde1672b2bcf11fe5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 21 Nov 2024 01:18:28 +0000 Subject: [PATCH 057/112] improve threading --- doxygen-awesome-css | 2 +- src/dpp/socketengine.cpp | 1 - src/dpp/socketengines/epoll.cpp | 22 ++++++++++++---------- src/dpp/socketengines/kqueue.cpp | 24 +++++++++++++----------- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/doxygen-awesome-css b/doxygen-awesome-css index c6568ebc70..af1d9030b3 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit c6568ebc70adf9fb0fb6c1745737ae6945576813 +Subproject commit af1d9030b3ffa7b483fa9997a7272fb12af6af4c diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index 89bc373fbd..38f738aeaf 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -63,7 +63,6 @@ socket_engine_base::socket_engine_base(cluster* creator) : owner(creator) { throw dpp::connection_exception(err_connect_failure, "WSAStartup failure"); } #endif - //pool = std::make_unique(); } time_t last_time = time(nullptr); diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 8852812b5f..8bcf3671ec 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -52,16 +52,15 @@ int modify_event(int epoll_handle, socket_events* eh, int new_events) { struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { int epoll_handle{INVALID_SOCKET}; - static const int epoll_hint = 128; - std::vector events; + static constexpr size_t MAX_EVENTS = 65536; + std::array events; socket_engine_epoll(const socket_engine_epoll&) = delete; socket_engine_epoll(socket_engine_epoll&&) = delete; socket_engine_epoll& operator=(const socket_engine_epoll&) = delete; socket_engine_epoll& operator=(socket_engine_epoll&&) = delete; - explicit socket_engine_epoll(cluster* creator) : socket_engine_base(creator), epoll_handle(epoll_create(socket_engine_epoll::epoll_hint)) { - events.resize(socket_engine_epoll::epoll_hint); + explicit socket_engine_epoll(cluster* creator) : socket_engine_base(creator), epoll_handle(epoll_create(MAX_EVENTS)) { if (epoll_handle == -1) { throw dpp::connection_exception("Failed to initialise epoll()"); } @@ -75,7 +74,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { void process_events() final { const int sleep_length = 1000; - int i = epoll_wait(epoll_handle, events.data(), static_cast(events.size()), sleep_length); + int i = epoll_wait(epoll_handle, events.data(), MAX_EVENTS, sleep_length); for (int j = 0; j < i; j++) { epoll_event ev = events[j]; @@ -145,14 +144,14 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { if ((e.flags & WANT_ERROR) != 0) { ev.events |= EPOLLERR; } - ev.data.ptr = fds.find(e.fd)->second.get(); + { + std::unique_lock lock(fds_mutex); + ev.data.ptr = fds.find(e.fd)->second.get(); + } int i = epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev); if (i < 0) { throw dpp::connection_exception("Failed to register socket to epoll_ctl()"); } - if (fds.size() * 2 > events.size()) { - events.resize(fds.size() * 2); - } } return r; } @@ -171,7 +170,10 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { if ((e.flags & WANT_ERROR) != 0) { ev.events |= EPOLLERR; } - ev.data.ptr = fds.find(e.fd)->second.get(); + { + std::unique_lock lock(fds_mutex); + ev.data.ptr = fds.find(e.fd)->second.get(); + } int i = epoll_ctl(epoll_handle, EPOLL_CTL_MOD, e.fd, &ev); if (i < 0) { throw dpp::connection_exception("Failed to modify socket with epoll_ctl()"); diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index bc93272714..4ba0c64e33 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -33,8 +34,10 @@ namespace dpp { struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { + static constexpr size_t MAX_SOCKET_VALUE = 65536; + int kqueue_handle{INVALID_SOCKET}; - std::array ke_list; + std::array ke_list; socket_engine_kqueue(const socket_engine_kqueue&) = delete; socket_engine_kqueue(socket_engine_kqueue&&) = delete; @@ -57,7 +60,7 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { struct timespec ts{}; ts.tv_sec = 1; - int i = kevent(kqueue_handle, NULL, 0, ke_list.data(), static_cast(ke_list.size()), &ts); + int i = kevent(kqueue_handle, nullptr, 0, ke_list.data(), static_cast(ke_list.size()), &ts); if (i < 0) { return; } @@ -101,15 +104,15 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { bool register_socket(const socket_events& e) final { bool r = socket_engine_base::register_socket(e); if (r) { - struct kevent ke; + struct kevent ke{}; socket_events* se = fds.find(e.fd)->second.get(); if ((se->flags & WANT_READ) != 0) { EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); - kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); } if ((se->flags & WANT_WRITE) != 0) { EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); - kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); } } return r; @@ -118,15 +121,14 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { bool update_socket(const socket_events& e) final { bool r = socket_engine_base::update_socket(e); if (r) { - socket_events* se = fds.find(e.fd)->second.get(); - struct kevent ke; + struct kevent ke{}; if ((e.flags & WANT_READ) != 0) { EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); - kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); } if ((e.flags & WANT_WRITE) != 0) { EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); - kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); } } return r; @@ -139,9 +141,9 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { if (r) { struct kevent ke; EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); - kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - kevent(kqueue_handle, &ke, 1, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); } return r; } From 0114e143ebd8fdb2a148b80357f2743340d9b6a0 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 21 Nov 2024 01:28:18 +0000 Subject: [PATCH 058/112] [skip ci] fix typo in kqueue --- src/dpp/socketengines/kqueue.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 4ba0c64e33..ce2da57653 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -105,7 +105,11 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { bool r = socket_engine_base::register_socket(e); if (r) { struct kevent ke{}; - socket_events* se = fds.find(e.fd)->second.get(); + socket_events* se{}; + { + std::unique_lock lock(fds_mutex); + se = fds.find(e.fd)->second.get(); + } if ((se->flags & WANT_READ) != 0) { EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); @@ -122,6 +126,11 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { bool r = socket_engine_base::update_socket(e); if (r) { struct kevent ke{}; + socket_events* se{}; + { + std::unique_lock lock(fds_mutex); + se = fds.find(e.fd)->second.get(); + } if ((e.flags & WANT_READ) != 0) { EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); From 8bf9b8c95cc7ee27d6c7edf13f749e987879db5c Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 21 Nov 2024 01:32:14 +0000 Subject: [PATCH 059/112] [skip ci] warning fixes --- src/dpp/dave/session.cpp | 4 ++-- src/unittest/test.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dpp/dave/session.cpp b/src/dpp/dave/session.cpp index 8fa34409c8..9b3e410fdf 100755 --- a/src/dpp/dave/session.cpp +++ b/src/dpp/dave/session.cpp @@ -247,7 +247,7 @@ bool session::is_recognized_user_id(const ::mlspp::Credential& cred, std::set Date: Fri, 22 Nov 2024 13:26:31 +0000 Subject: [PATCH 060/112] fix: these things shouldnt throw --- src/dpp/socketengines/epoll.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 8bcf3671ec..cfde495d94 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -148,10 +148,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { std::unique_lock lock(fds_mutex); ev.data.ptr = fds.find(e.fd)->second.get(); } - int i = epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev); - if (i < 0) { - throw dpp::connection_exception("Failed to register socket to epoll_ctl()"); - } + return epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev) >= 0; } return r; } @@ -174,10 +171,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { std::unique_lock lock(fds_mutex); ev.data.ptr = fds.find(e.fd)->second.get(); } - int i = epoll_ctl(epoll_handle, EPOLL_CTL_MOD, e.fd, &ev); - if (i < 0) { - throw dpp::connection_exception("Failed to modify socket with epoll_ctl()"); - } + return epoll_ctl(epoll_handle, EPOLL_CTL_MOD, e.fd, &ev) >= 0; } return r; } From d22254b8bd6ba2cad931264603a2d478ae66e54e Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 22 Nov 2024 23:06:51 +0000 Subject: [PATCH 061/112] feat: error handling and client auto reconnect --- include/dpp/discordclient.h | 23 ++++++++++++----------- include/dpp/thread_pool.h | 9 +++++++-- include/dpp/wsclient.h | 5 +++++ src/dpp/cluster.cpp | 2 +- src/dpp/discordclient.cpp | 32 ++++++++++++++++++-------------- src/dpp/thread_pool.cpp | 15 ++++++++++++--- src/dpp/wsclient.cpp | 5 +++++ src/soaktest/soak.cpp | 6 ------ 8 files changed, 60 insertions(+), 37 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index d57c2d775b..bb7c4a0974 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -179,6 +179,13 @@ class DPP_EXPORT discord_client : public websocket_client */ void disconnect_voice_internal(snowflake guild_id, bool send_json = true); + /** + * @brief Start connecting the websocket + * + * Called from the constructor, or during reconnection + */ + void start_connecting(); + private: /** @@ -191,17 +198,6 @@ class DPP_EXPORT discord_client : public websocket_client */ std::deque message_queue; - /** - * @brief Thread this shard is executing on - */ - std::thread* runner; - - /** - * @brief Run shard loop under a thread. - * Calls discord_client::run() from within a std::thread. - */ - void thread_run(); - /** * @brief If true, stream compression is enabled */ @@ -499,6 +495,11 @@ class DPP_EXPORT discord_client : public websocket_client */ void run(); + /** + * @brief Called when the HTTP socket is closed + */ + virtual void on_disconnect(); + /** * @brief Connect to a voice channel * diff --git a/include/dpp/thread_pool.h b/include/dpp/thread_pool.h index ef15ab33ea..1f40c125ec 100644 --- a/include/dpp/thread_pool.h +++ b/include/dpp/thread_pool.h @@ -41,7 +41,7 @@ struct DPP_EXPORT thread_pool_task { }; struct DPP_EXPORT thread_pool_task_comparator { - bool operator()(const thread_pool_task &a, const thread_pool_task &b) { + bool operator()(const thread_pool_task &a, const thread_pool_task &b) const { return a.priority < b.priority; }; }; @@ -57,7 +57,12 @@ struct DPP_EXPORT thread_pool { std::condition_variable cv; bool stop{false}; - explicit thread_pool(size_t num_threads = std::thread::hardware_concurrency()); + /** + * @brief Create a new priority thread pool + * @param creator creating cluster (for logging) + * @param num_threads number of threads in the pool + */ + explicit thread_pool(class cluster* creator, size_t num_threads = std::thread::hardware_concurrency()); ~thread_pool(); void enqueue(thread_pool_task task); }; diff --git a/include/dpp/wsclient.h b/include/dpp/wsclient.h index 73f8eb9321..c7fc99d4ee 100644 --- a/include/dpp/wsclient.h +++ b/include/dpp/wsclient.h @@ -235,6 +235,11 @@ class DPP_EXPORT websocket_client : public ssl_client { * This indicates graceful close. */ void send_close_packet(); + + /** + * @brief Called on HTTP socket closure + */ + virtual void on_disconnect(); }; } diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index c09616b9b1..604deff496 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -89,7 +89,7 @@ cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, numshards(_shards), cluster_id(_cluster_id), maxclusters(_maxclusters), rest_ping(0.0), cache_policy(policy), ws_mode(ws_json) { socketengine = create_socket_engine(this); - pool = std::make_unique(request_threads); + pool = std::make_unique(this, request_threads); /* Instantiate REST request queues */ try { rest = new request_queue(this, request_threads); diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 15b8fd24f2..50ba878ced 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -65,8 +65,7 @@ thread_local static std::string last_ping_message; discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), - terminating(false), - runner(nullptr), + terminating(false), compressed(comp), decomp_buffer(nullptr), zlib(nullptr), @@ -90,6 +89,10 @@ discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint3 protocol(ws_proto), resume_gateway_url(_cluster->default_gateway) { + start_connecting(); +} + +void discord_client::start_connecting() { try { zlib = new zlibcontext(); etf = new etf_parser(); @@ -112,10 +115,6 @@ discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint3 void discord_client::cleanup() { terminating = true; - if (runner) { - runner->join(); - delete runner; - } delete etf; delete zlib; } @@ -125,6 +124,19 @@ discord_client::~discord_client() cleanup(); } +void discord_client::on_disconnect() +{ + set_resume_hostname(); + log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting in 5 seconds..."); + owner->start_timer([this](auto handle) { + owner->stop_timer(handle); + cleanup(); + terminating = false; + start_connecting(); + run(); + }, 5); +} + uint64_t discord_client::get_decompressed_bytes_in() { return decompressed_total; @@ -159,20 +171,12 @@ void discord_client::set_resume_hostname() hostname = resume_gateway_url; } -void discord_client::thread_run() -{ -} - void discord_client::run() { - // TODO: This only runs once. Replace the reconnect mechanics. - // To make this work, we will need to intercept errors. setup_zlib(); ready = false; message_queue.clear(); ssl_client::read_loop(); - //ssl_client::close(); - //end_zlib(); } bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) diff --git a/src/dpp/thread_pool.cpp b/src/dpp/thread_pool.cpp index 2ee5624117..c7a64bc176 100644 --- a/src/dpp/thread_pool.cpp +++ b/src/dpp/thread_pool.cpp @@ -23,12 +23,13 @@ #include #include #include +#include namespace dpp { -thread_pool::thread_pool(size_t num_threads) { +thread_pool::thread_pool(cluster* creator, size_t num_threads) { for (size_t i = 0; i < num_threads; ++i) { - threads.emplace_back([this, i]() { + threads.emplace_back([this, i, creator]() { dpp::utility::set_thread_name("pool/exec/" + std::to_string(i)); while (true) { thread_pool_task task; @@ -47,7 +48,15 @@ thread_pool::thread_pool(size_t num_threads) { tasks.pop(); } - task.function(); + try { + task.function(); + } + catch (const std::exception &e) { + creator->log(ll_warning, "Uncaught exception in thread pool: " + std::string(e.what())); + } + catch (...) { + creator->log(ll_warning, "Uncaught exception in thread pool, but not derived from std::exception!"); + } } }); } diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index e5ebe762de..328b5d8d8e 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -326,8 +326,13 @@ void websocket_client::error(uint32_t errorcode) { } +void websocket_client::on_disconnect() +{ +} + void websocket_client::close() { + this->on_disconnect(); this->state = HTTP_HEADERS; ssl_client::close(); } diff --git a/src/soaktest/soak.cpp b/src/soaktest/soak.cpp index de82ece80b..cbc462c194 100644 --- a/src/soaktest/soak.cpp +++ b/src/soaktest/soak.cpp @@ -35,12 +35,6 @@ int main() { }); soak_test.start(dpp::st_return); - dpp::https_client c2(&soak_test, "github.com", 80, "/", "GET", "", {}, true, 2, "1.1", [](dpp::https_client* c2) { - std::string hdr2 = c2->get_header("location"); - std::string content2 = c2->get_content(); - std::cout << "hdr2 == " << hdr2 << " ? https://github.com/ status = " << c2->get_status() << "\n"; - }); - while (true) { std::this_thread::sleep_for(60s); dpp::discord_client* dc = soak_test.get_shard(0); From fda53d4f3918fa8f247042101e0020d45f7bea06 Mon Sep 17 00:00:00 2001 From: Archie Jaskowicz Date: Sat, 23 Nov 2024 10:17:16 +0000 Subject: [PATCH 062/112] ci: force pkgconf unlink for brew --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f29e1affe..3c8d1404cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,6 +149,9 @@ jobs: with: xcode-version: ${{ matrix.cfg.xcode-version }} + - name: Force unlink pkg-config + run: brew link --overwrite pkgconf + - name: Install homebrew packages run: brew install cmake make opus openssl pkg-config From 2b7f7f342d031c4f6c17e0544782a57dc7041da9 Mon Sep 17 00:00:00 2001 From: Archie Jaskowicz Date: Sat, 23 Nov 2024 12:17:01 +0000 Subject: [PATCH 063/112] ci: removed pkg-config from brew as no longer needed --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c8d1404cc..94f9441983 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,11 +149,8 @@ jobs: with: xcode-version: ${{ matrix.cfg.xcode-version }} - - name: Force unlink pkg-config - run: brew link --overwrite pkgconf - - name: Install homebrew packages - run: brew install cmake make opus openssl pkg-config + run: brew install cmake make opus openssl - name: Generate CMake run: cmake -B build -DDPP_NO_VCPKG=ON -DCMAKE_BUILD_TYPE=Release -DDPP_CORO=ON -DAVX_TYPE=AVX0 From 246f2d76459f6f08babe622b10895efdb9cbcbcc Mon Sep 17 00:00:00 2001 From: Archie Jaskowicz Date: Sat, 23 Nov 2024 12:57:57 +0000 Subject: [PATCH 064/112] ci: macos version will now always be 15 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94f9441983..a0f3dc4543 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,9 +132,9 @@ jobs: fail-fast: false # Don't fail everything if one fails. We want to test each OS/Compiler individually matrix: cfg: - - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 16, cmake-flags: '', xcode-version: '16.0.0' } - - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 15, cmake-flags: '', xcode-version: '15.3' } - - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 14, cmake-flags: '', xcode-version: '14.3.1' } + - { arch: 'arm64', concurrency: 3, os: macos-15, cpp: clang++, version: 16, cmake-flags: '', xcode-version: '16.0.0' } + - { arch: 'arm64', concurrency: 3, os: macos-15, cpp: clang++, version: 15, cmake-flags: '', xcode-version: '15.3' } + - { arch: 'arm64', concurrency: 3, os: macos-15, cpp: clang++, version: 14, cmake-flags: '', xcode-version: '14.3.1' } steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 From dc4cf959e42c255f771acf2fbc364722e1ee6737 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 23 Nov 2024 17:07:58 +0000 Subject: [PATCH 065/112] refactor: remove nonblocking bool, as all sockets are always now nonblocking --- include/dpp/sslclient.h | 6 ------ src/dpp/httpsclient.cpp | 1 - src/dpp/sslclient.cpp | 1 - src/dpp/voice/enabled/thread.cpp | 3 ++- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 0d1bb693e7..552c677361 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -121,12 +121,6 @@ class DPP_EXPORT ssl_client */ std::string obuffer; - /** - * @brief True if in nonblocking mode. The socket switches to nonblocking mode - * once ReadLoop is called. - */ - bool nonblocking; - /** * @brief Raw file descriptor of connection */ diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 30835f3980..10c545b4ba 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -44,7 +44,6 @@ https_client::https_client(cluster* creator, const std::string &hostname, uint16 completed(done), state(HTTPS_HEADERS) { - nonblocking = false; https_client::connect(); } diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index a185e7b4b1..534ecc4705 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -188,7 +188,6 @@ uint64_t ssl_client::get_unique_id() const { } ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std::string &_port, bool plaintext_downgrade, bool reuse) : - nonblocking(false), sfd(INVALID_SOCKET), ssl(nullptr), last_tick(time(nullptr)), diff --git a/src/dpp/voice/enabled/thread.cpp b/src/dpp/voice/enabled/thread.cpp index 0082eb0a72..655e0b6a64 100644 --- a/src/dpp/voice/enabled/thread.cpp +++ b/src/dpp/voice/enabled/thread.cpp @@ -46,8 +46,9 @@ void discord_voice_client::thread_run() * this gives us time to see if it's an actual disconnect, or an error. * This will prevent us from looping too much, meaning error codes do not cause an infinite loop. */ - if (current_time - last_loop_time >= 3) + if (current_time - last_loop_time >= 3) { times_looped = 0; + } /* This does mean we'll always have times_looped at a minimum of 1, this is intended. */ times_looped++; From a548c2c976b47d8e45c22b69748f88f1dda60373 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 25 Nov 2024 11:44:18 +0000 Subject: [PATCH 066/112] audio isnt being heard, dont know why yet --- include/dpp/discordvoiceclient.h | 63 +++++++++----------- include/dpp/socketengine.h | 4 ++ src/davetest/dave.cpp | 2 +- src/dpp/voice/enabled/cleanup.cpp | 9 +-- src/dpp/voice/enabled/constructor.cpp | 8 ++- src/dpp/voice/enabled/handle_frame.cpp | 15 +++-- src/dpp/voice/enabled/read_write.cpp | 18 ++---- src/dpp/voice/enabled/thread.cpp | 79 ++++++++++---------------- src/dpp/voice/enabled/write_ready.cpp | 5 +- src/dpp/voice/stub/stubs.cpp | 17 ++---- 10 files changed, 96 insertions(+), 124 deletions(-) diff --git a/include/dpp/discordvoiceclient.h b/include/dpp/discordvoiceclient.h index ad0c5e93fa..b902208d4c 100644 --- a/include/dpp/discordvoiceclient.h +++ b/include/dpp/discordvoiceclient.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -267,16 +268,6 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ std::deque message_queue; - /** - * @brief Thread this connection is executing on - */ - std::thread* runner; - - /** - * @brief Run shard loop under a thread - */ - void thread_run(); - /** * @brief Last connect time of voice session */ @@ -446,6 +437,16 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ bool sent_stop_frames; + /** + * @brief Number of times we have tried to reconnect in the last few seconds + */ + size_t times_looped{0}; + + /** + * @brief Last time we reconnected + */ + time_t last_loop_time{0}; + #ifdef HAVE_VOICE /** * @brief libopus encoder @@ -623,25 +624,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client int udp_recv(char* data, size_t max_length); /** - * @brief This hooks the ssl_client, returning the file - * descriptor if we want to send buffered data, or - * -1 if there is nothing to send - * - * @return int file descriptor or -1 - */ - dpp::socket want_write(); - - /** - * @brief This hooks the ssl_client, returning the file - * descriptor if we want to receive buffered data, or - * -1 if we are not wanting to receive - * - * @return int file descriptor or -1 - */ - dpp::socket want_read(); - - /** - * @brief Called by ssl_client when the socket is ready + * @brief Called by socketengine when the socket is ready * for writing, at this point we pick the head item off * the buffer and send it. So long as it doesn't error * completely, we pop it off the head of the queue. @@ -649,7 +632,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client void write_ready(); /** - * @brief Called by ssl_client when there is data to be + * @brief Called by socketengine when there is data to be * read. At this point we insert that data into the * input queue. * @throw dpp::voice_exception if voice support is not compiled into D++ @@ -710,6 +693,16 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ void update_ratchets(bool force = false); + /** + * @brief Called in constructor and on reconnection of websocket + */ + void setup(); + + /** + * @brief Events for UDP Socket IO + */ + dpp::socket_events udp_events; + public: /** @@ -747,11 +740,6 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ time_t last_heartbeat; - /** - * @brief Thread ID - */ - std::thread::native_handle_type thread_id; - /** * @brief Discord voice session token */ @@ -1269,6 +1257,11 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @param rmap Roster map */ void process_mls_group_rosters(const std::map>& rmap); + + /** + * @brief Called on websocket disconnection + */ + void on_disconnect(); }; } diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index 7d2d20e164..18a8b2d98b 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -129,6 +129,10 @@ struct DPP_EXPORT socket_events { 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; }; /** diff --git a/src/davetest/dave.cpp b/src/davetest/dave.cpp index 94812e0d89..72bac9231b 100644 --- a/src/davetest/dave.cpp +++ b/src/davetest/dave.cpp @@ -80,7 +80,7 @@ int main() { dave_test.on_guild_create([&](const dpp::guild_create_t & event) { if (event.created->id == TEST_GUILD_ID) { dpp::discord_client* s = dave_test.get_shard(0); - bool muted = false, deaf = false, enable_dave = true; + bool muted = false, deaf = false, enable_dave = false; s->connect_voice(TEST_GUILD_ID, TEST_VC_ID, muted, deaf, enable_dave); } }); diff --git a/src/dpp/voice/enabled/cleanup.cpp b/src/dpp/voice/enabled/cleanup.cpp index 5ae1d9c7e2..6d7b594b1d 100644 --- a/src/dpp/voice/enabled/cleanup.cpp +++ b/src/dpp/voice/enabled/cleanup.cpp @@ -33,12 +33,6 @@ namespace dpp { void discord_voice_client::cleanup() { - if (runner) { - this->terminating = true; - runner->join(); - delete runner; - runner = nullptr; - } if (encoder) { opus_encoder_destroy(encoder); encoder = nullptr; @@ -55,6 +49,9 @@ void discord_voice_client::cleanup() voice_courier_shared_state.signal_iteration.notify_one(); voice_courier.join(); } + if (fd != INVALID_SOCKET) { + owner->socketengine->delete_socket(fd); + } } } diff --git a/src/dpp/voice/enabled/constructor.cpp b/src/dpp/voice/enabled/constructor.cpp index 939648fc9a..54f33753ae 100644 --- a/src/dpp/voice/enabled/constructor.cpp +++ b/src/dpp/voice/enabled/constructor.cpp @@ -34,13 +34,14 @@ namespace dpp { discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave) : websocket_client(_cluster, _host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT), - runner(nullptr), connect_time(0), mixer(std::make_unique()), port(0), ssrc(0), timescale(1000000), paused(false), + sent_stop_frames(false), + last_loop_time(time(nullptr)), encoder(nullptr), repacketizer(nullptr), fd(INVALID_SOCKET), @@ -60,6 +61,11 @@ discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _ch sessionid(_session_id), server_id(_server_id), channel_id(_channel_id) +{ + setup(); +} + +void discord_voice_client::setup() { int opusError = 0; encoder = opus_encoder_create(opus_sample_rate_hz, opus_channel_count, OPUS_APPLICATION_VOIP, &opusError); diff --git a/src/dpp/voice/enabled/handle_frame.cpp b/src/dpp/voice/enabled/handle_frame.cpp index bfd85da758..4521f7a375 100644 --- a/src/dpp/voice/enabled/handle_frame.cpp +++ b/src/dpp/voice/enabled/handle_frame.cpp @@ -461,10 +461,17 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod /* Hook poll() in the ssl_client to add a new file descriptor */ this->fd = newfd; - this->custom_writeable_fd = [this] { return want_write(); }; - this->custom_readable_fd = [this] { return want_read(); }; - this->custom_writeable_ready = [this] { write_ready(); }; - this->custom_readable_ready = [this] { read_ready(); }; + + udp_events = dpp::socket_events( + fd, + WANT_READ | WANT_WRITE | WANT_ERROR, + [this](socket fd, const struct socket_events &e) { read_ready(); }, + [this](socket fd, const struct socket_events &e) { write_ready(); }, + [this](socket fd, const struct socket_events &e, int error_code) { + this->close(); + } + ); + owner->socketengine->register_socket(udp_events); int bound_port = address_t().get_port(this->fd); this->write(json({ diff --git a/src/dpp/voice/enabled/read_write.cpp b/src/dpp/voice/enabled/read_write.cpp index 52a09d5a39..e131caf784 100644 --- a/src/dpp/voice/enabled/read_write.cpp +++ b/src/dpp/voice/enabled/read_write.cpp @@ -28,20 +28,6 @@ namespace dpp { -dpp::socket discord_voice_client::want_write() { - std::lock_guard lock(this->stream_mutex); - if (!this->sent_stop_frames && !outbuf.empty()) { - return fd; - } - return INVALID_SOCKET; - -} - -dpp::socket discord_voice_client::want_read() { - return fd; -} - - void discord_voice_client::send(const char* packet, size_t len, uint64_t duration, bool send_now) { if (!send_now) [[likely]] { voice_out_packet frame; @@ -50,6 +36,10 @@ void discord_voice_client::send(const char* packet, size_t len, uint64_t duratio std::lock_guard lock(this->stream_mutex); outbuf.emplace_back(frame); + if (!this->sent_stop_frames) { + udp_events.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(udp_events); + } } else [[unlikely]] { this->udp_send(packet, len); } diff --git a/src/dpp/voice/enabled/thread.cpp b/src/dpp/voice/enabled/thread.cpp index 655e0b6a64..2d5372977a 100644 --- a/src/dpp/voice/enabled/thread.cpp +++ b/src/dpp/voice/enabled/thread.cpp @@ -20,7 +20,6 @@ * ************************************************************************************/ -#include #include #include #include @@ -29,60 +28,42 @@ namespace dpp { -void discord_voice_client::thread_run() -{ - utility::set_thread_name(std::string("vc/") + std::to_string(server_id)); +void discord_voice_client::on_disconnect() { - size_t times_looped = 0; - time_t last_loop_time = time(nullptr); + time_t current_time = time(nullptr); - do { - bool error = false; - ssl_client::read_loop(); - ssl_client::close(); + /* Here, we check if it's been longer than 3 seconds since the previous loop, + * this gives us time to see if it's an actual disconnect, or an error. + * This will prevent us from looping too much, meaning error codes do not cause an infinite loop. + */ + if (current_time - last_loop_time >= 3) { + times_looped = 0; + } - time_t current_time = time(nullptr); - /* Here, we check if it's been longer than 3 seconds since the previous loop, - * this gives us time to see if it's an actual disconnect, or an error. - * This will prevent us from looping too much, meaning error codes do not cause an infinite loop. - */ - if (current_time - last_loop_time >= 3) { - times_looped = 0; - } + /* This does mean we'll always have times_looped at a minimum of 1, this is intended. */ + times_looped++; - /* This does mean we'll always have times_looped at a minimum of 1, this is intended. */ - times_looped++; - /* If we've looped 5 or more times, abort the loop. */ - if (times_looped >= 5) { - log(dpp::ll_warning, "Reached max loops whilst attempting to read from the websocket. Aborting websocket."); - break; - } + /* If we've looped 5 or more times, abort the loop. */ + if (terminating || times_looped >= 5) { + log(dpp::ll_warning, "Reached max loops whilst attempting to read from the websocket. Aborting websocket."); + return; + } + last_loop_time = current_time; - last_loop_time = current_time; - - if (!terminating) { - log(dpp::ll_debug, "Attempting to reconnect the websocket..."); - do { - try { - ssl_client::connect(); - websocket_client::connect(); - } - catch (const std::exception &e) { - log(dpp::ll_error, std::string("Error establishing voice websocket connection, retry in 5 seconds: ") + e.what()); - ssl_client::close(); - std::this_thread::sleep_for(std::chrono::seconds(5)); - error = true; - } - } while (error && !terminating); - } - } while(!terminating); + log(dpp::ll_debug, "Attempting to reconnect the websocket..."); + owner->start_timer([this](auto handle) { + owner->stop_timer(handle); + cleanup(); + setup(); + terminating = false; + ssl_client::connect(); + websocket_client::connect(); + run(); + }, 1); } -void discord_voice_client::run() -{ - this->runner = new std::thread(&discord_voice_client::thread_run, this); - this->thread_id = runner->native_handle(); +void discord_voice_client::run() { + ssl_client::read_loop(); } - -} +} \ No newline at end of file diff --git a/src/dpp/voice/enabled/write_ready.cpp b/src/dpp/voice/enabled/write_ready.cpp index ade6b20716..385388c356 100644 --- a/src/dpp/voice/enabled/write_ready.cpp +++ b/src/dpp/voice/enabled/write_ready.cpp @@ -59,6 +59,10 @@ void discord_voice_client::write_ready() { bufsize = outbuf[0].packet.length(); outbuf.erase(outbuf.begin()); } + if (!outbuf.empty()) { + udp_events.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(udp_events); + } } } } @@ -123,5 +127,4 @@ void discord_voice_client::write_ready() { } } - } diff --git a/src/dpp/voice/stub/stubs.cpp b/src/dpp/voice/stub/stubs.cpp index 52bb2d5fe2..226b8643db 100644 --- a/src/dpp/voice/stub/stubs.cpp +++ b/src/dpp/voice/stub/stubs.cpp @@ -36,15 +36,12 @@ namespace dpp { void discord_voice_client::voice_courier_loop(discord_voice_client& client, courier_shared_state_t& shared_state) { } - void discord_voice_client::cleanup(){ + void discord_voice_client::cleanup() { } void discord_voice_client::run() { } - void discord_voice_client::thread_run() { - } - bool discord_voice_client::voice_payload::operator<(const voice_payload& other) const { return false; } @@ -71,15 +68,6 @@ namespace dpp { return *this; } - dpp::socket discord_voice_client::want_write() { - return INVALID_SOCKET; - } - - dpp::socket discord_voice_client::want_read() { - return INVALID_SOCKET; - } - - void discord_voice_client::send(const char* packet, size_t len, uint64_t duration, bool send_now) { } @@ -99,4 +87,7 @@ namespace dpp { return ""; } + void discord_voice_client::setup() { + } + } From f21c6b9057b59c73dd49adb442b52c4444199343 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 25 Nov 2024 16:16:07 +0000 Subject: [PATCH 067/112] audio works if user joins 2nd --- src/dpp/voice/enabled/handle_frame.cpp | 6 +++--- src/dpp/voice/enabled/read_write.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dpp/voice/enabled/handle_frame.cpp b/src/dpp/voice/enabled/handle_frame.cpp index 4521f7a375..6a8bfe5510 100644 --- a/src/dpp/voice/enabled/handle_frame.cpp +++ b/src/dpp/voice/enabled/handle_frame.cpp @@ -465,9 +465,9 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod udp_events = dpp::socket_events( fd, WANT_READ | WANT_WRITE | WANT_ERROR, - [this](socket fd, const struct socket_events &e) { read_ready(); }, - [this](socket fd, const struct socket_events &e) { write_ready(); }, - [this](socket fd, const struct socket_events &e, int error_code) { + [this](socket, const struct socket_events &e) { read_ready(); }, + [this](socket, const struct socket_events &e) { write_ready(); }, + [this](socket, const struct socket_events &e, int error_code) { this->close(); } ); diff --git a/src/dpp/voice/enabled/read_write.cpp b/src/dpp/voice/enabled/read_write.cpp index e131caf784..5512f08c1c 100644 --- a/src/dpp/voice/enabled/read_write.cpp +++ b/src/dpp/voice/enabled/read_write.cpp @@ -36,13 +36,13 @@ void discord_voice_client::send(const char* packet, size_t len, uint64_t duratio std::lock_guard lock(this->stream_mutex); outbuf.emplace_back(frame); - if (!this->sent_stop_frames) { - udp_events.flags = WANT_READ | WANT_WRITE | WANT_ERROR; - owner->socketengine->update_socket(udp_events); - } } else [[unlikely]] { this->udp_send(packet, len); } + if (!this->sent_stop_frames) { + udp_events.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(udp_events); + } } int discord_voice_client::udp_send(const char* data, size_t length) { From 6830a831aebcd9c36e4099aeb1a6f37b39d43ef5 Mon Sep 17 00:00:00 2001 From: Neko Life Date: Wed, 27 Nov 2024 16:01:09 +0700 Subject: [PATCH 068/112] Fix/voice socketengine (#1337) --- include/dpp/cluster.h | 2 +- src/dpp/cluster.cpp | 10 +++++++--- src/dpp/socketengines/epoll.cpp | 1 + src/dpp/sslclient.cpp | 4 ++++ src/dpp/voice/enabled/read_write.cpp | 4 ---- src/dpp/voice/enabled/write_ready.cpp | 14 +++++++++----- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index c30c378735..fcae83b18e 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -183,7 +183,7 @@ class DPP_EXPORT cluster { * @brief Used to spawn the socket engine into its own thread if * the cluster is started with dpp::st_return. It is unused otherwise. */ - std::unique_ptr engine_thread{nullptr}; + std::thread engine_thread; /** * @brief Protection mutex for timers diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 604deff496..3d41f311a9 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -298,7 +298,7 @@ void cluster::start(bool return_after) { }); if (return_after) { - engine_thread = std::make_unique([event_loop]() { + engine_thread = std::thread([event_loop]() { dpp::utility::set_thread_name("event_loop"); event_loop(); }); @@ -310,9 +310,12 @@ void cluster::start(bool return_after) { void cluster::shutdown() { /* Signal termination */ terminating = true; - if (engine_thread) { - engine_thread->join(); + + if (engine_thread.joinable()) { + /* Join engine_thread if it ever started */ + engine_thread.join(); } + { std::lock_guard l(timer_guard); /* Free memory for active timers */ @@ -322,6 +325,7 @@ void cluster::shutdown() { timer_list.clear(); next_timer.clear(); } + /* Terminate shards */ for (const auto& sh : shards) { delete sh.second; diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index cfde495d94..c330cd4255 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -111,6 +111,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { } if ((ev.events & EPOLLOUT) != 0U) { + /* Should we have a flag to allow keeping WANT_WRITE? Maybe like WANT_WRITE_ONCE or GREEDY_WANT_WRITE, eh */ eh->flags = modify_event(epoll_handle, eh, eh->flags & ~WANT_WRITE); if (eh->on_write) { eh->on_write(fd, *eh); diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 534ecc4705..7fb71696cf 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -373,6 +373,10 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { } void ssl_client::on_write(socket fd, const struct socket_events& e) { + /* We wanted write before so keep it */ + socket_events se{e}; + se.flags |= WANT_WRITE; + owner->socketengine->update_socket(se); if (!tcp_connect_done) { tcp_connect_done = true; diff --git a/src/dpp/voice/enabled/read_write.cpp b/src/dpp/voice/enabled/read_write.cpp index 5512f08c1c..fd41423796 100644 --- a/src/dpp/voice/enabled/read_write.cpp +++ b/src/dpp/voice/enabled/read_write.cpp @@ -39,10 +39,6 @@ void discord_voice_client::send(const char* packet, size_t len, uint64_t duratio } else [[unlikely]] { this->udp_send(packet, len); } - if (!this->sent_stop_frames) { - udp_events.flags = WANT_READ | WANT_WRITE | WANT_ERROR; - owner->socketengine->update_socket(udp_events); - } } int discord_voice_client::udp_send(const char* data, size_t length) { diff --git a/src/dpp/voice/enabled/write_ready.cpp b/src/dpp/voice/enabled/write_ready.cpp index 385388c356..6877a97c26 100644 --- a/src/dpp/voice/enabled/write_ready.cpp +++ b/src/dpp/voice/enabled/write_ready.cpp @@ -31,6 +31,13 @@ namespace dpp { void discord_voice_client::write_ready() { + /* + * WANT_WRITE has been reset everytime this method is being called, + * ALWAYS set it again no matter what we're gonna do. + */ + udp_events.flags = WANT_READ | WANT_WRITE | WANT_ERROR; + owner->socketengine->update_socket(udp_events); + uint64_t duration = 0; bool track_marker_found = false; uint64_t bufsize = 0; @@ -54,15 +61,12 @@ void discord_voice_client::write_ready() { } } if (!outbuf.empty()) { - if (this->udp_send(outbuf[0].packet.data(), outbuf[0].packet.length()) == (int)outbuf[0].packet.length()) { + int sent_siz = this->udp_send(outbuf[0].packet.data(), outbuf[0].packet.length()); + if (sent_siz == (int)outbuf[0].packet.length()) { duration = outbuf[0].duration * timescale; bufsize = outbuf[0].packet.length(); outbuf.erase(outbuf.begin()); } - if (!outbuf.empty()) { - udp_events.flags = WANT_READ | WANT_WRITE | WANT_ERROR; - owner->socketengine->update_socket(udp_events); - } } } } From c26defc32b1079aec003f2f29acc7dc7fe89450f Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 27 Nov 2024 09:21:37 +0000 Subject: [PATCH 069/112] fix: SE shadows variable --- src/davetest/dave.cpp | 2 +- src/dpp/sslclient.cpp | 8 ++++---- src/unittest/test.cpp | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/davetest/dave.cpp b/src/davetest/dave.cpp index 72bac9231b..94812e0d89 100644 --- a/src/davetest/dave.cpp +++ b/src/davetest/dave.cpp @@ -80,7 +80,7 @@ int main() { dave_test.on_guild_create([&](const dpp::guild_create_t & event) { if (event.created->id == TEST_GUILD_ID) { dpp::discord_client* s = dave_test.get_shard(0); - bool muted = false, deaf = false, enable_dave = false; + bool muted = false, deaf = false, enable_dave = true; s->connect_voice(TEST_GUILD_ID, TEST_VC_ID, muted, deaf, enable_dave); } }); diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 7fb71696cf..4e81c0f887 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -373,10 +373,10 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { } void ssl_client::on_write(socket fd, const struct socket_events& e) { - /* We wanted write before so keep it */ - socket_events se{e}; - se.flags |= WANT_WRITE; - owner->socketengine->update_socket(se); + /* We wanted to write before so keep it */ + socket_events new_se{e}; + new_se.flags |= WANT_WRITE; + owner->socketengine->update_socket(new_se); if (!tcp_connect_done) { tcp_connect_done = true; diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 955d6f3781..bded8f1efd 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -1135,7 +1135,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } }); - bot.on_guild_create([](const dpp::guild_create_t & event) { + bot.on_guild_create([&bot](const dpp::guild_create_t & event) { if (event.created->id == TEST_GUILD_ID) { set_test(GUILDCREATE, true); if (event.presences.size() && event.presences.begin()->second.user_id > 0) { @@ -1146,8 +1146,8 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (g) { set_test(CACHE, true); set_test(VOICECONN, false); - //dpp::discord_client* s = bot.get_shard(0); - //s->connect_voice(g->id, TEST_VC_ID, false, false); + dpp::discord_client* s = bot.get_shard(0); + s->connect_voice(g->id, TEST_VC_ID, false, false); } else { set_test(CACHE, false); From f2d5cfd4f5ee1de5b1029c8f45738e0628dbd5de Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 27 Nov 2024 12:25:45 +0000 Subject: [PATCH 070/112] refactor: remove thread pool from request queue, run it off dpp::timer instances instead --- include/dpp/cluster.h | 6 +- include/dpp/coro/awaitable.h | 1 + include/dpp/discordvoiceclient.h | 50 ++++---- include/dpp/queues.h | 148 ++++++++++++----------- src/dpp/cluster.cpp | 9 +- src/dpp/queues.cpp | 199 ++++++++++++++++--------------- src/dpp/socketengines/epoll.cpp | 2 +- src/dpp/socketengines/kqueue.cpp | 2 +- src/dpp/socketengines/poll.cpp | 4 + src/dpp/sslclient.cpp | 3 +- src/unittest/test.cpp | 12 +- 11 files changed, 230 insertions(+), 206 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index fcae83b18e..9b193f8098 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -272,11 +272,11 @@ class DPP_EXPORT cluster { * @param maxclusters The total number of clusters that are active, which may be on separate processes or even separate machines. * @param compressed Whether or not to use compression for shards on this cluster. Saves a ton of bandwidth at the cost of some CPU * @param policy Set the caching policy for the cluster, either lazy (only cache users/members when they message the bot) or aggressive (request whole member lists on seeing new guilds too) - * @param request_threads The number of threads to allocate for making HTTP requests to Discord. This defaults to 12. You can increase this at runtime via the object returned from get_rest(). - * @param request_threads_raw The number of threads to allocate for making HTTP requests to sites outside of Discord. This defaults to 1. You can increase this at runtime via the object returned from get_raw_rest(). + * @param pool_threads The number of threads to allocate for the thread pool. This defaults to half your system concurrency and if set to a number less than 4, will default to 4. + * All callbacks and events are placed into the thread pool. The bigger you make this pool (but generally no bigger than your number of cores), the more your bot will scale. * @throw dpp::exception Thrown on windows, if WinSock fails to initialise, or on any other system if a dpp::request_queue fails to construct */ - cluster(const std::string& token, uint32_t intents = i_default_intents, uint32_t shards = 0, uint32_t cluster_id = 0, uint32_t maxclusters = 1, bool compressed = true, cache_policy_t policy = cache_policy::cpol_default, uint32_t request_threads = 12, uint32_t request_threads_raw = 1); + cluster(const std::string& token, uint32_t intents = i_default_intents, uint32_t shards = 0, uint32_t cluster_id = 0, uint32_t maxclusters = 1, bool compressed = true, cache_policy_t policy = cache_policy::cpol_default, uint32_t pool_threads = std::thread::hardware_concurrency() / 2); /** * @brief Place some arbitrary work into the thread pool for execution when time permits. diff --git a/include/dpp/coro/awaitable.h b/include/dpp/coro/awaitable.h index e8974c0a88..296ace2490 100644 --- a/include/dpp/coro/awaitable.h +++ b/include/dpp/coro/awaitable.h @@ -44,6 +44,7 @@ struct awaitable_dummy { #include #include #include +#include namespace dpp { diff --git a/include/dpp/discordvoiceclient.h b/include/dpp/discordvoiceclient.h index b902208d4c..14a0f63ec8 100644 --- a/include/dpp/discordvoiceclient.h +++ b/include/dpp/discordvoiceclient.h @@ -271,7 +271,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Last connect time of voice session */ - time_t connect_time; + time_t connect_time{}; /* * @brief For mixing outgoing voice data. @@ -286,12 +286,12 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Port number of UDP/RTP endpoint */ - uint16_t port; + uint16_t port{}; /** * @brief SSRC value */ - uint64_t ssrc; + uint64_t ssrc{}; /** * @brief List of supported audio encoding modes @@ -301,7 +301,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Timescale in nanoseconds */ - uint64_t timescale; + uint64_t timescale{}; /** * @brief Output buffer @@ -428,14 +428,14 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief If true, audio packet sending is paused */ - bool paused; + bool paused{}; /** * @brief Whether has sent 5 frame of silence before stopping on pause. * * This is to avoid unintended Opus interpolation with subsequent transmissions. */ - bool sent_stop_frames; + bool sent_stop_frames{}; /** * @brief Number of times we have tried to reconnect in the last few seconds @@ -451,13 +451,13 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief libopus encoder */ - OpusEncoder* encoder; + OpusEncoder* encoder{}; /** * @brief libopus repacketizer * (merges frames into one packet) */ - OpusRepacketizer* repacketizer; + OpusRepacketizer* repacketizer{}; /** * @brief This holds the state information for DAVE E2EE. @@ -499,14 +499,14 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief File descriptor for UDP connection */ - dpp::socket fd; + dpp::socket fd{}; /** * @brief Secret key for encrypting voice. * If it has been sent, this contains a sequence of exactly 32 bytes * (secret_key_size) and has_secret_key is set to true. */ - std::array secret_key; + std::array secret_key{}; /** * @brief True if the voice client has a secret key @@ -517,21 +517,21 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @brief Sequence number of outbound audio. This is incremented * once per frame sent. */ - uint16_t sequence; + uint16_t sequence{}; /** * @brief Last received sequence from gateway. * * Needed for heartbeat and resume payload. */ - int32_t receive_sequence; + int32_t receive_sequence{}; /** * @brief Timestamp value used in outbound audio. Each packet * has the timestamp value which is incremented to match * how many frames are sent. */ - uint32_t timestamp; + uint32_t timestamp{}; /** * @brief Each packet should have a nonce, a 32-bit incremental @@ -542,7 +542,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * * Current initial value is hardcoded to 1. */ - uint32_t packet_nonce; + uint32_t packet_nonce{}; /** * @brief Last sent packet high-resolution timestamp @@ -552,7 +552,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Fraction of the sleep that was not executed after the last audio packet was sent */ - std::chrono::nanoseconds last_sleep_remainder; + std::chrono::nanoseconds last_sleep_remainder{}; /** * @brief Maps receiving ssrc to user id @@ -564,7 +564,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * When this moves from false to true, this causes the * client to send the 'talking' notification to the websocket. */ - bool sending; + bool sending{}; /** * @brief Number of track markers in the buffer. For example if there @@ -575,7 +575,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * If the buffer is empty, there are zero tracks in the * buffer. */ - uint32_t tracks; + uint32_t tracks{}; /** * @brief Meta data associated with each track. @@ -587,7 +587,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Encoding buffer for opus repacketizer and encode */ - uint8_t encode_buffer[65536]; + uint8_t encode_buffer[65536]{}; /** * @brief DAVE - Discord Audio Visual Encryption @@ -708,37 +708,37 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Owning cluster */ - class dpp::cluster* creator; + class dpp::cluster* creator{}; /** * @brief True when the thread is shutting down */ - bool terminating; + bool terminating{}; /** * @brief The gain value for the end of the current voice iteration. */ - float end_gain; + float end_gain{}; /** * @brief The gain value for the current voice iteration. */ - float current_gain; + float current_gain{}; /** * @brief The amount to increment each successive sample for, for the current voice iteration. */ - float increment; + float increment{}; /** * @brief Heartbeat interval for sending heartbeat keepalive */ - uint32_t heartbeat_interval; + uint32_t heartbeat_interval{}; /** * @brief Last voice channel websocket heartbeat */ - time_t last_heartbeat; + time_t last_heartbeat{}; /** * @brief Discord voice session token diff --git a/include/dpp/queues.h b/include/dpp/queues.h index bd43db6a72..f54cb9c7a9 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -25,11 +25,10 @@ #include #include #include -#include #include +#include #include #include -#include #include #include @@ -172,8 +171,11 @@ struct DPP_EXPORT http_request_completion_t { * @brief Results of HTTP requests are called back to these std::function types. * * @note Returned http_completion_events are called ASYNCHRONOUSLY in your - * code which means they execute in a separate thread. The completion events - * arrive in order. + * code which means they execute in a separate thread, results for the requests going + * into a dpp::thread_pool. Completion events may not arrive in order depending on if + * one request takes longer than another. Using the callbacks or using coroutines + * correctly ensures that the order they arrive in the queue does not negatively affect + * your code. */ typedef std::function http_completion_event; @@ -377,7 +379,7 @@ class DPP_EXPORT http_request { * @brief Execute the HTTP request and mark the request complete. * @param owner creating cluster */ - http_request_completion_t run(class in_thread* processor, class cluster* owner); + http_request_completion_t run(class request_concurrency_queue* processor, class cluster* owner); /** @brief Returns true if the request is complete */ bool is_completed(); @@ -416,27 +418,32 @@ struct DPP_EXPORT bucket_t { /** - * @brief Represents a thread in the thread pool handling requests to HTTP(S) servers. - * There are several of these, the total defined by a constant in queues.cpp, and each + * @brief Represents a timer instance in a pool handling requests to HTTP(S) servers. + * There are several of these, the total defined by a constant in cluster.cpp, and each * one will always receive requests for the same rate limit bucket based on its endpoint * portion of the url. This makes rate limit handling reliable and easy to manage. - * Each of these also has its own mutex, so that requests are less likely to block while - * waiting for internal containers to be usable. + * Each of these also has its own mutex, making it thread safe to call and use these + * from anywhere in the code. */ -class DPP_EXPORT in_thread { +class DPP_EXPORT request_concurrency_queue { public: + /** + * @brief Queue index + */ + int in_index{0}; + /** * @brief True if ending. */ std::atomic terminating; /** - * @brief Request queue that owns this in_thread. + * @brief Request queue that owns this request_concurrency_queue. */ class request_queue* requests; /** - * @brief The cluster that owns this in_thread. + * @brief The cluster that owns this request_concurrency_queue. */ class cluster* creator; @@ -446,14 +453,12 @@ class DPP_EXPORT in_thread { std::shared_mutex in_mutex; /** - * @brief Inbound queue thread. + * @brief Inbound queue timer. The timer is called every second, + * and when it wakes up it checks for requests pending to be sent in the queue. + * If there are any requests and we are not waiting on rate limit, it will send them, + * else it will wait for the rate limit to expire. */ - std::thread* in_thr; - - /** - * @brief Inbound queue condition, signalled when there are requests to fulfill. - */ - std::condition_variable in_ready; + dpp::timer in_timer; /** * @brief Rate-limit bucket counters. @@ -466,34 +471,34 @@ class DPP_EXPORT in_thread { std::vector> requests_in; /** - * @brief Inbound queue thread loop. - * @param index Thread index + * @brief Timer callback + * @param index Index ID for this timer */ - void in_loop(uint32_t index); + void tick_and_deliver_requests(uint32_t index); /** - * @brief Construct a new in thread object + * @brief Construct a new concurrency queue object * * @param owner Owning cluster * @param req_q Owning request queue - * @param index Thread index number + * @param index Queue index number, uniquely identifies this queue for hashing */ - in_thread(class cluster* owner, class request_queue* req_q, uint32_t index); + request_concurrency_queue(class cluster* owner, class request_queue* req_q, uint32_t index); /** - * @brief Destroy the in thread object - * This will end the thread that is owned by this object by joining it. + * @brief Destroy the concurrency queue object + * This will stop the timer. */ - ~in_thread(); + ~request_concurrency_queue(); /** - * @brief Terminates the thread - * This will end the thread that is owned by this object, but will not join it. + * @brief Flags the queue as terminating + * This will set the internal atomic bool that indicates this queue is to accept no more requests */ void terminate(); /** - * @brief Post a http_request to this thread. + * @brief Post a http_request to this queue. * * @param req http_request to post. The pointer will be freed when it has * been executed. @@ -507,22 +512,24 @@ class DPP_EXPORT in_thread { * * It ensures asynchronous delivery of events and queueing of requests. * - * It will spawn two threads, one to make outbound HTTP requests and push the returned - * results into a queue, and the second to call the callback methods with these results. - * They are separated so that if the user decides to take a long time processing a reply - * in their callback it won't affect when other requests are sent, and if a HTTP request - * takes a long time due to latency, it won't hold up user processing. + * It will spawn multiple timers to make outbound HTTP requests and then call the callbacks + * of those requests on completion within the dpp::thread_pool for the cluster. + * If the user decides to take a long time processing a reply in their callback it won't affect + * when other requests are sent, and if a HTTP request takes a long time due to latency, it won't + * hold up user processing. * * There are usually two request_queue objects in each dpp::cluster, one of which is used * internally for the various REST methods to Discord such as sending messages, and the other - * used to support user REST calls via dpp::cluster::request(). + * used to support user REST calls via dpp::cluster::request(). They are separated so that the + * one for user requests can be specifically configured to never ever send the Discord token + * unless it is explicitly placed into the request, for security reasons. */ class DPP_EXPORT request_queue { public: /** - * @brief Required so in_thread can access these member variables + * @brief Required so request_concurrency_queue can access these member variables */ - friend class in_thread; + friend class request_concurrency_queue; /** * @brief The cluster that owns this request_queue @@ -545,66 +552,69 @@ class DPP_EXPORT request_queue { }; /** - * @brief A vector of inbound request threads forming a pool. - * There are a set number of these defined by a constant in queues.cpp. A request is always placed + * @brief A vector of timers forming a pool. + * + * There are a set number of these defined by a constant in cluster.cpp. A request is always placed * on the same element in this vector, based upon its url, so that two conditions are satisfied: - * 1) Any requests for the same ratelimit bucket are handled by the same thread in the pool so that + * + * 1) Any requests for the same ratelimit bucket are handled by the same concurrency queue in the pool so that * they do not create unnecessary 429 errors, * 2) Requests for different endpoints go into different buckets, so that they may be requested in parallel - * A global ratelimit event pauses all threads in the pool. These are few and far between. + * A global ratelimit event pauses all timers in the pool. These are few and far between. */ - std::vector> requests_in; + std::vector> requests_in; /** - * @brief Set to true if the threads should terminate + * @brief Set to true if the timers should terminate. + * When this is set to true no further requests are accepted to the queues. */ std::atomic terminating; /** - * @brief True if globally rate limited - makes the entire request thread wait + * @brief True if globally rate limited + * + * When globally rate limited the concurrency queues associated with this request queue + * will not process any requests in their timers until the global rate limit expires. */ bool globally_ratelimited; /** - * @brief How many seconds we are globally rate limited for + * @brief When we are globally rate limited until (unix epoch) * - * @note Only if globally_ratelimited is true. + * @note Only valid if globally_rate limited is true. If we are globally rate limited, + * queues in this class will not process requests until the current unix epoch time + * is greater than this time. */ - uint64_t globally_limited_for; + time_t globally_limited_until; /** - * @brief Number of request threads in the thread pool + * @brief Number of request queues in the pool. This is the direct size of the requests_in + * vector. */ - uint32_t in_thread_pool_size; + uint32_t in_queue_pool_size; /** * @brief constructor * @param owner The creating cluster. - * @param request_threads The number of http request threads to allocate to the threadpool. - * By default eight threads are allocated. - * Side effects: Creates threads for the queue - */ - request_queue(class cluster* owner, uint32_t request_threads = 8); - - /** - * @brief Add more request threads to the library at runtime. - * @note You should do this at a quiet time when there are few requests happening. - * This will reorganise the hashing used to place requests into the thread pool so if you do - * this while the bot is busy there is a small chance of receiving "429 rate limited" errors. - * @param request_threads Number of threads to add. It is not possible to scale down at runtime. - * @return reference to self + * @param request_concurrency The number of http request queues to allocate. + * Each request queue is a dpp::timer which ticks every second looking for new + * requests to run. The timer will hold back requests if we are waiting as to comply + * with rate limits. Adding a request to this class will cause the queue it is placed in + * to run immediately but this cannot override rate limits. + * By default eight concurrency queues are allocated. + * Side effects: Creates timers for the queue */ - request_queue& add_request_threads(uint32_t request_threads); + request_queue(class cluster* owner, uint32_t request_concurrency = 8); /** - * @brief Get the request thread count - * @return uint32_t number of request threads that are active + * @brief Get the request queue concurrency count + * @return uint32_t number of request queues that are active */ - uint32_t get_request_thread_count() const; + uint32_t get_request_queue_count() const; /** * @brief Destroy the request queue object. - * Side effects: Joins and deletes queue threads + * Side effects: Ends and deletes concurrency timers */ ~request_queue(); diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 3d41f311a9..99926b3f37 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -84,16 +84,17 @@ template bool DPP_EXPORT validate_configuration(); template bool DPP_EXPORT validate_configuration(); -cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, uint32_t _cluster_id, uint32_t _maxclusters, bool comp, cache_policy_t policy, uint32_t request_threads, uint32_t request_threads_raw) +cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, uint32_t _cluster_id, uint32_t _maxclusters, bool comp, cache_policy_t policy, uint32_t pool_threads) : default_gateway("gateway.discord.gg"), rest(nullptr), raw_rest(nullptr), compressed(comp), start_time(0), token(_token), last_identify(time(nullptr) - 5), intents(_intents), numshards(_shards), cluster_id(_cluster_id), maxclusters(_maxclusters), rest_ping(0.0), cache_policy(policy), ws_mode(ws_json) { socketengine = create_socket_engine(this); - pool = std::make_unique(this, request_threads); + pool = std::make_unique(this, pool_threads > 4 ? pool_threads : 4); /* Instantiate REST request queues */ try { - rest = new request_queue(this, request_threads); - raw_rest = new request_queue(this, request_threads_raw); + /* NOTE: These no longer use threads. This instantiates 16+4 dpp::timer instances. */ + rest = new request_queue(this, 16); + raw_rest = new request_queue(this, 4); } catch (std::bad_alloc&) { delete rest; diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index f1f34f4cba..0b76a4833b 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -20,16 +20,20 @@ * ************************************************************************************/ #include -#ifdef _WIN32 -/* Central point for forcing inclusion of winsock library for all socket code */ -#include -#endif #include #include #include +#ifdef _WIN32 + #include +#endif namespace dpp { +/** + * @brief List of possible request verbs. + * + * This MUST MATCH the size of the dpp::http_method enum! + */ constexpr std::array request_verb { "GET", "POST", @@ -151,7 +155,7 @@ bool http_request::is_completed() } /* Execute a HTTP request */ -http_request_completion_t http_request::run(in_thread* processor, cluster* owner) { +http_request_completion_t http_request::run(request_concurrency_queue* processor, cluster* owner) { http_request_completion_t rv; double start = dpp::utility::time_f(); @@ -247,7 +251,8 @@ http_request_completion_t http_request::run(in_thread* processor, cluster* owner newbucket.timestamp = time(nullptr); processor->requests->globally_ratelimited = rv.ratelimit_global; if (processor->requests->globally_ratelimited) { - processor->requests->globally_limited_for = (newbucket.retry_after ? newbucket.retry_after : newbucket.reset_after); + /* We are globally rate limited - user up to shenanigans */ + processor->requests->globally_limited_until = (newbucket.retry_after ? newbucket.retry_after : newbucket.reset_after) + newbucket.timestamp; } processor->buckets[this->endpoint] = newbucket; @@ -269,141 +274,131 @@ void http_request::stash_self(std::unique_ptr self) { me = std::move(self); } -request_queue::request_queue(class cluster* owner, uint32_t request_threads) : creator(owner), terminating(false), globally_ratelimited(false), globally_limited_for(0), in_thread_pool_size(request_threads) +request_queue::request_queue(class cluster* owner, uint32_t request_concurrency) : creator(owner), terminating(false), globally_ratelimited(false), globally_limited_until(0), in_queue_pool_size(request_concurrency) { - for (uint32_t in_alloc = 0; in_alloc < in_thread_pool_size; ++in_alloc) { - requests_in.push_back(std::make_unique(owner, this, in_alloc)); + /* Create request_concurrency timer instances */ + for (uint32_t in_alloc = 0; in_alloc < in_queue_pool_size; ++in_alloc) { + requests_in.push_back(std::make_unique(owner, this, in_alloc)); } } -request_queue& request_queue::add_request_threads(uint32_t request_threads) +uint32_t request_queue::get_request_queue_count() const { - for (uint32_t in_alloc_ex = 0; in_alloc_ex < request_threads; ++in_alloc_ex) { - requests_in.push_back(std::make_unique(creator, this, in_alloc_ex + in_thread_pool_size)); - } - in_thread_pool_size += request_threads; - return *this; + return in_queue_pool_size; } -uint32_t request_queue::get_request_thread_count() const +request_concurrency_queue::request_concurrency_queue(class cluster* owner, class request_queue* req_q, uint32_t index) : in_index(index), terminating(false), requests(req_q), creator(owner) { - return in_thread_pool_size; + in_timer = creator->start_timer([this](auto timer_handle) { + tick_and_deliver_requests(in_index); + }, 1); } -in_thread::in_thread(class cluster* owner, class request_queue* req_q, uint32_t index) : terminating(false), requests(req_q), creator(owner) -{ - this->in_thr = new std::thread(&in_thread::in_loop, this, index); -} - -in_thread::~in_thread() +request_concurrency_queue::~request_concurrency_queue() { terminate(); - in_thr->join(); - delete in_thr; + creator->stop_timer(in_timer); } -void in_thread::terminate() +void request_concurrency_queue::terminate() { terminating.store(true, std::memory_order_relaxed); - in_ready.notify_one(); } request_queue::~request_queue() { terminating.store(true, std::memory_order_relaxed); for (auto& in_thr : requests_in) { - in_thr->terminate(); // signal all of them here, otherwise they will all join 1 by 1 and it will take forever + /* Note: We don't need to set the atomic to make timers quit, this is purely + * to prevent additional requests going into the queue while it is being destructed + * from other threads, + */ + in_thr->terminate(); } } -void in_thread::in_loop(uint32_t index) +void request_concurrency_queue::tick_and_deliver_requests(uint32_t index) { - utility::set_thread_name(std::string("http_req/") + std::to_string(index)); - while (!terminating.load(std::memory_order_relaxed)) { - std::mutex mtx; - std::unique_lock lock{ mtx }; - in_ready.wait_for(lock, std::chrono::seconds(1)); - /* New request to be sent! */ + if (terminating) { + return; + } - if (!requests->globally_ratelimited) { + if (!requests->globally_ratelimited) { - std::vector requests_view; - { - /* Gather all the requests first within a mutex */ - std::shared_lock lock(in_mutex); - if (requests_in.empty()) { - /* Nothing to copy, wait again */ - continue; - } - requests_view.reserve(requests_in.size()); - std::transform(requests_in.begin(), requests_in.end(), std::back_inserter(requests_view), [](const std::unique_ptr &r) { - return r.get(); - }); + std::vector requests_view; + { + /* Gather all the requests first within a mutex */ + std::shared_lock lock(in_mutex); + if (requests_in.empty()) { + /* Nothing to copy, check again when we call the timer in a second */ + return; } + requests_view.reserve(requests_in.size()); + std::transform(requests_in.begin(), requests_in.end(), std::back_inserter(requests_view), [](const std::unique_ptr &r) { + return r.get(); + }); + } - for (auto& request_view : requests_view) { - const std::string &key = request_view->endpoint; - http_request_completion_t rv; - auto currbucket = buckets.find(key); - - if (currbucket != buckets.end()) { - /* There's a bucket for this request. Check its status. If the bucket says to wait, - * skip all requests in this bucket till its ok. - */ - if (currbucket->second.remaining < 1) { - uint64_t wait = (currbucket->second.retry_after ? currbucket->second.retry_after : currbucket->second.reset_after); - if ((uint64_t)time(nullptr) > currbucket->second.timestamp + wait) { - /* Time has passed, we can process this bucket again. send its request. */ - request_view->run(this, creator); - } else { - if (!request_view->waiting) { - request_view->waiting = true; - } - /* Time not up yet, wait more */ - break; - } - } else { - /* There's limit remaining, we can just run the request */ + for (auto& request_view : requests_view) { + const std::string &key = request_view->endpoint; + http_request_completion_t rv; + auto currbucket = buckets.find(key); + + if (currbucket != buckets.end()) { + /* There's a bucket for this request. Check its status. If the bucket says to wait, + * skip all requests until the timer value indicates the rate limit won't be hit + */ + if (currbucket->second.remaining < 1) { + uint64_t wait = (currbucket->second.retry_after ? currbucket->second.retry_after : currbucket->second.reset_after); + if ((uint64_t)time(nullptr) > currbucket->second.timestamp + wait) { + /* Time has passed, we can process this bucket again. send its request. */ request_view->run(this, creator); + } else { + if (!request_view->waiting) { + request_view->waiting = true; + } + /* Time not up yet, wait more */ + break; } } else { - /* No bucket for this endpoint yet. Just send it, and make one from its reply */ + /* We aren't at the limit, so we can just run the request */ request_view->run(this, creator); } + } else { + /* No bucket for this endpoint yet. Just send it, and make one from its reply */ + request_view->run(this, creator); + } - /* Remove from inbound requests */ - std::unique_ptr rq; - { - /* Find the owned pointer in requests_in */ - std::scoped_lock lock1{in_mutex}; - - const std::string &key = request_view->endpoint; - auto [begin, end] = std::equal_range(requests_in.begin(), requests_in.end(), key, compare_request{}); - for (auto it = begin; it != end; ++it) { - if (it->get() == request_view) { - /* Grab and remove */ - // NOTE: Where to move this to?! - request_view->stash_self(std::move(*it)); - requests_in.erase(it); - break; - } + /* Remove from inbound requests */ + std::unique_ptr rq; + { + /* Find the owned pointer in requests_in */ + std::scoped_lock lock1{in_mutex}; + + const std::string &key = request_view->endpoint; + auto [begin, end] = std::equal_range(requests_in.begin(), requests_in.end(), key, compare_request{}); + for (auto it = begin; it != end; ++it) { + if (it->get() == request_view) { + /* Grab and remove */ + request_view->stash_self(std::move(*it)); + requests_in.erase(it); + break; } } } + } - } else { - if (requests->globally_limited_for > 0) { - std::this_thread::sleep_for(std::chrono::seconds(requests->globally_limited_for)); - requests->globally_limited_for = 0; - } + } else { + /* If we are globally rate limited, do nothing until we are not */ + if (time(nullptr) > requests->globally_limited_until) { + requests->globally_limited_until = 0; requests->globally_ratelimited = false; - in_ready.notify_one(); } } } /* Post a http_request into the queue */ -void in_thread::post_request(std::unique_ptr req) +void request_concurrency_queue::post_request(std::unique_ptr req) { { std::scoped_lock lock(in_mutex); @@ -411,17 +406,21 @@ void in_thread::post_request(std::unique_ptr req) auto where = std::lower_bound(requests_in.begin(), requests_in.end(), req->endpoint, compare_request{}); requests_in.emplace(where, std::move(req)); } - in_ready.notify_one(); + /* Immediately trigger requests in this queue */ + tick_and_deliver_requests(in_index); } -/* Simple hash function for hashing urls into thread pool values, - * ensuring that the same url always ends up on the same thread, +/* @brief Simple hash function for hashing urls into request pool values, + * ensuring that the same url always ends up in the same queue, * which means that it will be part of the same ratelimit bucket. * I did consider std::hash for this, but std::hash returned even * numbers for absolutely every string i passed it on g++ 10.0, * so this was a no-no. There are also much bigger more complex * hash functions that claim to be really fast, but this is * readable and small and fits the requirement exactly. + * + * @param s String to hash + * @return Hash value */ inline uint32_t hash(const char *s) { @@ -435,7 +434,9 @@ inline uint32_t hash(const char *s) /* Post a http_request into a request queue */ request_queue& request_queue::post_request(std::unique_ptr req) { - requests_in[hash(req->endpoint.c_str()) % in_thread_pool_size]->post_request(std::move(req)); + if (!terminating) { + requests_in[hash(req->endpoint.c_str()) % in_queue_pool_size]->post_request(std::move(req)); + } return *this; } diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index c330cd4255..7418f6c7fd 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -85,7 +85,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { } const int fd = eh->fd; - if (fd == INVALID_SOCKET) { + if (fd == INVALID_SOCKET || eh->flags & WANT_DELETION) { continue; } diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index ce2da57653..9240d2e17b 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -68,7 +68,7 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { for (int j = 0; j < i; j++) { const struct kevent& kev = ke_list[j]; auto* eh = reinterpret_cast(kev.udata); - if (eh == nullptr) { + if (eh == nullptr || eh->flags & WANT_DELETION) { continue; } diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index dd89c6efda..a336a20954 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -96,6 +96,10 @@ struct DPP_EXPORT socket_engine_poll : public socket_engine_base { } socket_events *eh = iter->second.get(); + if (eh == nullptr || eh->flags & WANT_DELETION) { + continue; + } + try { if ((revents & POLLHUP) != 0) { diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 4e81c0f887..454f9517c2 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -547,8 +547,8 @@ void ssl_client::close() SSL_free(ssl->ssl); ssl->ssl = nullptr; } - close_socket(sfd); owner->socketengine->delete_socket(sfd); + close_socket(sfd); sfd = INVALID_SOCKET; obuffer.clear(); buffer.clear(); @@ -565,6 +565,7 @@ ssl_client::~ssl_client() cleanup(); if (timer_handle) { owner->stop_timer(timer_handle); + timer_handle = 0; } } diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index bded8f1efd..9b2e0778ab 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -1178,19 +1178,25 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } void set_pin_tested() { - assert(!pin_tested); + if (pin_tested) { + return; + } pin_tested = true; delete_message_if_done(); } void set_thread_tested() { - assert(!thread_tested); + if (thread_tested) { + return; + } thread_tested = true; delete_message_if_done(); } void set_file_tested(size_t index) { - assert(!files_tested[index]); + if (files_tested[index]) { + return; + } files_tested[index] = true; if (files_tested == std::array{true, true, true}) { set_test(MESSAGEFILE, files_success == std::array{true, true, true}); From 70c0ba363815ff272a07d362dc8a22311a4afa56 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 27 Nov 2024 12:36:34 +0000 Subject: [PATCH 071/112] fix: now compiles with voice disabled --- .github/workflows/ci.yml | 4 ++-- src/dpp/voice/stub/stubs.cpp | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0f3dc4543..c9235164ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,8 +133,8 @@ jobs: matrix: cfg: - { arch: 'arm64', concurrency: 3, os: macos-15, cpp: clang++, version: 16, cmake-flags: '', xcode-version: '16.0.0' } - - { arch: 'arm64', concurrency: 3, os: macos-15, cpp: clang++, version: 15, cmake-flags: '', xcode-version: '15.3' } - - { arch: 'arm64', concurrency: 3, os: macos-15, cpp: clang++, version: 14, cmake-flags: '', xcode-version: '14.3.1' } + - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 15, cmake-flags: '', xcode-version: '15.3' } + - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 14, cmake-flags: '', xcode-version: '14.3.1' } steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 diff --git a/src/dpp/voice/stub/stubs.cpp b/src/dpp/voice/stub/stubs.cpp index 226b8643db..97fd12cbc0 100644 --- a/src/dpp/voice/stub/stubs.cpp +++ b/src/dpp/voice/stub/stubs.cpp @@ -90,4 +90,7 @@ namespace dpp { void discord_voice_client::setup() { } + void on_disconnect() { + } + } From 7ce5182feb4687fe6be8b2e8428499792910c5e0 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 27 Nov 2024 12:58:52 +0000 Subject: [PATCH 072/112] fix: macos CI --- .github/workflows/ci.yml | 2 -- src/dpp/voice/stub/stubs.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9235164ee..b267dbb936 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,9 +132,7 @@ jobs: fail-fast: false # Don't fail everything if one fails. We want to test each OS/Compiler individually matrix: cfg: - - { arch: 'arm64', concurrency: 3, os: macos-15, cpp: clang++, version: 16, cmake-flags: '', xcode-version: '16.0.0' } - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 15, cmake-flags: '', xcode-version: '15.3' } - - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 14, cmake-flags: '', xcode-version: '14.3.1' } steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 diff --git a/src/dpp/voice/stub/stubs.cpp b/src/dpp/voice/stub/stubs.cpp index 97fd12cbc0..f85a90a9d7 100644 --- a/src/dpp/voice/stub/stubs.cpp +++ b/src/dpp/voice/stub/stubs.cpp @@ -90,7 +90,7 @@ namespace dpp { void discord_voice_client::setup() { } - void on_disconnect() { + void discord_voice_client::on_disconnect() { } } From feee5954151156dd019122f354dbac56648fa8b2 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 27 Nov 2024 14:16:33 +0000 Subject: [PATCH 073/112] docs: redo thread model dot --- docpages/advanced_reference/thread_model.md | 82 ++++++++------------- 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/docpages/advanced_reference/thread_model.md b/docpages/advanced_reference/thread_model.md index d34dc100a8..70a42cf047 100644 --- a/docpages/advanced_reference/thread_model.md +++ b/docpages/advanced_reference/thread_model.md @@ -4,8 +4,6 @@ digraph "Thread Model" { graph [ranksep=1]; node [colorscheme="blues9",fontname="helvetica"]; - "Discord Events" -> "Your Program" - "Your Program" [style=filled, color=1, shape=rect] "Cluster" [style=filled, color=1, shape=rect] @@ -14,68 +12,48 @@ digraph "Thread Model" { color=lightgrey; node [style=filled,color=2] "Your Program" - "Cluster" - label = "User Code"; + "Your Program" -> "Cluster" + label = "(1)"; } - subgraph cluster_0 { + subgraph cluster_3 { style=filled; color=lightgrey; - node [style=filled,color=4] - "Shard 1" [style=filled, color=4] - "Shard 2" - "Shard 3..." - label = "Shards (Each is a thread, one per 2500 Discord guilds)"; + node [style=filled,color=2] + "Cluster" -> "Event Loop" + "Event Loop" -> "HTTP(S) requests" + "Event Loop" -> "Voice Sessions" + "Event Loop" -> "Shards" + "Shards" -> "Websocket Events" + label = "(2)"; } - subgraph cluster_1 { - style=filled - color=lightgrey; - node [style=filled,color=4] - "REST Requests" - "Request In Queue 1" - "Request In Queue 2" - "Request In Queue 3..." - "Request Out Queue" - label = "REST Requests (Each in queue, and the out queue, are threads)" - } - subgraph cluster_3 { - style=filled + subgraph cluster_0 { + style=filled; color=lightgrey; node [style=filled,color=4] - "Discord Events" [style=filled,color=4] - "User Callback Functions" - label = "Events and Callbacks" + "Voice Sessions" -> "Websocket Events" + "HTTP(S) requests" -> "Thread Pool (4..n threads)" + "Websocket Events" -> "Thread Pool (4..n threads)" + "Thread Pool (4..n threads)" + label = "(3)"; } "Cluster" [shape=rect] - "REST Requests" [shape=rect] - "Request In Queue 1" [shape=rect] - "Request In Queue 2" [shape=rect] - "Request In Queue 3..." [shape=rect] - "Shard 1" [shape=rect] - "Shard 2" [shape=rect] - "Shard 3..." [shape=rect] - "Request Out Queue" [shape=rect] - "Discord Events" [shape=rect] - "User Callback Functions" [shape=rect] + "Thread Pool (4..n threads)" [shape=rect] + "HTTP(S) requests" [shape=rect] + "Shards" [shape=rect] + "Websocket Events" [shape=rect] + "Event Loop" [shape=rect] + "Voice Sessions" [shape=rect] - "Cluster" -> "REST Requests" - "Shard 1" -> "Discord Events" - "Shard 2" -> "Discord Events" - "Shard 3..." -> "Discord Events" - "Your Program" -> "Cluster" - "Cluster" -> "Shard 1" - "Cluster" -> "Shard 2" - "Cluster" -> "Shard 3..." - "REST Requests" -> "Request In Queue 1" - "REST Requests" -> "Request In Queue 2" - "REST Requests" -> "Request In Queue 3..." - "Request In Queue 1" -> "Request Out Queue" - "Request In Queue 2" -> "Request Out Queue" - "Request In Queue 3..." -> "Request Out Queue" - "Request Out Queue" -> "User Callback Functions" - "User Callback Functions" -> "Your Program" } \enddot + +## Details + +1. User Code - No assumptions are made about how your program threads, if at all. +2. The event loop manages all socket IO for the cluster. If you start the cluster with dpp::st_return this will run under its own thread, otherwise if you use dpp::st_wait it will run in the same thread as the caller of the dpp::cluster::start method. +The event loop will be either poll, epoll or kqueue based depending on your system capabilities. You should always start a cluster after forking, if your program forks, as various types of IO loop cannot be inherited by a forked process. +3. Set thread pool size via cluster constructor. Thread pool uses a priority queue and defaults in size to half the system concurrency value. Every callback or completed coroutine ends up executing here. The minimum concurrency of this pool is 4. From 4d9fbbf4219127511e94f6b509aa1d87ff8b3ae0 Mon Sep 17 00:00:00 2001 From: Neko Life Date: Sun, 1 Dec 2024 10:52:19 +0000 Subject: [PATCH 074/112] fix/sslclient-spinlock (#1339) --- src/dpp/sslclient.cpp | 5 ----- src/dpp/wsclient.cpp | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 454f9517c2..c40820df0a 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -373,11 +373,6 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { } void ssl_client::on_write(socket fd, const struct socket_events& e) { - /* We wanted to write before so keep it */ - socket_events new_se{e}; - new_se.flags |= WANT_WRITE; - owner->socketengine->update_socket(new_se); - if (!tcp_connect_done) { tcp_connect_done = true; } diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index 328b5d8d8e..1c2ef3763d 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -129,6 +129,23 @@ void websocket_client::write(const std::string_view data, ws_opcode _opcode) ssl_client::socket_write(header); ssl_client::socket_write(data); } + + bool should_append_want_write = false; + socket_events *new_se = nullptr; + { + std::lock_guard lk(owner->socketengine->fds_mutex); + auto i = owner->socketengine->fds.find(sfd); + + should_append_want_write = i != owner->socketengine->fds.end() && (i->second->flags & WANT_WRITE) != WANT_WRITE; + if (should_append_want_write) { + new_se = i->second.get(); + new_se->flags |= WANT_WRITE; + } + } + + if (should_append_want_write) { + owner->socketengine->update_socket(*new_se); + } } bool websocket_client::handle_buffer(std::string& buffer) From e1d0ab8c3d6c9e5a951efa07c25457c8b179e205 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sun, 1 Dec 2024 23:03:09 +0000 Subject: [PATCH 075/112] fix: reconnect of shards --- include/dpp/cluster.h | 2 +- src/davetest/dave.cpp | 2 +- src/dpp/cluster.cpp | 2 +- src/dpp/discordclient.cpp | 72 +++++++++++++++++++++++---------------- src/dpp/sslclient.cpp | 9 +++-- src/soaktest/soak.cpp | 15 ++++++++ 6 files changed, 67 insertions(+), 35 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 9b193f8098..8b40f8f53b 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -473,7 +473,7 @@ class DPP_EXPORT cluster { * * @param return_after If true the bot will return to your program after starting shards, if false this function will never return. */ - void start(bool return_after = true); + void start(start_type return_after = st_wait); /** * @brief Set the presence for all shards on the cluster diff --git a/src/davetest/dave.cpp b/src/davetest/dave.cpp index 94812e0d89..83ebc5e78f 100644 --- a/src/davetest/dave.cpp +++ b/src/davetest/dave.cpp @@ -84,5 +84,5 @@ int main() { s->connect_voice(TEST_GUILD_ID, TEST_VC_ID, muted, deaf, enable_dave); } }); - dave_test.start(false); + dave_test.start(dpp::st_wait); } diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 99926b3f37..21947d0679 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -198,7 +198,7 @@ dpp::utility::uptime cluster::uptime() return dpp::utility::uptime(time(nullptr) - start_time); } -void cluster::start(bool return_after) { +void cluster::start(start_type return_after) { auto event_loop = [this]() -> void { while (!this->terminating && socketengine.get()) { diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 50ba878ced..c698c8cb7f 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -128,10 +128,19 @@ void discord_client::on_disconnect() { set_resume_hostname(); log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting in 5 seconds..."); + ssl_client::close(); + end_zlib(); owner->start_timer([this](auto handle) { + log(dpp::ll_debug, "Reconnecting shard " + std::to_string(shard_id) + " to wss://" + hostname + "..."); owner->stop_timer(handle); cleanup(); terminating = false; + if (timer_handle) { + owner->stop_timer(timer_handle); + timer_handle = 0; + } + start = time(nullptr); + ssl_client::connect(); start_connecting(); run(); }, 5); @@ -305,34 +314,40 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) resumes++; } else { /* Full connect */ - while (time(nullptr) < creator->last_identify + 5) { - time_t wait = (creator->last_identify + 5) - time(nullptr); - std::this_thread::sleep_for(std::chrono::seconds(wait)); - } - log(dpp::ll_debug, "Connecting new session..."); - json obj = { - { "op", 2 }, - { - "d", + auto connect_now = [this]() { + log(dpp::ll_debug, "Connecting new session..."); + json obj = { + { "op", 2 }, { - { "token", this->token }, - { "properties", - { - { "os", STRINGIFY(DPP_OS) }, - { "browser", "D++" }, - { "device", "D++" } - } - }, - { "shard", json::array({ shard_id, max_shards }) }, - { "compress", false }, - { "large_threshold", 250 }, - { "intents", this->intents } + "d", + { + { "token", this->token }, + { "properties", + { + { "os", STRINGIFY(DPP_OS) }, + { "browser", "D++" }, + { "device", "D++" } + } + }, + { "shard", json::array({ shard_id, max_shards }) }, + { "compress", false }, + { "large_threshold", 250 }, + { "intents", this->intents } + } } - } + }; + this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); + this->connect_time = creator->last_identify = time(nullptr); + reconnects++; }; - this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); - this->connect_time = creator->last_identify = time(nullptr); - reconnects++; + if (time(nullptr) < creator->last_identify + 5) { + owner->start_timer([this, connect_now](timer h) { + owner->stop_timer(h); + connect_now(); + }, (creator->last_identify + 5) - time(nullptr)); + } else { + connect_now(); + } } this->last_heartbeat_ack = time(nullptr); websocket_ping = 0; @@ -345,7 +360,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) case 7: log(dpp::ll_debug, "Reconnection requested, closing socket " + sessionid); message_queue.clear(); - throw dpp::connection_exception(err_reconnection, "Remote site requested reconnection"); + this->close(); break; /* Heartbeat ack */ case 11: @@ -411,6 +426,7 @@ void discord_client::error(uint32_t errorcode) error = i->second; } log(dpp::ll_warning, "OOF! Error from underlying websocket: " + std::to_string(errorcode) + ": " + error); + this->close(); } void discord_client::log(dpp::loglevel severity, const std::string &msg) const @@ -454,10 +470,6 @@ size_t discord_client::get_queue_size() void discord_client::one_second_timer() { - if (terminating) { - throw dpp::exception("Shard terminating due to cluster shutdown"); - } - websocket_client::one_second_timer(); /* This all only triggers if we are connected (have completed websocket, and received READY or RESUMED) */ diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index c40820df0a..dbe85c1060 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -482,7 +482,7 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { void ssl_client::on_error(socket fd, const struct socket_events&, int error_code) { if (sfd != INVALID_SOCKET) { - ssl_client::close(); + this->close(); } } @@ -542,6 +542,10 @@ void ssl_client::close() SSL_free(ssl->ssl); ssl->ssl = nullptr; } + connected = tcp_connect_done = false; + client_to_server_length = client_to_server_offset = 0; + last_tick = time(nullptr); + bytes_in = bytes_out = 0; owner->socketengine->delete_socket(sfd); close_socket(sfd); sfd = INVALID_SOCKET; @@ -552,7 +556,6 @@ void ssl_client::close() void ssl_client::cleanup() { this->close(); - delete ssl; } ssl_client::~ssl_client() @@ -562,6 +565,8 @@ ssl_client::~ssl_client() owner->stop_timer(timer_handle); timer_handle = 0; } + delete ssl; + ssl = nullptr; } } diff --git a/src/soaktest/soak.cpp b/src/soaktest/soak.cpp index cbc462c194..679da011ae 100644 --- a/src/soaktest/soak.cpp +++ b/src/soaktest/soak.cpp @@ -23,18 +23,33 @@ #include #include #include +#ifndef _WIN32 + #include +#endif + +dpp::cluster* s{nullptr}; int main() { using namespace std::chrono_literals; char* t = getenv("DPP_UNIT_TEST_TOKEN"); if (t) { dpp::cluster soak_test(t, dpp::i_default_intents | dpp::i_guild_members, 1, 0, 1); + s = &soak_test; //soak_test.set_websocket_protocol(dpp::ws_etf); soak_test.on_log([&](const dpp::log_t& log) { std::cout << "[" << dpp::utility::current_date_time() << "] " << dpp::utility::loglevel(log.severity) << ": " << log.message << std::endl; }); soak_test.start(dpp::st_return); +#ifndef _WIN32 + signal(SIGINT, [](int sig) { + dpp::discord_client* dc = s->get_shard(0); + if (dc != nullptr) { + dc->close(); + } + }); +#endif + while (true) { std::this_thread::sleep_for(60s); dpp::discord_client* dc = soak_test.get_shard(0); From 315046ce7b916d5de1c80bd773498a561f7f5dca Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 2 Dec 2024 11:11:48 +0000 Subject: [PATCH 076/112] tidy up shard startup --- include/dpp/cluster_coro_calls.h | 8 ++++---- include/dpp/discordclient.h | 5 ----- include/dpp/exception.h | 1 + mlspp/include/namespace.h | 2 +- src/dpp/cluster.cpp | 35 ++++++++++++++++++++++++-------- src/dpp/discordclient.cpp | 6 +----- 6 files changed, 34 insertions(+), 23 deletions(-) diff --git a/include/dpp/cluster_coro_calls.h b/include/dpp/cluster_coro_calls.h index 9077e9efa6..ffeefbc1c0 100644 --- a/include/dpp/cluster_coro_calls.h +++ b/include/dpp/cluster_coro_calls.h @@ -450,8 +450,8 @@ * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. * @param c Channel to set permissions for * @param overwrite_id Overwrite to change (a user or role ID) - * @param allow allow permissions bitmask - * @param deny deny permissions bitmask + * @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) + * @param deny Bitmask of denied permissions (refer to enum dpp::permissions) * @param member true if the overwrite_id is a user id, false if it is a channel id * @return confirmation returned object on completion * \memberof dpp::cluster @@ -466,8 +466,8 @@ * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. * @param channel_id ID of the channel to set permissions for * @param overwrite_id Overwrite to change (a user or role ID) - * @param allow allow permissions bitmask - * @param deny deny permissions bitmask + * @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) + * @param deny Bitmask of denied permissions (refer to enum dpp::permissions) * @param member true if the overwrite_id is a user id, false if it is a channel id * @return confirmation returned object on completion * \memberof dpp::cluster diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index bb7c4a0974..74a609fa0f 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -165,11 +165,6 @@ class DPP_EXPORT discord_client : public websocket_client */ friend class dpp::cluster; - /** - * @brief True if the shard is terminating - */ - bool terminating; - /** * @brief Disconnect from the connected voice channel on a guild * diff --git a/include/dpp/exception.h b/include/dpp/exception.h index 3d5f41ff08..9d6dd2d69d 100644 --- a/include/dpp/exception.h +++ b/include/dpp/exception.h @@ -98,6 +98,7 @@ enum exception_error_code { err_no_voice_support = 29, err_invalid_voice_packet_length = 30, err_opus = 31, + err_cant_start_shard = 32, err_etf = 33, err_cache = 34, err_icon_size = 35, diff --git a/mlspp/include/namespace.h b/mlspp/include/namespace.h index 43a5121ccb..d07ba5ee94 100755 --- a/mlspp/include/namespace.h +++ b/mlspp/include/namespace.h @@ -1,4 +1,4 @@ #pragma once // Configurable top-level MLS namespace -#define MLS_NAMESPACE mls +#define MLS_NAMESPACE ../include/dpp/mlspp/mls diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 21947d0679..7d4be31810 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -223,25 +223,39 @@ void cluster::start(start_type return_after) { } /* Start up all shards */ - get_gateway_bot([this](const auto& response) { + get_gateway_bot([this, return_after](const auto& response) { + + auto throw_if_not_threaded = [this, return_after](exception_error_code error_id, const std::string& msg) { + log(ll_critical, msg); + if (return_after == st_wait) { + throw dpp::connection_exception(error_id, msg); + } + }; + if (response.is_error()) { - // TODO: Check for 401 unauthorized - // throw dpp::invalid_token_exception(err_unauthorized, "Invalid bot token (401: Unauthorized when getting gateway shard count)"); + if (response.http_info.status == 401) { + throw_if_not_threaded(err_unauthorized, "Invalid bot token (401: Unauthorized when getting gateway shard count)"); + } else { + throw_if_not_threaded(err_auto_shard, "get_gateway_bot: " + response.http_info.body); + } return; } auto g = std::get(response.value); log(ll_debug, "Cluster: " + std::to_string(g.session_start_remaining) + " of " + std::to_string(g.session_start_total) + " session starts remaining"); if (g.session_start_remaining < g.shards || g.shards == 0) { - throw dpp::connection_exception(err_no_sessions_left, "Discord indicates you cannot start enough sessions to boot this cluster! Cluster startup aborted. Try again later."); + throw_if_not_threaded(err_no_sessions_left, "Discord indicates you cannot start enough sessions to boot this cluster! Cluster startup aborted. Try again later."); + return; } else if (g. session_start_max_concurrency == 0) { - throw dpp::connection_exception(err_auto_shard, "Cluster: Could not determine concurrency, startup aborted!"); + throw_if_not_threaded(err_auto_shard, "Cluster: Could not determine concurrency, startup aborted!"); + return; } else if (g.session_start_max_concurrency > 1) { log(ll_debug, "Cluster: Large bot sharding; Using session concurrency: " + std::to_string(g.session_start_max_concurrency)); } else if (numshards == 0) { if (g.shards) { log(ll_info, "Auto Shard: Bot requires " + std::to_string(g.shards) + std::string(" shard") + ((g.shards > 1) ? "s" : "")); } else { - throw dpp::connection_exception(err_auto_shard, "Auto Shard: Cannot determine number of shards. Cluster startup aborted. Check your connection."); + throw_if_not_threaded(err_auto_shard, "Auto Shard: Cannot determine number of shards. Cluster startup aborted. Check your connection."); + return; } numshards = g.shards; } @@ -257,7 +271,8 @@ void cluster::start(start_type return_after) { this->shards[s]->run(); } catch (const std::exception &e) { - log(dpp::ll_critical, "Could not start shard " + std::to_string(s) + ": " + std::string(e.what())); + throw_if_not_threaded(err_cant_start_shard, "Could not start shard " + std::to_string(s) + ": " + std::string(e.what())); + return; } /* Stagger the shard startups, pausing every 'session_start_max_concurrency' shards for 5 seconds. * This means that for bots that don't have large bot sharding, any number % 1 is always 0, @@ -287,6 +302,10 @@ void cluster::start(start_type return_after) { /* Get all active DM channels and map them to user id -> dm id */ current_user_get_dms([this](const dpp::confirmation_callback_t& completion) { + if (completion.is_error()) { + log(dpp::ll_debug, "Failed to get bot DM list"); + return; + } dpp::channel_map dmchannels = std::get(completion.value); for (auto & c : dmchannels) { for (auto & u : c.second.recipients) { @@ -298,7 +317,7 @@ void cluster::start(start_type return_after) { log(ll_debug, "Shards started."); }); - if (return_after) { + if (return_after == st_return) { engine_thread = std::thread([event_loop]() { dpp::utility::set_thread_name("event_loop"); event_loop(); diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index c698c8cb7f..37d703d59e 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -65,7 +65,6 @@ thread_local static std::string last_ping_message; discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), - terminating(false), compressed(comp), decomp_buffer(nullptr), zlib(nullptr), @@ -98,8 +97,7 @@ void discord_client::start_connecting() { etf = new etf_parser(); } catch (std::bad_alloc&) { - delete zlib; - delete etf; + cleanup(); /* Clean up and rethrow to caller */ throw std::bad_alloc(); } @@ -114,7 +112,6 @@ void discord_client::start_connecting() { void discord_client::cleanup() { - terminating = true; delete etf; delete zlib; } @@ -134,7 +131,6 @@ void discord_client::on_disconnect() log(dpp::ll_debug, "Reconnecting shard " + std::to_string(shard_id) + " to wss://" + hostname + "..."); owner->stop_timer(handle); cleanup(); - terminating = false; if (timer_handle) { owner->stop_timer(timer_handle); timer_handle = 0; From f2e1edcff3f366eb9bdf75a7e49c27066b426bf5 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 2 Dec 2024 11:38:19 +0000 Subject: [PATCH 077/112] voice session reconnect --- include/dpp/discordclient.h | 3 +-- include/dpp/wsclient.h | 10 +--------- src/dpp/discordvoiceclient.cpp | 2 ++ src/dpp/voice/enabled/cleanup.cpp | 4 ++-- src/dpp/voice/enabled/thread.cpp | 8 +++++++- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 74a609fa0f..fd5d1812aa 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -35,10 +35,9 @@ #include #include - - #define DISCORD_API_VERSION "10" #define API_PATH "/api/v" DISCORD_API_VERSION + namespace dpp { // Forward declarations diff --git a/include/dpp/wsclient.h b/include/dpp/wsclient.h index c7fc99d4ee..da2a6c6fdf 100644 --- a/include/dpp/wsclient.h +++ b/include/dpp/wsclient.h @@ -135,15 +135,6 @@ class DPP_EXPORT websocket_client : public ssl_client { */ bool parseheader(std::string& buffer); - /** - * @brief Unpack a frame and pass completed frames up the stack. - * @param buffer The buffer to operate on. Gets modified to remove completed frames on the head of the buffer - * @param offset The offset to start at (reserved for future use) - * @param first True if is the first element (reserved for future use) - * @return true if a complete frame has been received - */ - bool unpack(std::string& buffer, uint32_t offset, bool first = true); - /** * @brief Fill a header for outbound messages * @param outbuf The raw frame to fill @@ -233,6 +224,7 @@ class DPP_EXPORT websocket_client : public ssl_client { /** * @brief Send OP_CLOSE error code 1000 to the other side of the connection. * This indicates graceful close. + * @note This informs Discord to invalidate the session, you cannot resume if you send this */ void send_close_packet(); diff --git a/src/dpp/discordvoiceclient.cpp b/src/dpp/discordvoiceclient.cpp index 5b241d1122..31801c62f7 100644 --- a/src/dpp/discordvoiceclient.cpp +++ b/src/dpp/discordvoiceclient.cpp @@ -231,6 +231,8 @@ void discord_voice_client::error(uint32_t errorcode) this->terminating = true; log(dpp::ll_error, "This is a non-recoverable error, giving up on voice connection"); } + + this->close(); } void discord_voice_client::set_user_gain(snowflake user_id, float factor) diff --git a/src/dpp/voice/enabled/cleanup.cpp b/src/dpp/voice/enabled/cleanup.cpp index 6d7b594b1d..45288afa8c 100644 --- a/src/dpp/voice/enabled/cleanup.cpp +++ b/src/dpp/voice/enabled/cleanup.cpp @@ -33,11 +33,11 @@ namespace dpp { void discord_voice_client::cleanup() { - if (encoder) { + if (encoder != nullptr) { opus_encoder_destroy(encoder); encoder = nullptr; } - if (repacketizer) { + if (repacketizer != nullptr) { opus_repacketizer_destroy(repacketizer); repacketizer = nullptr; } diff --git a/src/dpp/voice/enabled/thread.cpp b/src/dpp/voice/enabled/thread.cpp index 2d5372977a..135d1cc3fe 100644 --- a/src/dpp/voice/enabled/thread.cpp +++ b/src/dpp/voice/enabled/thread.cpp @@ -50,10 +50,16 @@ void discord_voice_client::on_disconnect() { } last_loop_time = current_time; - log(dpp::ll_debug, "Attempting to reconnect the websocket..."); + ssl_client::close(); owner->start_timer([this](auto handle) { + log(dpp::ll_debug, "Attempting to reconnect voice websocket " + std::to_string(channel_id) + " to wss://" + hostname + "..."); owner->stop_timer(handle); cleanup(); + if (timer_handle) { + owner->stop_timer(timer_handle); + timer_handle = 0; + } + start = time(nullptr); setup(); terminating = false; ssl_client::connect(); From 8c0c5d236f4ef241c079de42d42a7d834546fabb Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 3 Dec 2024 00:06:25 +0000 Subject: [PATCH 078/112] fix: prevent concurrent on_disconnect() --- include/dpp/discordclient.h | 5 +++++ library/CMakeLists.txt | 2 +- src/dpp/discordclient.cpp | 10 +++++++++- src/dpp/socketengines/epoll.cpp | 4 ++-- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index fd5d1812aa..1be08ae3b6 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -180,6 +180,11 @@ class DPP_EXPORT discord_client : public websocket_client */ void start_connecting(); + /** + * @brief Timer for reconnecting + */ + timer reconnect_timer{0}; + private: /** diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index c57394f739..4d1933ae0c 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -227,7 +227,7 @@ if(MSVC) string(REGEX REPLACE "/W[1|2|3|4]" "/W3" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wno-unused-private-field -Wno-psabi -Wempty-body -Wignored-qualifiers -Wimplicit-fallthrough -Wmissing-field-initializers -Wsign-compare -Wtype-limits -Wuninitialized -Wshift-negative-value -pthread") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") if (NOT MINGW) add_link_options("-rdynamic") diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 37d703d59e..55a6029619 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -114,6 +114,8 @@ void discord_client::cleanup() { delete etf; delete zlib; + etf = nullptr; + zlib = nullptr; } discord_client::~discord_client() @@ -127,9 +129,15 @@ void discord_client::on_disconnect() log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting in 5 seconds..."); ssl_client::close(); end_zlib(); - owner->start_timer([this](auto handle) { + /* Stop the timer first if its already ticking, to prevent concurrent reconnects */ + if (reconnect_timer) { + owner->stop_timer(reconnect_timer); + reconnect_timer = 0; + } + reconnect_timer = owner->start_timer([this](auto handle) { log(dpp::ll_debug, "Reconnecting shard " + std::to_string(shard_id) + " to wss://" + hostname + "..."); owner->stop_timer(handle); + reconnect_timer = 0; cleanup(); if (timer_handle) { owner->stop_timer(timer_handle); diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 7418f6c7fd..e36f825158 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -146,7 +146,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { ev.events |= EPOLLERR; } { - std::unique_lock lock(fds_mutex); + std::shared_lock lock(fds_mutex); ev.data.ptr = fds.find(e.fd)->second.get(); } return epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev) >= 0; @@ -169,7 +169,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { ev.events |= EPOLLERR; } { - std::unique_lock lock(fds_mutex); + std::shared_lock lock(fds_mutex); ev.data.ptr = fds.find(e.fd)->second.get(); } return epoll_ctl(epoll_handle, EPOLL_CTL_MOD, e.fd, &ev) >= 0; From 5f5489e4d5fb3bfce4bac34675fb490c624ec836 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 3 Dec 2024 02:54:43 +0000 Subject: [PATCH 079/112] refactor: magic numbers in discordclient --- .clang-tidy | 2 +- include/dpp/discordclient.h | 82 ++++++++++++++++++++++++++++++++- src/dpp/discordclient.cpp | 47 +++++++++---------- src/dpp/events/guild_create.cpp | 3 +- src/dpp/presence.cpp | 3 +- 5 files changed, 106 insertions(+), 31 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b1312fbca0..010c3e59f6 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,2 @@ # TODO: Discuss about -readability-identifier-length, -readability-avoid-const-params-in-decls -Checks: "-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,llvm-namespace-comment,modernize-*,performance-*,portability-*,readability-*,-bugprone-implicit-widening-of-multiplication-result, -bugprone-easily-swappable-parameters,-readability-identifier-length,-portability-restrict-system-includes,-modernize-use-trailing-return-type,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-avoid-const-params-in-decls" +Checks: "-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,llvm-namespace-comment,modernize-*,performance-*,portability-*,readability-*,-bugprone-implicit-widening-of-multiplication-result, -bugprone-easily-swappable-parameters,-readability-identifier-length,-portability-restrict-system-includes,-modernize-use-trailing-return-type,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-avoid-const-params-in-decls,-cppcoreguidelines-owning-memory,-readability-function-cognitive-complexity" diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 1be08ae3b6..c5427e5f68 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -50,6 +50,84 @@ class cluster; */ class zlibcontext; +/** + * @brief Represents different event opcodes sent and received on a shard websocket + */ +enum shard_frame_type : int { + + /** + * @brief An event was dispatched. + * @note Receive only + */ + ft_dispatch = 0, + + /** + * @brief Fired periodically by the client to keep the connection alive. + * @note Send/Receive + */ + ft_heartbeat = 1, + + /** + * @brief Starts a new session during the initial handshake. + * @note Send only + */ + ft_identify = 2, + + /** + * @brief Update the client's presence. + * @note Send only + */ + ft_presence = 3, + + /** + * @brief Used to join/leave or move between voice channels. + * @note Send only + */ + ft_voice_state_update = 4, + + /** + * @brief Resume a previous session that was disconnected. + * @note Send only + */ + ft_resume = 6, + + /** + * @brief You should attempt to reconnect and resume immediately. + * @note Receive only + */ + ft_reconnect = 7, + + /** + * @brief Request information about offline guild members in a large guild. + * @note Send only + */ + ft_request_guild_members = 8, + + /** + * @brief The session has been invalidated. You should reconnect and identify/resume accordingly. + * @note Receive only + */ + ft_invalid_session = 9, + + /** + * @brief Sent immediately after connecting, contains the heartbeat interval to use. + * @note Receive only + */ + ft_hello = 10, + + /** + * @brief Sent in response to receiving a heartbeat to acknowledge that it has been received. + * @note Receive only + */ + ft_heartbeat_ack = 11, + + /** + * @brief Request information about soundboard sounds in a set of guilds. + * @note Send only + */ + ft_request_soundboard_sounds = 31, +}; + /** * @brief Represents a connection to a voice channel. * A client can only connect to one voice channel per guild at a time, so these are stored in a map @@ -118,14 +196,14 @@ class DPP_EXPORT voiceconn { * * @return true if ready to connect */ - bool is_ready(); + bool is_ready() const; /** * @brief return true if the connection is active (websocket exists) * * @return true if has an active websocket */ - bool is_active(); + bool is_active() const; /** * @brief Create websocket object and connect it. diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 55a6029619..e61fa64f8b 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -34,17 +34,17 @@ #define PATH_COMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json&compress=zlib-stream" #define PATH_UNCOMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf" #define PATH_COMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf&compress=zlib-stream" -#define DECOMP_BUFFER_SIZE 512 * 1024 - #define STRINGIFY(a) STRINGIFY_(a) #define STRINGIFY_(a) #a #ifndef DPP_OS -#define DPP_OS unknown + #define DPP_OS unknown #endif namespace dpp { +constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; + /** * @brief This is an opaque class containing zlib library specific structures. * We define it this way so that the public facing D++ library doesn't require @@ -130,7 +130,7 @@ void discord_client::on_disconnect() ssl_client::close(); end_zlib(); /* Stop the timer first if its already ticking, to prevent concurrent reconnects */ - if (reconnect_timer) { + if (reconnect_timer != 0U) { owner->stop_timer(reconnect_timer); reconnect_timer = 0; } @@ -206,29 +206,25 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) zlib->d_stream.next_in = (Bytef *)buffer.c_str(); zlib->d_stream.avail_in = (uInt)buffer.size(); do { - int have = 0; zlib->d_stream.next_out = (Bytef*)decomp_buffer; zlib->d_stream.avail_out = DECOMP_BUFFER_SIZE; int ret = inflate(&(zlib->d_stream), Z_NO_FLUSH); - have = DECOMP_BUFFER_SIZE - zlib->d_stream.avail_out; + int have = DECOMP_BUFFER_SIZE - zlib->d_stream.avail_out; switch (ret) { case Z_NEED_DICT: case Z_STREAM_ERROR: this->error(err_compression_stream); this->close(); - return true; - break; + return true; case Z_DATA_ERROR: this->error(err_compression_data); this->close(); - return true; - break; + return true; case Z_MEM_ERROR: this->error(err_compression_memory); this->close(); - return true; - break; + return true; case Z_OK: this->decompressed.append((const char*)decomp_buffer, have); this->decompressed_total += have; @@ -288,7 +284,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) uint32_t op = o->get(); switch (op) { - case 9: + case ft_invalid_session: /* Reset session state and fall through to 10 */ op = 10; log(dpp::ll_debug, "Failed to resume session " + sessionid + ", will reidentify"); @@ -296,7 +292,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) this->last_seq = 0; /* No break here, falls through to state 10 to cause a reidentify */ [[fallthrough]]; - case 10: + case ft_hello: /* Need to check carefully for the existence of this before we try to access it! */ if (j.find("d") != j.end() && j["d"].find("heartbeat_interval") != j["d"].end() && !j["d"]["heartbeat_interval"].is_null()) { this->heartbeat_interval = j["d"]["heartbeat_interval"].get(); @@ -306,7 +302,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) /* Resume */ log(dpp::ll_debug, "Resuming session " + sessionid + " with seq=" + std::to_string(last_seq)); json obj = { - { "op", 6 }, + { "op", ft_resume }, { "d", { {"token", this->token }, {"session_id", this->sessionid }, @@ -321,9 +317,8 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) auto connect_now = [this]() { log(dpp::ll_debug, "Connecting new session..."); json obj = { - { "op", 2 }, - { - "d", + { "op", ft_identify }, + { "d", { { "token", this->token }, { "properties", @@ -356,18 +351,18 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) this->last_heartbeat_ack = time(nullptr); websocket_ping = 0; break; - case 0: { + case ft_dispatch: { std::string event = j["t"]; handle_event(event, j, data); } break; - case 7: + case ft_reconnect: log(dpp::ll_debug, "Reconnection requested, closing socket " + sessionid); message_queue.clear(); this->close(); break; /* Heartbeat ack */ - case 11: + case ft_heartbeat_ack: this->last_heartbeat_ack = time(nullptr); websocket_ping = utility::time_f() - ping_start; break; @@ -514,7 +509,7 @@ void discord_client::one_second_timer() if (this->heartbeat_interval && this->last_seq) { /* Check if we're due to emit a heartbeat */ if (time(nullptr) > last_heartbeat + ((heartbeat_interval / 1000.0) * 0.75)) { - last_ping_message = jsonobj_to_string(json({{"op", 1}, {"d", last_seq}})); + last_ping_message = jsonobj_to_string(json({{"op", ft_heartbeat}, {"d", last_seq}})); queue_message(last_ping_message, true); last_heartbeat = time(nullptr); } @@ -588,7 +583,7 @@ discord_client& discord_client::connect_voice(snowflake guild_id, snowflake chan */ log(ll_debug, "Sending op 4 to join VC, guild " + std::to_string(guild_id) + " channel " + std::to_string(channel_id) + (enable_dave ? " WITH DAVE" : "")); queue_message(jsonobj_to_string(json({ - { "op", 4 }, + { "op", ft_voice_state_update }, { "d", { { "guild_id", std::to_string(guild_id) }, { "channel_id", std::to_string(channel_id) }, @@ -617,7 +612,7 @@ void discord_client::disconnect_voice_internal(snowflake guild_id, bool emit_jso log(ll_debug, "Disconnecting voice, guild: " + std::to_string(guild_id)); if (emit_json) { queue_message(jsonobj_to_string(json({ - { "op", 4 }, + { "op", ft_voice_state_update }, { "d", { { "guild_id", std::to_string(guild_id) }, { "channel_id", json::value_t::null }, @@ -652,11 +647,11 @@ voiceconn* discord_client::get_voice(snowflake guild_id) { voiceconn::voiceconn(discord_client* o, snowflake _channel_id, bool enable_dave) : creator(o), channel_id(_channel_id), voiceclient(nullptr), dave(enable_dave) { } -bool voiceconn::is_ready() { +bool voiceconn::is_ready() const { return (!websocket_hostname.empty() && !session_id.empty() && !token.empty()); } -bool voiceconn::is_active() { +bool voiceconn::is_active() const { return voiceclient != nullptr; } diff --git a/src/dpp/events/guild_create.cpp b/src/dpp/events/guild_create.cpp index 354cce0a09..ea43521815 100644 --- a/src/dpp/events/guild_create.cpp +++ b/src/dpp/events/guild_create.cpp @@ -21,6 +21,7 @@ ************************************************************************************/ #include #include +#include #include #include #include @@ -136,7 +137,7 @@ void guild_create::handle(discord_client* client, json &j, const std::string &ra dpp::get_guild_cache()->store(g); if (is_new_guild && g->id && (client->intents & dpp::i_guild_members)) { if (client->creator->cache_policy.user_policy == cp_aggressive) { - json chunk_req = json({{"op", 8}, {"d", {{"guild_id",std::to_string(g->id)},{"query",""},{"limit",0}}}}); + json chunk_req = json({{"op", ft_request_guild_members}, {"d", {{"guild_id",std::to_string(g->id)},{"query",""},{"limit",0}}}}); if (client->intents & dpp::i_guild_presences) { chunk_req["d"]["presences"] = true; } diff --git a/src/dpp/presence.cpp b/src/dpp/presence.cpp index 7b40dc9a0d..2fff07f31e 100644 --- a/src/dpp/presence.cpp +++ b/src/dpp/presence.cpp @@ -21,6 +21,7 @@ ************************************************************************************/ #include #include +#include #include namespace dpp { @@ -250,7 +251,7 @@ json presence::to_json_impl(bool with_id) const { }; json j({ - {"op", 3}, + {"op", ft_presence}, {"d", { { "status", status_name_mapping[status()] }, From f584b2fbd1059134c9f9d15b6e88ee15276ad3fc Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 3 Dec 2024 10:54:35 +0000 Subject: [PATCH 080/112] catch exceptions on reconnect --- src/dpp/discordclient.cpp | 36 ++++++++++++++++++++++-------------- src/dpp/wsclient.cpp | 4 +++- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index e61fa64f8b..3948132ab9 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -125,28 +125,36 @@ discord_client::~discord_client() void discord_client::on_disconnect() { + if (reconnect_timer != 0U) { + log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnection already in progress..."); + return; + } set_resume_hostname(); log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting in 5 seconds..."); ssl_client::close(); end_zlib(); /* Stop the timer first if its already ticking, to prevent concurrent reconnects */ - if (reconnect_timer != 0U) { - owner->stop_timer(reconnect_timer); - reconnect_timer = 0; - } reconnect_timer = owner->start_timer([this](auto handle) { log(dpp::ll_debug, "Reconnecting shard " + std::to_string(shard_id) + " to wss://" + hostname + "..."); - owner->stop_timer(handle); - reconnect_timer = 0; - cleanup(); - if (timer_handle) { - owner->stop_timer(timer_handle); - timer_handle = 0; + try { + cleanup(); + if (timer_handle) { + owner->stop_timer(timer_handle); + timer_handle = 0; + } + start = time(nullptr); + ssl_client::connect(); + start_connecting(); + run(); + owner->stop_timer(handle); + reconnect_timer = 0; + } + catch (const std::exception &e) { + /* If we get here, the timer will tick again */ + ssl_client::close(); + end_zlib(); + log(dpp::ll_debug, "Error reconnecting shard " + std::to_string(shard_id) + ": " + std::string(e.what()) + "; Retry in 5 seconds..."); } - start = time(nullptr); - ssl_client::connect(); - start_connecting(); - run(); }, 5); } diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index 1c2ef3763d..ffad79ea3f 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -349,7 +349,9 @@ void websocket_client::on_disconnect() void websocket_client::close() { - this->on_disconnect(); + if (sfd != INVALID_SOCKET) { + this->on_disconnect(); + } this->state = HTTP_HEADERS; ssl_client::close(); } From 7ae853ed1b061ffd7fae0b6537c1d49704dd0e57 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 3 Dec 2024 13:43:01 +0000 Subject: [PATCH 081/112] fix: locking primitives --- include/dpp/discordclient.h | 11 +++++++-- include/dpp/sslclient.h | 3 +++ src/dpp/discordclient.cpp | 45 +++++++++++++------------------------ src/dpp/sslclient.cpp | 2 ++ src/soaktest/soak.cpp | 19 ++++++++++------ 5 files changed, 41 insertions(+), 39 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index c5427e5f68..116b80f354 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -31,7 +31,9 @@ #include #include #include +#include #include +#include #include #include @@ -270,6 +272,11 @@ class DPP_EXPORT discord_client : public websocket_client */ std::shared_mutex queue_mutex; + /** + * @brief Mutex for zlib pointer + */ + std::mutex zlib_mutex; + /** * @brief Queue of outbound messages */ @@ -283,7 +290,7 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief ZLib decompression buffer */ - unsigned char* decomp_buffer; + std::vector decomp_buffer; /** * @brief Decompressed string @@ -316,7 +323,7 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief ETF parser for when in ws_etf mode */ - class etf_parser* etf; + std::unique_ptr etf; /** * @brief Convert a JSON object to string. diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 552c677361..3d3c5a8566 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,8 @@ class DPP_EXPORT ssl_client */ void cleanup(); + std::mutex ssl_mutex; + /** * @brief Start offset into internal ring buffer for client to server IO */ diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 3948132ab9..0d9bb6ac3b 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -66,7 +66,6 @@ thread_local static std::string last_ping_message; discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), compressed(comp), - decomp_buffer(nullptr), zlib(nullptr), decompressed_total(0), connect_time(0), @@ -86,41 +85,23 @@ discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint3 ready(false), last_heartbeat_ack(time(nullptr)), protocol(ws_proto), - resume_gateway_url(_cluster->default_gateway) + resume_gateway_url(_cluster->default_gateway) { + etf = std::make_unique(etf_parser()); start_connecting(); } void discord_client::start_connecting() { - try { - zlib = new zlibcontext(); - etf = new etf_parser(); - } - catch (std::bad_alloc&) { - cleanup(); - /* Clean up and rethrow to caller */ - throw std::bad_alloc(); - } - try { - this->connect(); - } - catch (std::exception&) { - cleanup(); - throw; - } + this->connect(); } void discord_client::cleanup() { - delete etf; - delete zlib; - etf = nullptr; - zlib = nullptr; } discord_client::~discord_client() { - cleanup(); + end_zlib(); } void discord_client::on_disconnect() @@ -137,7 +118,6 @@ void discord_client::on_disconnect() reconnect_timer = owner->start_timer([this](auto handle) { log(dpp::ll_debug, "Reconnecting shard " + std::to_string(shard_id) + " to wss://" + hostname + "..."); try { - cleanup(); if (timer_handle) { owner->stop_timer(timer_handle); timer_handle = 0; @@ -165,7 +145,11 @@ uint64_t discord_client::get_decompressed_bytes_in() void discord_client::setup_zlib() { + std::lock_guard lock(zlib_mutex); if (compressed) { + if (zlib == nullptr) { + zlib = new zlibcontext(); + } zlib->d_stream.zalloc = (alloc_func)0; zlib->d_stream.zfree = (free_func)0; zlib->d_stream.opaque = (voidpf)0; @@ -173,18 +157,19 @@ void discord_client::setup_zlib() if (error != Z_OK) { throw dpp::connection_exception((exception_error_code)error, "Can't initialise stream compression!"); } - this->decomp_buffer = new unsigned char[DECOMP_BUFFER_SIZE]; + decomp_buffer.resize(DECOMP_BUFFER_SIZE); } } void discord_client::end_zlib() { - if (compressed) { + std::lock_guard lock(zlib_mutex); + if (compressed && zlib) { inflateEnd(&(zlib->d_stream)); - delete[] this->decomp_buffer; - this->decomp_buffer = nullptr; } + delete zlib; + zlib = nullptr; } void discord_client::set_resume_hostname() @@ -214,7 +199,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) zlib->d_stream.next_in = (Bytef *)buffer.c_str(); zlib->d_stream.avail_in = (uInt)buffer.size(); do { - zlib->d_stream.next_out = (Bytef*)decomp_buffer; + zlib->d_stream.next_out = (Bytef*)decomp_buffer.data(); zlib->d_stream.avail_out = DECOMP_BUFFER_SIZE; int ret = inflate(&(zlib->d_stream), Z_NO_FLUSH); int have = DECOMP_BUFFER_SIZE - zlib->d_stream.avail_out; @@ -234,7 +219,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) this->close(); return true; case Z_OK: - this->decompressed.append((const char*)decomp_buffer, have); + this->decompressed.append((const char*)decomp_buffer.data(), have); this->decompressed_total += have; break; default: diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index dbe85c1060..cfef6e949b 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -402,6 +402,7 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { } if (!ssl->ssl) { /* Create SSL session */ + std::lock_guard lock(ssl_mutex); ssl->ssl = SSL_new(openssl_context.get()); if (ssl->ssl == nullptr) { throw dpp::connection_exception(err_ssl_new, "SSL_new failed!"); @@ -539,6 +540,7 @@ bool ssl_client::handle_buffer(std::string &buffer) void ssl_client::close() { if (!plaintext && ssl->ssl) { + std::lock_guard lock(ssl_mutex); SSL_free(ssl->ssl); ssl->ssl = nullptr; } diff --git a/src/soaktest/soak.cpp b/src/soaktest/soak.cpp index 679da011ae..fecebdc853 100644 --- a/src/soaktest/soak.cpp +++ b/src/soaktest/soak.cpp @@ -23,11 +23,13 @@ #include #include #include +#include #ifndef _WIN32 #include #endif dpp::cluster* s{nullptr}; +std::atomic_bool signalled{false}; int main() { using namespace std::chrono_literals; @@ -42,19 +44,22 @@ int main() { soak_test.start(dpp::st_return); #ifndef _WIN32 - signal(SIGINT, [](int sig) { - dpp::discord_client* dc = s->get_shard(0); - if (dc != nullptr) { - dc->close(); - } + signal(SIGUSR1, [](int sig) { + signalled = true; }); #endif while (true) { - std::this_thread::sleep_for(60s); + std::this_thread::sleep_for(1s); dpp::discord_client* dc = soak_test.get_shard(0); if (dc != nullptr) { - std::cout << "Websocket latency: " << std::fixed << dc->websocket_ping << " Guilds: " << dpp::get_guild_count() << " Users: " << dpp::get_user_count() << "\n"; + if (time(nullptr) % 60 == 0) { + std::cout << "Websocket latency: " << std::fixed << dc->websocket_ping << " Guilds: " << dpp::get_guild_count() << " Users: " << dpp::get_user_count() << "\n"; + } + if (signalled) { + signalled = false; + dc->close(); + } } } } From 8b83af2263de39078e20078251324d1c880fdab7 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 3 Dec 2024 15:42:10 +0000 Subject: [PATCH 082/112] refactor: eliminate raw pointers from timer code --- include/dpp/cluster.h | 2 +- include/dpp/timer.h | 8 +++--- src/dpp/cluster.cpp | 4 --- src/dpp/cluster/timer.cpp | 56 +++++++++++++++++++++------------------ 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 8b40f8f53b..6471e42a17 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -172,7 +172,7 @@ class DPP_EXPORT cluster { * * @param t Timer to reschedule */ - void timer_reschedule(timer_t* t); + void timer_reschedule(timer_t t); /** * @brief Thread pool diff --git a/include/dpp/timer.h b/include/dpp/timer.h index c3dfbfa97e..c98149d21e 100644 --- a/include/dpp/timer.h +++ b/include/dpp/timer.h @@ -22,10 +22,10 @@ #pragma once #include -#include +#include #include #include -#include +#include #include #include @@ -77,12 +77,12 @@ struct DPP_EXPORT timer_t { * @brief A map of timers, ordered by earliest first so that map::begin() is always the * soonest to be due. */ -typedef std::multimap timer_next_t; +typedef std::multimap timer_next_t; /** * @brief A map of timers stored by handle */ -typedef std::unordered_map timer_reg_t; +typedef std::unordered_map timer_reg_t; /** * @brief Trigger a timed event once. diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 7d4be31810..9dc807cafb 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -338,10 +338,6 @@ void cluster::shutdown() { { std::lock_guard l(timer_guard); - /* Free memory for active timers */ - for (auto &t: timer_list) { - delete t.second; - } timer_list.clear(); next_timer.clear(); } diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index 961935c3a7..0cf558a5f6 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -28,17 +28,17 @@ timer lasthandle = 1; timer cluster::start_timer(timer_callback_t on_tick, uint64_t frequency, timer_callback_t on_stop) { std::lock_guard l(timer_guard); - timer_t* newtimer = new timer_t(); + timer_t newtimer; - newtimer->handle = lasthandle++; - newtimer->next_tick = time(nullptr) + frequency; - newtimer->on_tick = on_tick; - newtimer->on_stop = on_stop; - newtimer->frequency = frequency; - timer_list[newtimer->handle] = newtimer; - next_timer.emplace(newtimer->next_tick, newtimer); + newtimer.handle = lasthandle++; + newtimer.next_tick = time(nullptr) + frequency; + newtimer.on_tick = on_tick; + newtimer.on_stop = on_stop; + newtimer.frequency = frequency; + timer_list[newtimer.handle] = newtimer; + next_timer.emplace(newtimer.next_tick, newtimer); - return newtimer->handle; + return newtimer.handle; } bool cluster::stop_timer(timer t) { @@ -46,47 +46,51 @@ bool cluster::stop_timer(timer t) { auto i = timer_list.find(t); if (i != timer_list.end()) { - timer_t* tptr = i->second; - if (tptr->on_stop) { + timer_t timer_current = i->second; + if (timer_current.on_stop) { /* If there is an on_stop event, call it */ - tptr->on_stop(t); + timer_current.on_stop(t); } timer_list.erase(i); - for (auto this_timer = next_timer.begin(); this_timer != next_timer.end(); ++this_timer) { - if (this_timer->second == tptr) { - next_timer.erase(this_timer); - break; + bool again; + do { + again = false; + for (auto this_timer = next_timer.begin(); this_timer != next_timer.end(); ++this_timer) { + if (this_timer->second.handle == t) { + next_timer.erase(this_timer); + again = true; + break; + } } - } - delete tptr; + } while(again); return true; } return false; } -void cluster::timer_reschedule(timer_t* t) { +void cluster::timer_reschedule(timer_t t) { std::lock_guard l(timer_guard); for (auto i = next_timer.begin(); i != next_timer.end(); ++i) { /* Rescheduling the timer means finding it in the next tick map. * It should be pretty much near the start of the map so this loop * should only be at most a handful of iterations. */ - if (i->second->handle == t->handle) { + if (i->second.handle == t.handle) { next_timer.erase(i); - t->next_tick = time(nullptr) + t->frequency; - next_timer.emplace(t->next_tick, t); + t.next_tick = time(nullptr) + t.frequency; + next_timer.emplace(t.next_tick, t); break; } } } void cluster::tick_timers() { - std::vector scheduled; + std::vector scheduled; { time_t now = time(nullptr); std::lock_guard l(timer_guard); for (auto & i : next_timer) { - if (now >= i.second->next_tick) { + if (now >= i.second.next_tick) { scheduled.push_back(i.second); } else { /* The first time we encounter an entry which is not due, @@ -98,9 +102,9 @@ void cluster::tick_timers() { } } for (auto & t : scheduled) { - timer handle = t->handle; + timer handle = t.handle; /* Call handler */ - t->on_tick(t->handle); + t.on_tick(handle); /* Reschedule if it wasn't deleted. * Note: We wrap the .contains() check in a lambda as it needs locking * for thread safety, but timer_rescheudle also locks the container, so this From e6f0d504f24e4f96b41f0395bda6df749be5f44c Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 4 Dec 2024 10:08:11 +0000 Subject: [PATCH 083/112] feat: improved timer system, fix for runaway cpu usage due to connect() retry, fix fd and memory leak in httpsclient --- include/dpp/cluster.h | 8 +-- include/dpp/sslclient.h | 17 +++++- include/dpp/thread_pool.h | 2 +- include/dpp/timer.h | 27 ++++++--- src/dpp/cluster.cpp | 3 +- src/dpp/cluster/timer.cpp | 118 ++++++++++++++++---------------------- src/dpp/discordclient.cpp | 2 + src/dpp/httpsclient.cpp | 4 +- src/dpp/queues.cpp | 5 ++ src/dpp/socketengine.cpp | 7 ++- src/dpp/sslclient.cpp | 2 +- 11 files changed, 105 insertions(+), 90 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 6471e42a17..ef16157c22 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -126,12 +126,12 @@ class DPP_EXPORT cluster { shard_list shards; /** - * @brief List of all active registered timers + * @brief Ephemeral list of deleted timer ids */ - timer_reg_t timer_list; + timers_deleted_t deleted_timers; /** - * @brief List of timers by time + * @brief Priority queue of of timers by time */ timer_next_t next_timer; @@ -172,7 +172,7 @@ class DPP_EXPORT cluster { * * @param t Timer to reschedule */ - void timer_reschedule(timer_t t); + void timer_reschedule(timer_t& t); /** * @brief Thread pool diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 3d3c5a8566..3e7a834dea 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -66,15 +66,25 @@ DPP_EXPORT bool close_socket(dpp::socket sfd); */ DPP_EXPORT bool set_nonblocking(dpp::socket sockfd, bool non_blocking); -/* You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each. +/** + * @brief SSL_read buffer size + * + * You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each. * SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as * it'd go unused. */ constexpr uint16_t DPP_BUFSIZE{16 * 1024}; -/* Represents a failed socket system call, e.g. connect() failure */ +/** + * @brief Represents a failed socket system call, e.g. connect() failure + */ constexpr int ERROR_STATUS{-1}; +/** + * @brief Maximum number of internal connect() retries on TCP connections + */ +constexpr int MAX_RETRIES{4}; + /** * @brief Implements a simple non-blocking SSL stream client. @@ -91,6 +101,9 @@ class DPP_EXPORT ssl_client */ void cleanup(); + /** + * @brief Mutex for creation of internal SSL pointers by openssl + */ std::mutex ssl_mutex; /** diff --git a/include/dpp/thread_pool.h b/include/dpp/thread_pool.h index 1f40c125ec..54a498c868 100644 --- a/include/dpp/thread_pool.h +++ b/include/dpp/thread_pool.h @@ -42,7 +42,7 @@ struct DPP_EXPORT thread_pool_task { struct DPP_EXPORT thread_pool_task_comparator { bool operator()(const thread_pool_task &a, const thread_pool_task &b) const { - return a.priority < b.priority; + return a.priority > b.priority; }; }; diff --git a/include/dpp/timer.h b/include/dpp/timer.h index c98149d21e..8896288ffe 100644 --- a/include/dpp/timer.h +++ b/include/dpp/timer.h @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include namespace dpp { @@ -50,39 +52,46 @@ struct DPP_EXPORT timer_t { /** * @brief Timer handle */ - timer handle; + timer handle{0}; /** * @brief Next timer tick as unix epoch */ - time_t next_tick; + time_t next_tick{0}; /** * @brief Frequency between ticks */ - uint64_t frequency; + uint64_t frequency{0}; /** * @brief Lambda to call on tick */ - timer_callback_t on_tick; + timer_callback_t on_tick{}; /** * @brief Lambda to call on stop (optional) */ - timer_callback_t on_stop; + timer_callback_t on_stop{}; }; +struct DPP_EXPORT timer_comparator { + bool operator()(const timer_t &a, const timer_t &b) const { + return a.next_tick > b.next_tick; + }; +}; + + /** - * @brief A map of timers, ordered by earliest first so that map::begin() is always the + * @brief A priority timers, ordered by earliest first so that the head is always the * soonest to be due. */ -typedef std::multimap timer_next_t; +typedef std::priority_queue, timer_comparator> timer_next_t; /** - * @brief A map of timers stored by handle + * @brief A set of deleted timer handles */ -typedef std::unordered_map timer_reg_t; +typedef std::set timers_deleted_t; /** * @brief Trigger a timed event once. diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 9dc807cafb..5c5b5a20d4 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -338,8 +338,7 @@ void cluster::shutdown() { { std::lock_guard l(timer_guard); - timer_list.clear(); - next_timer.clear(); + next_timer = {}; } /* Terminate shards */ diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index 0cf558a5f6..b13e404b76 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -35,89 +35,71 @@ timer cluster::start_timer(timer_callback_t on_tick, uint64_t frequency, timer_c newtimer.on_tick = on_tick; newtimer.on_stop = on_stop; newtimer.frequency = frequency; - timer_list[newtimer.handle] = newtimer; - next_timer.emplace(newtimer.next_tick, newtimer); + + next_timer.emplace(newtimer); return newtimer.handle; } bool cluster::stop_timer(timer t) { + /* + * Because iterating a priority queue is O(log n) we don't actually walk the queue + * looking for the timer to remove. Instead, we just insert the timer handle into a std::set + * to inform the tick_timers() function later if it sees a handle in this set, it is to + * have its on_stop() called and it is not to be rescheduled. + */ std::lock_guard l(timer_guard); - - auto i = timer_list.find(t); - if (i != timer_list.end()) { - timer_t timer_current = i->second; - if (timer_current.on_stop) { - /* If there is an on_stop event, call it */ - timer_current.on_stop(t); - } - timer_list.erase(i); - bool again; - do { - again = false; - for (auto this_timer = next_timer.begin(); this_timer != next_timer.end(); ++this_timer) { - if (this_timer->second.handle == t) { - next_timer.erase(this_timer); - again = true; - break; - } - } - } while(again); - return true; - } - return false; + deleted_timers.emplace(t); + return true; } -void cluster::timer_reschedule(timer_t t) { - std::lock_guard l(timer_guard); - for (auto i = next_timer.begin(); i != next_timer.end(); ++i) { - /* Rescheduling the timer means finding it in the next tick map. - * It should be pretty much near the start of the map so this loop - * should only be at most a handful of iterations. - */ - if (i->second.handle == t.handle) { - next_timer.erase(i); - t.next_tick = time(nullptr) + t.frequency; - next_timer.emplace(t.next_tick, t); - break; - } - } +void cluster::timer_reschedule(timer_t& t) { + } void cluster::tick_timers() { - std::vector scheduled; - { - time_t now = time(nullptr); - std::lock_guard l(timer_guard); - for (auto & i : next_timer) { - if (now >= i.second.next_tick) { - scheduled.push_back(i.second); - } else { - /* The first time we encounter an entry which is not due, - * we can bail out, because std::map is ordered storage, so - * we know at this point no more will match either. - */ + time_t now = time(nullptr); + time_t time_frame{}; + if (next_timer.empty()) { + return; + } + do { + timer_t cur_timer; + { + std::lock_guard l(timer_guard); + cur_timer = next_timer.top(); + if (cur_timer.next_tick > now) { + /* Nothing to do */ break; } + next_timer.pop(); } - } - for (auto & t : scheduled) { - timer handle = t.handle; - /* Call handler */ - t.on_tick(handle); - /* Reschedule if it wasn't deleted. - * Note: We wrap the .contains() check in a lambda as it needs locking - * for thread safety, but timer_rescheudle also locks the container, so this - * is the cleanest way to do it. - */ - bool not_deleted = ([handle, this]() -> bool { - std::lock_guard l(timer_guard); - return timer_list.find(handle) != timer_list.end(); - }()); - if (not_deleted) { - timer_reschedule(t); + timers_deleted_t::iterator deleted_iter{}; + bool deleted{}; + { + deleted_iter = deleted_timers.find(cur_timer.handle); + deleted = deleted_iter != deleted_timers.end(); } - } + + if (!deleted) { + cur_timer.on_tick(cur_timer.handle); + cur_timer.next_tick += cur_timer.frequency; + { + std::lock_guard l(timer_guard); + next_timer.emplace(cur_timer); + } + } else { + /* Deleted timers are not reinserted into the priority queue and their on_stop is called */ + if (cur_timer.on_stop) { + cur_timer.on_stop(cur_timer.handle); + } + { + std::lock_guard l(timer_guard); + deleted_timers.erase(deleted_iter); + } + } + + } while (true); } #ifdef DPP_CORO diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 0d9bb6ac3b..bae60f0a62 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -113,6 +113,8 @@ void discord_client::on_disconnect() set_resume_hostname(); log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting in 5 seconds..."); ssl_client::close(); + /* Prevent low level connect retries here, as we are handling it ourselves */ + connect_retries = MAX_RETRIES + 1; end_zlib(); /* Stop the timer first if its already ticking, to prevent concurrent reconnects */ reconnect_timer = owner->start_timer([this](auto handle) { diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 10c545b4ba..a16cbdf1a6 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -342,9 +342,9 @@ void https_client::close() { completed(this); completed = {}; } - state = HTTPS_DONE; - ssl_client::close(); } + state = HTTPS_DONE; + ssl_client::close(); } https_client::~https_client() { diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 0b76a4833b..51964e5787 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -258,6 +258,11 @@ http_request_completion_t http_request::run(request_concurrency_queue* processor /* Transfer it to completed requests */ owner->queue_work(0, [this, result]() { + /* Manually release this unique_ptr now, to keep memory consumption and file descriptor consumption low. + * Note we do this BEFORE we call complete(), becasue if user code throws an exception we need to be sure + * we freed this first to avoid dangling pointer leaks. + */ + this->cli.reset(); complete(result); }); } diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index 38f738aeaf..3a39a31eb3 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -32,7 +32,12 @@ namespace dpp { bool socket_engine_base::register_socket(const socket_events &e) { std::unique_lock lock(fds_mutex); - if (e.fd != INVALID_SOCKET && fds.find(e.fd) == fds.end()) { + auto i = fds.find(e.fd); + if (e.fd != INVALID_SOCKET && i == fds.end()) { + fds.emplace(e.fd, std::make_unique(e)); + return true; + } else if (e.fd != INVALID_SOCKET && i != fds.end()) { + this->remove_socket(e.fd); fds.emplace(e.fd, std::make_unique(e)); return true; } diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index cfef6e949b..731866cbc9 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -503,7 +503,7 @@ void ssl_client::read_loop() if (!timer_handle) { timer_handle = owner->start_timer([this, setup_events](auto handle) { one_second_timer(); - if (!tcp_connect_done && time(nullptr) > start + 2 && connect_retries < 3) { + if (!tcp_connect_done && time(nullptr) > start + 2 && connect_retries < MAX_RETRIES) { /* Retry failed connect(). This can happen even in the best situation with bullet-proof hosting. * Previously with blocking connect() there was some leniency in this, but now we have to do this * ourselves. From 6a76049a835bc4030e44dccd213abc7ca2fc9632 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 4 Dec 2024 11:12:35 +0000 Subject: [PATCH 084/112] fix: segfault when calling read on an invalid fd --- include/dpp/cluster.h | 8 -------- include/dpp/discordclient.h | 31 +++++++++++++++++++++++++++++-- src/dpp/cluster/timer.cpp | 29 +++++++++++++---------------- src/dpp/discordclient.cpp | 22 +++++++--------------- src/dpp/sslclient.cpp | 11 ++++++++++- 5 files changed, 59 insertions(+), 42 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index ef16157c22..46747db0e7 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -166,14 +166,6 @@ class DPP_EXPORT cluster { */ std::map named_commands; #endif - - /** - * @brief Reschedule a timer for its next tick - * - * @param t Timer to reschedule - */ - void timer_reschedule(timer_t& t); - /** * @brief Thread pool */ diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 116b80f354..e0c9b43915 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -37,12 +37,19 @@ #include #include +/** + * @brief Discord API version for shard websockets and HTTPS API requests + */ #define DISCORD_API_VERSION "10" + +/** + * @brief HTTPS Request base path for API calls + */ #define API_PATH "/api/v" DISCORD_API_VERSION namespace dpp { -// Forward declarations +/* Forward declarations */ class cluster; /** @@ -52,8 +59,15 @@ class cluster; */ class zlibcontext; +/** + * @brief Size of decompression buffer for zlib compressed traffic + */ +constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; + /** * @brief Represents different event opcodes sent and received on a shard websocket + * + * These are used internally to route frames. */ enum shard_frame_type : int { @@ -261,10 +275,19 @@ class DPP_EXPORT discord_client : public websocket_client void start_connecting(); /** - * @brief Timer for reconnecting + * @brief Timer for use when reconnecting. + * + * The client will wait 5 seconds before retrying a connection, to comply + * with Discord rate limiting for websocket connections. */ timer reconnect_timer{0}; + /** + * @brief Stores the most recent ping message on this shard, which we check + * for to monitor latency + */ + std::string last_ping_message; + private: /** @@ -289,6 +312,10 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief ZLib decompression buffer + * + * If compression is not in use, this remains set to + * a vector of length zero, but when compression is + * enabled it will be resized to a DECOMP_BUFFER_SIZE buffer. */ std::vector decomp_buffer; diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index b13e404b76..2fa610c002 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -21,29 +21,30 @@ #include #include #include +#include namespace dpp { -timer lasthandle = 1; +std::atomic next_handle = 1; timer cluster::start_timer(timer_callback_t on_tick, uint64_t frequency, timer_callback_t on_stop) { - std::lock_guard l(timer_guard); - timer_t newtimer; + timer_t new_timer; - newtimer.handle = lasthandle++; - newtimer.next_tick = time(nullptr) + frequency; - newtimer.on_tick = on_tick; - newtimer.on_stop = on_stop; - newtimer.frequency = frequency; + new_timer.handle = next_handle++; + new_timer.next_tick = time(nullptr) + frequency; + new_timer.on_tick = on_tick; + new_timer.on_stop = on_stop; + new_timer.frequency = frequency; - next_timer.emplace(newtimer); + std::lock_guard l(timer_guard); + next_timer.emplace(new_timer); - return newtimer.handle; + return new_timer.handle; } bool cluster::stop_timer(timer t) { /* - * Because iterating a priority queue is O(log n) we don't actually walk the queue + * Because iterating a priority queue is at best O(log n) we don't actually walk the queue * looking for the timer to remove. Instead, we just insert the timer handle into a std::set * to inform the tick_timers() function later if it sees a handle in this set, it is to * have its on_stop() called and it is not to be rescheduled. @@ -53,13 +54,9 @@ bool cluster::stop_timer(timer t) { return true; } -void cluster::timer_reschedule(timer_t& t) { - -} - void cluster::tick_timers() { time_t now = time(nullptr); - time_t time_frame{}; + if (next_timer.empty()) { return; } diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index bae60f0a62..695c23bfb8 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -43,8 +43,6 @@ namespace dpp { -constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; - /** * @brief This is an opaque class containing zlib library specific structures. * We define it this way so that the public facing D++ library doesn't require @@ -58,11 +56,6 @@ class zlibcontext { z_stream d_stream; }; -/** - * @brief Stores the most recent ping message on this shard, which we check for to monitor latency - */ -thread_local static std::string last_ping_message; - discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), compressed(comp), @@ -189,7 +182,7 @@ void discord_client::run() bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) { - std::string& data = (std::string&)buffer; + auto& data = (std::string&)buffer; /* gzip compression is a special case */ if (compressed) { @@ -198,13 +191,13 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) && (uint8_t)buffer[buffer.size() - 1] == 0xFF) { /* Decompress buffer */ decompressed.clear(); - zlib->d_stream.next_in = (Bytef *)buffer.c_str(); - zlib->d_stream.avail_in = (uInt)buffer.size(); + zlib->d_stream.next_in = (Bytef*)buffer.data(); + zlib->d_stream.avail_in = static_cast(buffer.size()); do { - zlib->d_stream.next_out = (Bytef*)decomp_buffer.data(); + zlib->d_stream.next_out = reinterpret_cast(decomp_buffer.data()); zlib->d_stream.avail_out = DECOMP_BUFFER_SIZE; int ret = inflate(&(zlib->d_stream), Z_NO_FLUSH); - int have = DECOMP_BUFFER_SIZE - zlib->d_stream.avail_out; + size_t have = DECOMP_BUFFER_SIZE - zlib->d_stream.avail_out; switch (ret) { case Z_NEED_DICT: @@ -486,9 +479,8 @@ void discord_client::one_second_timer() if (message_queue.size()) { std::string message = message_queue.front(); message_queue.pop_front(); - /* Checking here with .find() saves us having to deserialise the json - * to find pings in our queue. The assumption is that the format of the - * ping isn't going to change. + /* Checking here by string comparison saves us having to deserialise the json + * to find pings in our queue. */ if (!last_ping_message.empty() && message == last_ping_message) { ping_start = utility::time_f(); diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 731866cbc9..0fb6af11b5 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -296,6 +296,10 @@ void ssl_client::complete_handshake(const socket_events* ev) void ssl_client::on_read(socket fd, const struct socket_events& ev) { + if (sfd == INVALID_SOCKET) { + return; + } + if (plaintext && connected) { int r = (int) ::recv(sfd, server_to_client_buffer, DPP_BUFSIZE, 0); if (r <= 0) { @@ -373,6 +377,11 @@ void ssl_client::on_read(socket fd, const struct socket_events& ev) { } void ssl_client::on_write(socket fd, const struct socket_events& e) { + + if (sfd == INVALID_SOCKET) { + return; + } + if (!tcp_connect_done) { tcp_connect_done = true; } @@ -400,7 +409,7 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { throw dpp::connection_exception(err_ssl_version, "Failed to set minimum SSL version!"); } } - if (!ssl->ssl) { + if (ssl != nullptr && ssl->ssl == nullptr) { /* Create SSL session */ std::lock_guard lock(ssl_mutex); ssl->ssl = SSL_new(openssl_context.get()); From 0d5e9325a06a14ef8cde5b1e7440e2eeb46aab18 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 4 Dec 2024 12:50:20 +0000 Subject: [PATCH 085/112] fix: https request queues still need a removals queue, it can be simplified but needs to still exist, there is a chicken-and-egg situation with the pointer for the request --- include/dpp/queues.h | 10 ++++++++++ src/dpp/queues.cpp | 42 ++++++++++++++++++++++++++++++------------ src/dpp/sslclient.cpp | 16 ++++++++++------ 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/include/dpp/queues.h b/include/dpp/queues.h index f54cb9c7a9..a58754e08c 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -383,6 +383,11 @@ class DPP_EXPORT http_request { /** @brief Returns true if the request is complete */ bool is_completed(); + + /** + * @brief Get the HTTPS client used to perform this request, or nullptr if there is none + */ + https_client* get_client() const; }; /** @@ -470,6 +475,11 @@ class DPP_EXPORT request_concurrency_queue { */ std::vector> requests_in; + /** + * @brief Requests to remove after a set amount of time has passed + */ + std::vector> removals; + /** * @brief Timer callback * @param index Index ID for this timer diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 51964e5787..a92e230d4f 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -107,11 +107,10 @@ http_request::http_request(const std::string &_url, http_completion_event comple { } -http_request::~http_request() = default; +http_request::~http_request() = default; void http_request::complete(const http_request_completion_t &c) { - /* Call completion handler only if the request has been completed */ - if (is_completed() && complete_handler) { + if (complete_handler) { complete_handler(c); } } @@ -154,6 +153,11 @@ bool http_request::is_completed() return completed; } +https_client* http_request::get_client() const +{ + return cli.get(); +} + /* Execute a HTTP request */ http_request_completion_t http_request::run(request_concurrency_queue* processor, cluster* owner) { @@ -241,7 +245,6 @@ http_request_completion_t http_request::run(request_concurrency_queue* processor } populate_result(_url, owner, result, *client); /* Set completion flag */ - completed = true; bucket_t newbucket; newbucket.limit = result.ratelimit_limit; @@ -257,13 +260,17 @@ http_request_completion_t http_request::run(request_concurrency_queue* processor processor->buckets[this->endpoint] = newbucket; /* Transfer it to completed requests */ - owner->queue_work(0, [this, result]() { - /* Manually release this unique_ptr now, to keep memory consumption and file descriptor consumption low. - * Note we do this BEFORE we call complete(), becasue if user code throws an exception we need to be sure - * we freed this first to avoid dangling pointer leaks. - */ - this->cli.reset(); - complete(result); + owner->queue_work(0, [owner, this, result, hci, _url]() { + try { + complete(result); + } + catch (const std::exception& e) { + owner->log(ll_error, "Uncaught exception thrown in HTTPS callback for " + std::string(request_verb[method]) + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": " + std::string(e.what())); + } + catch (...) { + owner->log(ll_error, "Uncaught exception thrown in HTTPS callback for " + std::string(request_verb[method]) + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": "); + } + completed = true; }); } ); @@ -296,6 +303,17 @@ request_concurrency_queue::request_concurrency_queue(class cluster* owner, class { in_timer = creator->start_timer([this](auto timer_handle) { tick_and_deliver_requests(in_index); + /* Clear pending removals in the removals queue */ + if (time(nullptr) % 90 == 0) { + std::scoped_lock lock1{in_mutex}; + for (auto it = removals.cbegin(); it != removals.cend();) { + if ((*it)->is_completed()) { + it = removals.erase(it); + } else { + ++it; + } + } + } }, 1); } @@ -385,7 +403,7 @@ void request_concurrency_queue::tick_and_deliver_requests(uint32_t index) for (auto it = begin; it != end; ++it) { if (it->get() == request_view) { /* Grab and remove */ - request_view->stash_self(std::move(*it)); + removals.emplace_back(std::move(*it)); requests_in.erase(it); break; } diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 0fb6af11b5..f9bb713334 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -548,18 +548,22 @@ bool ssl_client::handle_buffer(std::string &buffer) void ssl_client::close() { - if (!plaintext && ssl->ssl) { + if (!plaintext) { std::lock_guard lock(ssl_mutex); - SSL_free(ssl->ssl); - ssl->ssl = nullptr; + if (ssl != nullptr && ssl->ssl != nullptr) { + SSL_free(ssl->ssl); + ssl->ssl = nullptr; + } } connected = tcp_connect_done = false; client_to_server_length = client_to_server_offset = 0; last_tick = time(nullptr); bytes_in = bytes_out = 0; - owner->socketengine->delete_socket(sfd); - close_socket(sfd); - sfd = INVALID_SOCKET; + if (sfd != INVALID_SOCKET) { + owner->socketengine->delete_socket(sfd); + close_socket(sfd); + sfd = INVALID_SOCKET; + } obuffer.clear(); buffer.clear(); } From 4edc9a24b4b742c5945c8d931afb00bc87f30689 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 4 Dec 2024 17:58:29 +0000 Subject: [PATCH 086/112] fix: apple weirdness --- src/dpp/httpsclient.cpp | 2 +- src/dpp/queues.cpp | 5 +++-- src/dpp/socketengines/kqueue.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index a16cbdf1a6..7ab9f5fdac 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -349,7 +349,7 @@ void https_client::close() { https_client::~https_client() { if (sfd != INVALID_SOCKET) { - https_client::close(); + ssl_client::close(); } } diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index a92e230d4f..b4d737082b 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -239,7 +239,7 @@ http_request_completion_t http_request::run(request_concurrency_queue* processor if (client->timed_out) { result.error = h_connection; owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": Timed out while waiting for the response"); - } else if (cli->get_status() < 100) { + } else if (client->get_status() < 100) { result.error = h_connection; owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + request_verb[method] + " " + hci.hostname + ":" + std::to_string(hci.port) + _url + ": Malformed HTTP response"); } @@ -403,7 +403,8 @@ void request_concurrency_queue::tick_and_deliver_requests(uint32_t index) for (auto it = begin; it != end; ++it) { if (it->get() == request_view) { /* Grab and remove */ - removals.emplace_back(std::move(*it)); + rq = std::move(*it); + removals.push_back(std::move(rq)); requests_in.erase(it); break; } diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 9240d2e17b..4e81f0aab2 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -148,7 +148,7 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { bool remove_socket(dpp::socket fd) final { bool r = socket_engine_base::remove_socket(fd); if (r) { - struct kevent ke; + struct kevent ke{}; EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); From b01da99ce803cb97c2e9a6ad1eaa68708f0ba98f Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 5 Dec 2024 13:55:51 +0000 Subject: [PATCH 087/112] docs: document various new functions [skip ci] --- include/dpp/discordclient.h | 5 ++- include/dpp/discordvoiceclient.h | 3 +- include/dpp/socketengine.h | 10 ++++++ include/dpp/sslclient.h | 62 ++++++++++++-------------------- 4 files changed, 38 insertions(+), 42 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index e0c9b43915..ffca504ed0 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -519,7 +519,10 @@ class DPP_EXPORT discord_client : public websocket_client */ uint64_t get_channel_count(); - /** Fires every second from the underlying socket I/O loop, used for sending heartbeats */ + /** + * @brief Fires every second from the underlying socket I/O loop, used for sending heartbeats + * and any queued outbound websocket frames. + */ virtual void one_second_timer(); /** diff --git a/include/dpp/discordvoiceclient.h b/include/dpp/discordvoiceclient.h index 14a0f63ec8..3dd0e64a87 100644 --- a/include/dpp/discordvoiceclient.h +++ b/include/dpp/discordvoiceclient.h @@ -201,7 +201,6 @@ struct dave_binary_header_t { /** * Get the data package from the packed binary frame, as a vector of uint8_t * for use in the libdave functions - * @return data blob */ [[nodiscard]] std::vector get_data() const; @@ -905,7 +904,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client virtual void error(uint32_t errorcode); /** - * @brief Start and monitor I/O loop + * @brief Start and monitor websocket I/O */ void run(); diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index 18a8b2d98b..ce21d1cc75 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -246,6 +246,11 @@ struct DPP_EXPORT socket_engine_base { void prune(); protected: + /** + * @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); }; @@ -256,6 +261,11 @@ struct DPP_EXPORT socket_engine_base { 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 diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 3e7a834dea..9d237172d3 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -39,16 +39,6 @@ namespace dpp { */ class openssl_connection; -/** - * @brief A callback for socket status - */ -typedef std::function socket_callback_t; - -/** - * @brief A socket notification callback - */ -typedef std::function socket_notification_t; - /** * @brief Close a socket * @@ -254,33 +244,6 @@ class DPP_EXPORT ssl_client */ std::string get_cipher(); - /** - * @brief Attaching an additional file descriptor to this function will send notifications when there is data to read. - * - * NOTE: Only hook this if you NEED it as it can increase CPU usage of the thread! - * Returning -1 means that you don't want to be notified. - */ - socket_callback_t custom_readable_fd; - - /** - * @brief Attaching an additional file descriptor to this function will send notifications when you are able to write - * to the socket. - * - * NOTE: Only hook this if you NEED it as it can increase CPU usage of the thread! You should toggle this - * to -1 when you do not have anything to write otherwise it'll keep triggering repeatedly (it is level triggered). - */ - socket_callback_t custom_writeable_fd; - - /** - * @brief This event will be called when you can read from the custom fd - */ - socket_notification_t custom_readable_ready; - - /** - * @brief This event will be called when you can write to a custom fd - */ - socket_notification_t custom_writeable_ready; - /** * @brief True if we are keeping the connection alive after it has finished */ @@ -304,8 +267,8 @@ class DPP_EXPORT ssl_client ssl_client(cluster* creator, const std::string &_hostname, const std::string &_port = "443", bool plaintext_downgrade = false, bool reuse = false); /** - * @brief Nonblocking I/O loop - * @throw std::exception Any std::exception (or derivative) thrown from read_loop() causes reconnection of the shard + * @brief Set up non blocking I/O and configure on_read, on_write and on_error. + * @throw std::exception Any std::exception (or derivative) thrown from read_loop() indicates setup failed */ void read_loop(); @@ -341,12 +304,33 @@ class DPP_EXPORT ssl_client */ virtual void log(dpp::loglevel severity, const std::string &msg) const; + /** + * @brief Called while SSL handshake is in progress. + * If the handshake completes, the state of the socket is progressed to + * an established state. + * @param ev Socket events for the socket + */ void complete_handshake(const struct socket_events* ev); + /** + * @brief Called when the TCP socket has data to read + * @param fd Fild descriptor + * @param ev Socket events + */ void on_read(dpp::socket fd, const struct dpp::socket_events& ev); + /** + * @brief Called when the TCP socket can be written to without blocking + * @param fd File descriptor + * @param e Socket events + */ void on_write(dpp::socket fd, const struct dpp::socket_events& e); + /** + * @brief Called when there is an error on the TCP socket + * @param fd File descriptor + * @param error_code Error code + */ void on_error(dpp::socket fd, const struct dpp::socket_events&, int error_code); }; From a8b006b73ed4924efe296b022d2917defefe2e56 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 6 Dec 2024 11:08:51 +0000 Subject: [PATCH 088/112] [skip ci] adjust version to 10.1 --- include/dpp/discordclient.h | 9 +++++++++ include/dpp/version.h | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index ffca504ed0..669b81c32f 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -577,6 +577,15 @@ class DPP_EXPORT discord_client : public websocket_client */ discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t intents = 0, bool compressed = true, websocket_protocol_t ws_protocol = ws_json); + /** + * @brief Construct a discord_client object from another discord_client object + * Used when resuming, the url to connect to will be taken from the resume url of the + * other object, along with the seq number. + * + * @param old Previous connection to resume from + */ + explicit discord_client(discord_client& old); + /** * @brief Destroy the discord client object */ diff --git a/include/dpp/version.h b/include/dpp/version.h index 16665e519e..3380c1ddf1 100644 --- a/include/dpp/version.h +++ b/include/dpp/version.h @@ -22,9 +22,9 @@ #pragma once #ifndef DPP_VERSION_LONG -#define DPP_VERSION_LONG 0x00100035 -#define DPP_VERSION_SHORT 100035 -#define DPP_VERSION_TEXT "D++ 10.0.35 (29-Oct-2024)" +#define DPP_VERSION_LONG 0x00100100 +#define DPP_VERSION_SHORT 100100 +#define DPP_VERSION_TEXT "D++ 10.1.0 (06-Dec-2024)" #define DPP_VERSION_MAJOR ((DPP_VERSION_LONG & 0x00ff0000) >> 16) #define DPP_VERSION_MINOR ((DPP_VERSION_LONG & 0x0000ff00) >> 8) From e2771ad56e96ad89b61d46e368fe9f3d50cda294 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 7 Dec 2024 01:16:12 +0000 Subject: [PATCH 089/112] fix reconnection of shards by creating new shards on reconnect, with a management timer to handle reconnections to stop reconnect storms --- include/dpp/cluster.h | 13 ++++++++ include/dpp/discordclient.h | 19 ++--------- include/dpp/restresults.h | 5 +++ src/dpp/cluster.cpp | 36 ++++++++++++++++++++ src/dpp/discordclient.cpp | 65 ++++++++++++++++++++----------------- 5 files changed, 92 insertions(+), 46 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 46747db0e7..413b89fad7 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -125,6 +125,11 @@ class DPP_EXPORT cluster { */ shard_list shards; + /** + * @brief List of shards waiting for reconnection + */ + reconnect_list reconnections; + /** * @brief Ephemeral list of deleted timer ids */ @@ -182,6 +187,14 @@ class DPP_EXPORT cluster { */ std::mutex timer_guard; + /** + * @brief Mark a shard as requiring reconnection. + * Destructs the old shard in 5 seconds and creates a new one attempting to resume. + * + * @param shard_id Shard ID + */ + void add_reconnect(uint32_t shard_id); + public: /** * @brief Current bot token for all shards on this cluster and all commands sent via HTTP diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 669b81c32f..2b08949971 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -274,14 +274,6 @@ class DPP_EXPORT discord_client : public websocket_client */ void start_connecting(); - /** - * @brief Timer for use when reconnecting. - * - * The client will wait 5 seconds before retrying a connection, to comply - * with Discord rate limiting for websocket connections. - */ - timer reconnect_timer{0}; - /** * @brief Stores the most recent ping message on this shard, which we check * for to monitor latency @@ -410,11 +402,6 @@ class DPP_EXPORT discord_client : public websocket_client */ uint32_t max_shards; - /** - * @brief Thread ID - */ - std::thread::native_handle_type thread_id; - /** * @brief Last sequence number received, for resumes and pings */ @@ -583,8 +570,10 @@ class DPP_EXPORT discord_client : public websocket_client * other object, along with the seq number. * * @param old Previous connection to resume from + * @param sequence Sequence number of previous session + * @param session_id Session ID of previous session */ - explicit discord_client(discord_client& old); + explicit discord_client(discord_client& old, uint64_t sequence, const std::string& session_id); /** * @brief Destroy the discord client object @@ -613,8 +602,6 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief Start and monitor I/O loop. - * @note this is a blocking call and is usually executed within a - * thread by whatever creates the object. */ void run(); diff --git a/include/dpp/restresults.h b/include/dpp/restresults.h index a78b9e9dfe..05a227d47f 100644 --- a/include/dpp/restresults.h +++ b/include/dpp/restresults.h @@ -53,6 +53,11 @@ namespace dpp { */ typedef std::map shard_list; +/** + * @brief List of shards awaiting reconnection, by id with earliest possible reconnect time + */ +typedef std::map reconnect_list; + /** * @brief Represents the various information from the 'get gateway bot' api call */ diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 5c5b5a20d4..489ac17851 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -198,12 +198,48 @@ dpp::utility::uptime cluster::uptime() return dpp::utility::uptime(time(nullptr) - start_time); } +void cluster::add_reconnect(uint32_t shard_id) { + reconnections.emplace(shard_id, time(nullptr)); +} + void cluster::start(start_type return_after) { auto event_loop = [this]() -> void { + auto reconnect_monitor = start_timer([this](auto t) { + time_t now = time(nullptr); + for (auto reconnect = reconnections.begin(); reconnect != reconnections.end(); ++reconnect) { + auto shard_id = reconnect->first; + auto shard_reconnect_time = reconnect->second; + if (now >= shard_reconnect_time) { + /* This shard needs to be reconnected */ + reconnections.erase(reconnect); + discord_client* old = shards[shard_id]; + /* These values must be copied to the new connection + * to attempt to resume it + */ + auto seq_no = old->last_seq; + auto session_id = old->sessionid; + log(ll_info, "Reconnecting shard " + std::to_string(shard_id)); + /* Make a new resumed connection based off the old one */ + shards[shard_id] = new discord_client(*old, seq_no, session_id); + /* Delete the old one */ + delete old; + /* Set up the new shard's IO events */ + shards[shard_id]->run(); + /* It is not possible to reconnect another shard within the same 5-second window, + * due to discords strict rate limiting on shard connections, so we bail out here + * and only try another reconnect in the next timer interval. Do not try and make + * this support multiple reconnects per loop iteration or Discord will smack us + * with the rate limiting clue-by-four. + */ + return; + } + } + }, 5); while (!this->terminating && socketengine.get()) { socketengine->process_events(); } + stop_timer(reconnect_monitor); }; if (on_guild_member_add && !(intents & dpp::i_guild_members)) { diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 695c23bfb8..c7c8407d72 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -56,6 +56,39 @@ class zlibcontext { z_stream d_stream; }; + +/** + * @brief Resume constructor for websocket client + */ +discord_client::discord_client(discord_client &old, uint64_t sequence, const std::string& session_id) + : websocket_client(old.owner, old.resume_gateway_url, "443", old.compressed ? (old.protocol == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (old.protocol == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), + compressed(old.compressed), + zlib(nullptr), + decompressed_total(old.decompressed_total), + connect_time(0), + ping_start(0.0), + etf(nullptr), + creator(old.owner), + heartbeat_interval(0), + last_heartbeat(time(nullptr)), + shard_id(old.shard_id), + max_shards(old.max_shards), + last_seq(sequence), + token(old.token), + intents(old.intents), + sessionid(session_id), + resumes(old.resumes), + reconnects(old.reconnects), + websocket_ping(old.websocket_ping), + ready(false), + last_heartbeat_ack(time(nullptr)), + protocol(old.protocol), + resume_gateway_url(old.resume_gateway_url) +{ + etf = std::make_unique(etf_parser()); + start_connecting(); +} + discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), compressed(comp), @@ -99,38 +132,10 @@ discord_client::~discord_client() void discord_client::on_disconnect() { - if (reconnect_timer != 0U) { - log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnection already in progress..."); - return; - } set_resume_hostname(); - log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting in 5 seconds..."); + log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting..."); ssl_client::close(); - /* Prevent low level connect retries here, as we are handling it ourselves */ - connect_retries = MAX_RETRIES + 1; - end_zlib(); - /* Stop the timer first if its already ticking, to prevent concurrent reconnects */ - reconnect_timer = owner->start_timer([this](auto handle) { - log(dpp::ll_debug, "Reconnecting shard " + std::to_string(shard_id) + " to wss://" + hostname + "..."); - try { - if (timer_handle) { - owner->stop_timer(timer_handle); - timer_handle = 0; - } - start = time(nullptr); - ssl_client::connect(); - start_connecting(); - run(); - owner->stop_timer(handle); - reconnect_timer = 0; - } - catch (const std::exception &e) { - /* If we get here, the timer will tick again */ - ssl_client::close(); - end_zlib(); - log(dpp::ll_debug, "Error reconnecting shard " + std::to_string(shard_id) + ": " + std::string(e.what()) + "; Retry in 5 seconds..."); - } - }, 5); + owner->add_reconnect(this->shard_id); } uint64_t discord_client::get_decompressed_bytes_in() From 9d3fbb2cc2f690df59a4be5fd297cd9d0862e579 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 9 Dec 2024 17:42:29 +0000 Subject: [PATCH 090/112] fix reconnect on timeout --- include/dpp/sslclient.h | 2 +- include/dpp/wsclient.h | 20 +++++++++++++++++--- src/dpp/cluster.cpp | 21 ++++++++++++++++----- src/dpp/discordclient.cpp | 13 +++++++++++-- src/dpp/wsclient.cpp | 25 +++++++++++++++++++++++-- 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 9d237172d3..3fe1c4c0db 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -314,7 +314,7 @@ class DPP_EXPORT ssl_client /** * @brief Called when the TCP socket has data to read - * @param fd Fild descriptor + * @param fd File descriptor * @param ev Socket events */ void on_read(dpp::socket fd, const struct dpp::socket_events& ev); diff --git a/include/dpp/wsclient.h b/include/dpp/wsclient.h index da2a6c6fdf..11251f2a1c 100644 --- a/include/dpp/wsclient.h +++ b/include/dpp/wsclient.h @@ -153,7 +153,7 @@ class DPP_EXPORT websocket_client : public ssl_client { protected: /** - * @brief (Re)connect + * @brief Connect to websocket server */ virtual void connect(); @@ -163,6 +163,18 @@ class DPP_EXPORT websocket_client : public ssl_client { */ [[nodiscard]] ws_state get_state() const; + /** + * @brief If true the connection timed out while waiting, + * when waiting for SSL negotiation, TCP connect(), or HTTP. + */ + bool timed_out; + + /** + * @brief Time at which the connection should be abandoned, + * if we are still connecting or negotiating with a HTTP server + */ + time_t timeout; + public: /** @@ -171,8 +183,10 @@ class DPP_EXPORT websocket_client : public ssl_client { * @param port Port to connect to * @param urlpath The URL path components of the HTTP request to send * @param opcode The encoding type to use, either OP_BINARY or OP_TEXT - * @note Voice websockets only support OP_TEXT, and other websockets must be - * OP_BINARY if you are going to send ETF. + * @note This just indicates the default for frames sent. Certain sockets, + * such as voice websockets, may send a combination of OP_TEXT and OP_BINARY + * frames, whereas shard websockets will only ever send OP_BINARY for ETF and + * OP_TEXT for JSON. */ websocket_client(cluster* creator, const std::string& hostname, const std::string& port = "443", const std::string& urlpath = "", ws_opcode opcode = OP_BINARY); diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 489ac17851..eee7436bd9 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -221,11 +221,22 @@ void cluster::start(start_type return_after) { auto session_id = old->sessionid; log(ll_info, "Reconnecting shard " + std::to_string(shard_id)); /* Make a new resumed connection based off the old one */ - shards[shard_id] = new discord_client(*old, seq_no, session_id); - /* Delete the old one */ - delete old; - /* Set up the new shard's IO events */ - shards[shard_id]->run(); + try { + shards[shard_id] = nullptr; + shards[shard_id] = new discord_client(*old, seq_no, session_id); + /* Delete the old one */ + delete old; + old = nullptr; + /* Set up the new shard's IO events */ + shards[shard_id]->run(); + } + catch (const std::exception& e) { + log(ll_info, "Exception when reconnecting shard " + std::to_string(shard_id) + ": " + std::string(e.what())); + delete shards[shard_id]; + delete old; + old = nullptr; + add_reconnect(shard_id); + } /* It is not possible to reconnect another shard within the same 5-second window, * due to discords strict rate limiting on shard connections, so we bail out here * and only try another reconnect in the next timer interval. Do not try and make diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index c7c8407d72..a59f76e325 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -274,12 +274,12 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) auto o = j.find("op"); if (o != j.end() && !o->is_null()) { - uint32_t op = o->get(); + shard_frame_type op = o->get(); switch (op) { case ft_invalid_session: /* Reset session state and fall through to 10 */ - op = 10; + op = ft_hello; log(dpp::ll_debug, "Failed to resume session " + sessionid + ", will reidentify"); this->sessionid.clear(); this->last_seq = 0; @@ -359,6 +359,15 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) this->last_heartbeat_ack = time(nullptr); websocket_ping = utility::time_f() - ping_start; break; + case ft_heartbeat: + case ft_identify: + case ft_presence: + case ft_voice_state_update: + case ft_resume: + case ft_request_guild_members: + case ft_request_soundboard_sounds: + log(dpp::ll_error, "Received invalid opcode on websocket for session " + sessionid); + break; } } return true; diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index ffad79ea3f..8f455d085c 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -42,7 +42,9 @@ websocket_client::websocket_client(cluster* creator, const std::string& hostname : ssl_client(creator, hostname, port), state(HTTP_HEADERS), path(urlpath), - data_opcode(opcode) + data_opcode(opcode), + timed_out(false), + timeout(time(nullptr) + 5) { uint64_t k = (time(nullptr) * time(nullptr)); /* A 64 bit value as hex with leading zeroes is always 16 chars. @@ -303,7 +305,9 @@ bool websocket_client::parseheader(std::string& data) void websocket_client::one_second_timer() { - if (((time(nullptr) % 20) == 0) && (state == CONNECTED)) { + time_t now = time(nullptr); + + if (((now % 20) == 0) && (state == CONNECTED)) { /* For sending pings, we send with payload */ unsigned char out[MAXHEADERSIZE]; std::string payload = "keepalive"; @@ -312,6 +316,23 @@ void websocket_client::one_second_timer() ssl_client::socket_write(header); ssl_client::socket_write(payload); } + + /* Handle timeouts for connect(), SSL negotiation and HTTP negotiation */ + if (!timed_out && sfd != INVALID_SOCKET) { + if (!tcp_connect_done && now >= timeout) { + log(ll_warning, "Websocket connection timed out: connect()"); + timed_out = true; + this->close(); + } else if (tcp_connect_done && !connected && now >= timeout && this->state != CONNECTED) { + log(ll_warning, "Websocket connection timed out: SSL handshake"); + timed_out = true; + this->close(); + } else if (now >= timeout && this->state != CONNECTED) { + log(ll_warning, "Websocket connection timed out: HTTP negotiation"); + timed_out = true; + this->close(); + } + } } void websocket_client::handle_ping(const std::string &payload) From f3b85a4a68154b69759ba116a3676cc3122bce26 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Mon, 9 Dec 2024 23:05:34 +0000 Subject: [PATCH 091/112] fix: check for events on invalid sockets --- src/dpp/sslclient.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index f9bb713334..60f7748e7a 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -491,9 +491,7 @@ void ssl_client::on_write(socket fd, const struct socket_events& e) { } void ssl_client::on_error(socket fd, const struct socket_events&, int error_code) { - if (sfd != INVALID_SOCKET) { - this->close(); - } + this->close(); } void ssl_client::read_loop() @@ -502,8 +500,24 @@ void ssl_client::read_loop() dpp::socket_events events( sfd, WANT_READ | WANT_WRITE | WANT_ERROR, - [this](socket fd, const struct socket_events &e) { on_read(fd, e); }, - [this](socket fd, const struct socket_events &e) { on_write(fd, e); }, + [this](socket fd, const struct socket_events &e) { + if (this->sfd == INVALID_SOCKET) { + close_socket(fd); + owner->socketengine->delete_socket(fd); + on_error(fd, e, 0); + return; + } + on_read(fd, e); + }, + [this](socket fd, const struct socket_events &e) { + if (this->sfd == INVALID_SOCKET) { + close_socket(fd); + owner->socketengine->delete_socket(fd); + on_error(fd, e, 0); + return; + } + on_write(fd, e); + }, [this](socket fd, const struct socket_events &e, int error_code) { on_error(fd, e, error_code); } ); owner->socketengine->register_socket(events); From 6e87030018d3ce2515c97281e3629e039a8d6354 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 10 Dec 2024 02:22:11 +0000 Subject: [PATCH 092/112] call on_disconnect in more situations so we can properly handle dead sockets --- src/dpp/discordclient.cpp | 4 +++- src/dpp/wsclient.cpp | 10 ++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index a59f76e325..520d0a1e00 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -133,7 +133,9 @@ discord_client::~discord_client() void discord_client::on_disconnect() { set_resume_hostname(); - log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting..."); + if (sfd != INVALID_SOCKET) { + log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting..."); + } ssl_client::close(); owner->add_reconnect(this->shard_id); } diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index 8f455d085c..a87239d011 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -320,15 +320,15 @@ void websocket_client::one_second_timer() /* Handle timeouts for connect(), SSL negotiation and HTTP negotiation */ if (!timed_out && sfd != INVALID_SOCKET) { if (!tcp_connect_done && now >= timeout) { - log(ll_warning, "Websocket connection timed out: connect()"); + log(ll_trace, "Websocket connection timed out: connect()"); timed_out = true; this->close(); } else if (tcp_connect_done && !connected && now >= timeout && this->state != CONNECTED) { - log(ll_warning, "Websocket connection timed out: SSL handshake"); + log(ll_trace, "Websocket connection timed out: SSL handshake"); timed_out = true; this->close(); } else if (now >= timeout && this->state != CONNECTED) { - log(ll_warning, "Websocket connection timed out: HTTP negotiation"); + log(ll_trace, "Websocket connection timed out: HTTP negotiation"); timed_out = true; this->close(); } @@ -370,9 +370,7 @@ void websocket_client::on_disconnect() void websocket_client::close() { - if (sfd != INVALID_SOCKET) { - this->on_disconnect(); - } + this->on_disconnect(); this->state = HTTP_HEADERS; ssl_client::close(); } From edcefd6899f7d914b9f8db374cfdde420733ac97 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Tue, 10 Dec 2024 14:49:09 +0000 Subject: [PATCH 093/112] fix: ensure heirarchy correctly calls the various inherited functions. todo: refactor this mess --- src/dpp/cluster.cpp | 18 +++++++++++++++--- src/dpp/discordclient.cpp | 2 +- src/dpp/socketengines/epoll.cpp | 2 ++ src/dpp/socketengines/kqueue.cpp | 1 + src/dpp/sslclient.cpp | 13 ++++++++++--- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index eee7436bd9..b9c5faf838 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -199,7 +199,8 @@ dpp::utility::uptime cluster::uptime() } void cluster::add_reconnect(uint32_t shard_id) { - reconnections.emplace(shard_id, time(nullptr)); + reconnections.emplace(shard_id, time(nullptr) + 5); + log(ll_trace, "Reconnecting in 5 seconds..."); } void cluster::start(start_type return_after) { @@ -222,12 +223,20 @@ void cluster::start(start_type return_after) { log(ll_info, "Reconnecting shard " + std::to_string(shard_id)); /* Make a new resumed connection based off the old one */ try { - shards[shard_id] = nullptr; - shards[shard_id] = new discord_client(*old, seq_no, session_id); + if (shards[shard_id] != nullptr) { + log(ll_trace, "Attempting resume..."); + shards[shard_id] = nullptr; + shards[shard_id] = new discord_client(*old, seq_no, session_id); + } else { + log(ll_trace, "Attempting full reconnection..."); + shards[shard_id] = new discord_client(this, shard_id, numshards, token, intents, compressed, ws_mode); + } /* Delete the old one */ + log(ll_trace, "Attempting to delete old connection..."); delete old; old = nullptr; /* Set up the new shard's IO events */ + log(ll_trace, "Running new connection..."); shards[shard_id]->run(); } catch (const std::exception& e) { @@ -235,6 +244,7 @@ void cluster::start(start_type return_after) { delete shards[shard_id]; delete old; old = nullptr; + shards[shard_id] = nullptr; add_reconnect(shard_id); } /* It is not possible to reconnect another shard within the same 5-second window, @@ -244,6 +254,8 @@ void cluster::start(start_type return_after) { * with the rate limiting clue-by-four. */ return; + } else { + log(ll_trace, "Shard " + std::to_string(shard_id) + " not ready to reconnect yet."); } } }, 5); diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 520d0a1e00..8239af4b04 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -118,7 +118,7 @@ discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint3 } void discord_client::start_connecting() { - this->connect(); + websocket_client::connect(); } void discord_client::cleanup() diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index e36f825158..2ccc28b2fe 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -125,6 +126,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { } } catch (const std::exception& e) { + owner->log(ll_trace, "Socket loop exception: " + std::string(e.what())); eh->on_error(fd, *eh, 0); } } diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 4e81f0aab2..310c4573ab 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -95,6 +95,7 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { } } catch (const std::exception& e) { + owner->log(ll_trace, "Socket loop exception: " + std::string(e.what())); eh->on_error(kev.ident, *eh, 0); } } diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 60f7748e7a..5cf0616a21 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -208,7 +208,8 @@ ssl_client::ssl_client(cluster* creator, const std::string &_hostname, const std ssl = new openssl_connection(); } try { - this->connect(); + //this->connect(); + ssl_client::connect(); } catch (std::exception&) { cleanup(); @@ -526,7 +527,7 @@ void ssl_client::read_loop() if (!timer_handle) { timer_handle = owner->start_timer([this, setup_events](auto handle) { one_second_timer(); - if (!tcp_connect_done && time(nullptr) > start + 2 && connect_retries < MAX_RETRIES) { + if (!tcp_connect_done && time(nullptr) > start + 2 && connect_retries < MAX_RETRIES && sfd != INVALID_SOCKET) { /* Retry failed connect(). This can happen even in the best situation with bullet-proof hosting. * Previously with blocking connect() there was some leniency in this, but now we have to do this * ourselves. @@ -534,9 +535,15 @@ void ssl_client::read_loop() * Retry up to 3 times, 2 seconds between retries. After this, give up and let timeout code * take the wheel (will likely end with an exception). */ + log(ll_trace, "connect() retry #" + std::to_string(connect_retries + 1)); close_socket(sfd); owner->socketengine->delete_socket(sfd); - ssl_client::connect(); + try { + ssl_client::connect(); + } + catch (const std::exception& e) { + log(ll_trace, "connect() exception: " + std::string(e.what())); + } setup_events(); start = time(nullptr) + 2; connect_retries++; From 587a51bce58d012893165b91cffcef0a59311b40 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 11 Dec 2024 10:47:46 +0000 Subject: [PATCH 094/112] fix: don't store the event ptrs in the kevent/epoll udata, this can end up with dangling ptrs in events --- include/dpp/socketengine.h | 15 +++++---- src/dpp/cluster.cpp | 4 +-- src/dpp/socketengine.cpp | 9 +++++ src/dpp/socketengines/epoll.cpp | 22 ++++--------- src/dpp/socketengines/kqueue.cpp | 56 ++++++++++++-------------------- src/dpp/sslclient.cpp | 2 -- 6 files changed, 45 insertions(+), 63 deletions(-) diff --git a/include/dpp/socketengine.h b/include/dpp/socketengine.h index ce21d1cc75..051aa2f801 100644 --- a/include/dpp/socketengine.h +++ b/include/dpp/socketengine.h @@ -160,13 +160,6 @@ struct DPP_EXPORT socket_engine_base { */ socket_container fds; - /** - * @brief Thread pool. - * Event calls go into the thread pool and are called as - * and when threads in the pool are available. - */ - std::unique_ptr pool; - /** * @brief Number of file descriptors we are waiting to delete */ @@ -244,6 +237,7 @@ struct DPP_EXPORT socket_engine_base { * remove_socket() on each entry to be removed. */ void prune(); + protected: /** @@ -252,6 +246,13 @@ struct DPP_EXPORT socket_engine_base { * @param fd File descriptor to remove */ virtual bool remove_socket(dpp::socket fd); + + /** + * @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); }; /** diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index b9c5faf838..3ad3553d6c 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -199,8 +199,8 @@ dpp::utility::uptime cluster::uptime() } void cluster::add_reconnect(uint32_t shard_id) { - reconnections.emplace(shard_id, time(nullptr) + 5); - log(ll_trace, "Reconnecting in 5 seconds..."); + reconnections[shard_id] =time(nullptr) + 5; + log(ll_trace, "Reconnecting shard " + std::to_string(shard_id) + " in 5 seconds..."); } void cluster::start(start_type return_after) { diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index 3a39a31eb3..4b8730ee0a 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -72,6 +72,15 @@ socket_engine_base::socket_engine_base(cluster* creator) : owner(creator) { time_t last_time = time(nullptr); +socket_events* socket_engine_base::get_fd(dpp::socket fd) { + std::unique_lock lock(fds_mutex); + auto iter = fds.find(fd); + if (iter == fds.end() || ((iter->second->flags & WANT_DELETION) != 0L)) { + return nullptr; + } + return iter->second.get(); +} + void socket_engine_base::prune() { if (to_delete_count > 0) { std::unique_lock lock(fds_mutex); diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 2ccc28b2fe..648e28ed0d 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -44,7 +44,7 @@ int modify_event(int epoll_handle, socket_events* eh, int new_events) { if ((new_events & WANT_ERROR) != 0) { new_ev.events |= EPOLLERR; } - new_ev.data.ptr = static_cast(eh); + new_ev.data.fd = eh->fd; epoll_ctl(epoll_handle, EPOLL_CTL_MOD, eh->fd, &new_ev); } return new_events; @@ -80,13 +80,9 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { for (int j = 0; j < i; j++) { epoll_event ev = events[j]; - auto* const eh = static_cast(ev.data.ptr); - if (!eh) { - continue; - } - - const int fd = eh->fd; - if (fd == INVALID_SOCKET || eh->flags & WANT_DELETION) { + const int fd = ev.data.fd; + auto eh = get_fd(fd); + if (!eh || fd == INVALID_SOCKET) { continue; } @@ -147,10 +143,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { if ((e.flags & WANT_ERROR) != 0) { ev.events |= EPOLLERR; } - { - std::shared_lock lock(fds_mutex); - ev.data.ptr = fds.find(e.fd)->second.get(); - } + ev.data.fd = e.fd; return epoll_ctl(epoll_handle, EPOLL_CTL_ADD, e.fd, &ev) >= 0; } return r; @@ -170,10 +163,7 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { if ((e.flags & WANT_ERROR) != 0) { ev.events |= EPOLLERR; } - { - std::shared_lock lock(fds_mutex); - ev.data.ptr = fds.find(e.fd)->second.get(); - } + ev.data.fd = e.fd; return epoll_ctl(epoll_handle, EPOLL_CTL_MOD, e.fd, &ev) >= 0; } return r; diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 310c4573ab..3c3acf16c5 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -67,8 +67,8 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { for (int j = 0; j < i; j++) { const struct kevent& kev = ke_list[j]; - auto* eh = reinterpret_cast(kev.udata); - if (eh == nullptr || eh->flags & WANT_DELETION) { + auto eh = get_fd(kev.ident); + if (eh == nullptr) { continue; } @@ -102,46 +102,30 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { prune(); } + bool set_events(const socket_events& e) { + struct kevent ke{}; + if ((e.flags & WANT_READ) != 0) { + EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); + } + if ((e.flags & WANT_WRITE) != 0) { + EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); + } + } + bool register_socket(const socket_events& e) final { - bool r = socket_engine_base::register_socket(e); - if (r) { - struct kevent ke{}; - socket_events* se{}; - { - std::unique_lock lock(fds_mutex); - se = fds.find(e.fd)->second.get(); - } - if ((se->flags & WANT_READ) != 0) { - EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); - kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); - } - if ((se->flags & WANT_WRITE) != 0) { - EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); - kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); - } + if (socket_engine_base::register_socket(e)) { + return set_events(e); } - return r; + return false; } bool update_socket(const socket_events& e) final { - bool r = socket_engine_base::update_socket(e); - if (r) { - struct kevent ke{}; - socket_events* se{}; - { - std::unique_lock lock(fds_mutex); - se = fds.find(e.fd)->second.get(); - } - if ((e.flags & WANT_READ) != 0) { - EV_SET(&ke, e.fd, EVFILT_READ, EV_ADD, 0, 0, static_cast(se)); - kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); - } - if ((e.flags & WANT_WRITE) != 0) { - EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast(se)); - kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); - } + if (socket_engine_base::update_socket(e)) { + return set_events(e); } - return r; + return false; } protected: diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index 5cf0616a21..fcc31c05ab 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -505,7 +505,6 @@ void ssl_client::read_loop() if (this->sfd == INVALID_SOCKET) { close_socket(fd); owner->socketengine->delete_socket(fd); - on_error(fd, e, 0); return; } on_read(fd, e); @@ -514,7 +513,6 @@ void ssl_client::read_loop() if (this->sfd == INVALID_SOCKET) { close_socket(fd); owner->socketengine->delete_socket(fd); - on_error(fd, e, 0); return; } on_write(fd, e); From 5a06ebc9fca25817f737a45b593443fe822e4dbd Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 12 Dec 2024 02:17:52 +0000 Subject: [PATCH 095/112] fix: immediately delete fds that want deletion after we are done firing their events rather than prune() loop fix: replace fds that already exist in the set. emplace would fail. --- src/dpp/cluster.cpp | 3 ++- src/dpp/discordclient.cpp | 1 + src/dpp/socketengine.cpp | 29 +++++++++-------------------- src/dpp/socketengines/epoll.cpp | 18 ++++++++++-------- src/dpp/socketengines/kqueue.cpp | 23 +++++++++++++---------- src/dpp/socketengines/poll.cpp | 28 +++++++++++++--------------- src/dpp/sslclient.cpp | 1 + src/dpp/wsclient.cpp | 1 + 8 files changed, 50 insertions(+), 54 deletions(-) diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 3ad3553d6c..05665cf6db 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -199,7 +199,7 @@ dpp::utility::uptime cluster::uptime() } void cluster::add_reconnect(uint32_t shard_id) { - reconnections[shard_id] =time(nullptr) + 5; + reconnections[shard_id] = time(nullptr) + 5; log(ll_trace, "Reconnecting shard " + std::to_string(shard_id) + " in 5 seconds..."); } @@ -208,6 +208,7 @@ void cluster::start(start_type return_after) { auto event_loop = [this]() -> void { auto reconnect_monitor = start_timer([this](auto t) { time_t now = time(nullptr); + log(ll_trace, "Ticking reconnect monitor with " + std::to_string(reconnections.size()) + " queued reconnections"); for (auto reconnect = reconnections.begin(); reconnect != reconnections.end(); ++reconnect) { auto shard_id = reconnect->first; auto shard_reconnect_time = reconnect->second; diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 8239af4b04..f45ed0de93 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -132,6 +132,7 @@ discord_client::~discord_client() void discord_client::on_disconnect() { + log(ll_trace, "discord_client::on_disconnect()"); set_resume_hostname(); if (sfd != INVALID_SOCKET) { log(dpp::ll_debug, "Lost connection to websocket on shard " + std::to_string(shard_id) + ", reconnecting..."); diff --git a/src/dpp/socketengine.cpp b/src/dpp/socketengine.cpp index 4b8730ee0a..762dbcf9e5 100644 --- a/src/dpp/socketengine.cpp +++ b/src/dpp/socketengine.cpp @@ -36,8 +36,10 @@ bool socket_engine_base::register_socket(const socket_events &e) { if (e.fd != INVALID_SOCKET && i == fds.end()) { fds.emplace(e.fd, std::make_unique(e)); return true; - } else if (e.fd != INVALID_SOCKET && i != fds.end()) { - this->remove_socket(e.fd); + } + if (e.fd != INVALID_SOCKET && i != fds.end()) { + remove_socket(e.fd); + fds.erase(i); fds.emplace(e.fd, std::make_unique(e)); return true; } @@ -75,37 +77,25 @@ time_t last_time = time(nullptr); socket_events* socket_engine_base::get_fd(dpp::socket fd) { std::unique_lock lock(fds_mutex); auto iter = fds.find(fd); - if (iter == fds.end() || ((iter->second->flags & WANT_DELETION) != 0L)) { + if (iter == fds.end()) { return nullptr; } return iter->second.get(); } void socket_engine_base::prune() { - if (to_delete_count > 0) { - std::unique_lock lock(fds_mutex); - for (auto it = fds.cbegin(); it != fds.cend();) { - if ((it->second->flags & WANT_DELETION) != 0L) { - remove_socket(it->second->fd); - it = fds.erase(it); - } else { - ++it; - } - } - to_delete_count = 0; - } if (time(nullptr) != last_time) { try { - /* Every minute, rehash all cache containers. - * We do this from the socket engine now, not from - * shard 0, so no need to run shards to have timers! - */ owner->tick_timers(); } catch (const std::exception& e) { owner->log(dpp::ll_error, "Uncaught exception in tick_timers: " + std::string(e.what())); } if ((time(nullptr) % 60) == 0) { + /* Every minute, rehash all cache containers. + * We do this from the socket engine now, not from + * shard 0, so no need to run shards to have timers! + */ dpp::garbage_collection(); } @@ -120,7 +110,6 @@ bool socket_engine_base::delete_socket(dpp::socket fd) { return false; } iter->second->flags |= WANT_DELETION; - to_delete_count++; return true; } diff --git a/src/dpp/socketengines/epoll.cpp b/src/dpp/socketengines/epoll.cpp index 648e28ed0d..ed601878a8 100644 --- a/src/dpp/socketengines/epoll.cpp +++ b/src/dpp/socketengines/epoll.cpp @@ -82,11 +82,11 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { const int fd = ev.data.fd; auto eh = get_fd(fd); - if (!eh || fd == INVALID_SOCKET) { + if (eh == nullptr || fd == INVALID_SOCKET) { continue; } - try { + if ((eh->flags & WANT_DELETION) == 0L) try { if ((ev.events & EPOLLHUP) != 0U) { if (eh->on_error) { @@ -125,6 +125,11 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { owner->log(ll_trace, "Socket loop exception: " + std::string(e.what())); eh->on_error(fd, *eh, 0); } + + if ((eh->flags & WANT_DELETION) != 0L) { + remove_socket(fd); + fds.erase(fd); + } } prune(); } @@ -172,12 +177,9 @@ struct DPP_EXPORT socket_engine_epoll : public socket_engine_base { protected: bool remove_socket(dpp::socket fd) final { - bool r = socket_engine_base::remove_socket(fd); - if (r) { - struct epoll_event ev{}; - epoll_ctl(epoll_handle, EPOLL_CTL_DEL, fd, &ev); - } - return r; + struct epoll_event ev{}; + epoll_ctl(epoll_handle, EPOLL_CTL_DEL, fd, &ev); + return true; } }; diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 3c3acf16c5..77fd81d385 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -72,7 +72,7 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { continue; } - try { + if ((eh->flags & WANT_DELETION) == 0L) try { const short filter = kev.filter; if (kev.flags & EV_EOF || kev.flags & EV_ERROR) { @@ -98,6 +98,12 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { owner->log(ll_trace, "Socket loop exception: " + std::string(e.what())); eh->on_error(kev.ident, *eh, 0); } + + + if ((eh->flags & WANT_DELETION) != 0L) { + remove_socket(kev.ident); + fds.erase(kev.ident); + } } prune(); } @@ -131,15 +137,12 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { protected: bool remove_socket(dpp::socket fd) final { - bool r = socket_engine_base::remove_socket(fd); - if (r) { - struct kevent ke{}; - EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); - kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); - EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); - } - return r; + struct kevent ke{}; + EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); + EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); + return true; } }; diff --git a/src/dpp/socketengines/poll.cpp b/src/dpp/socketengines/poll.cpp index a336a20954..beb168ed9e 100644 --- a/src/dpp/socketengines/poll.cpp +++ b/src/dpp/socketengines/poll.cpp @@ -90,17 +90,12 @@ struct DPP_EXPORT socket_engine_poll : public socket_engine_base { processed++; } - auto iter = fds.find(fd); - if (iter == fds.end()) { - continue; - } - socket_events *eh = iter->second.get(); - + socket_events *eh = get_fd(fd); if (eh == nullptr || eh->flags & WANT_DELETION) { continue; } - try { + if ((eh->flags & WANT_DELETION) == 0L) try { if ((revents & POLLHUP) != 0) { eh->on_error(fd, *eh, 0); @@ -130,6 +125,12 @@ struct DPP_EXPORT socket_engine_poll : public socket_engine_base { } catch (const std::exception &e) { eh->on_error(fd, *eh, 0); } + + if ((eh->flags & WANT_DELETION) != 0L) { + remove_socket(fd); + std::unique_lock lock(fds_mutex); + fds.erase(fd); + } } } @@ -184,14 +185,11 @@ struct DPP_EXPORT socket_engine_poll : public socket_engine_base { protected: bool remove_socket(dpp::socket fd) final { - bool r = socket_engine_base::remove_socket(fd); - if (r) { - std::unique_lock lock(poll_set_mutex); - for (auto i = poll_set.begin(); i != poll_set.end(); ++i) { - if (i->fd == fd) { - poll_set.erase(i); - return true; - } + std::unique_lock lock(poll_set_mutex); + for (auto i = poll_set.begin(); i != poll_set.end(); ++i) { + if (i->fd == fd) { + poll_set.erase(i); + return true; } } return false; diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index fcc31c05ab..c7c9a15c02 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -579,6 +579,7 @@ void ssl_client::close() last_tick = time(nullptr); bytes_in = bytes_out = 0; if (sfd != INVALID_SOCKET) { + log(ll_trace, "ssl_client::close() with sfd"); owner->socketengine->delete_socket(sfd); close_socket(sfd); sfd = INVALID_SOCKET; diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index a87239d011..d4e6d749fb 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -370,6 +370,7 @@ void websocket_client::on_disconnect() void websocket_client::close() { + log(ll_trace, "websocket_client::close()"); this->on_disconnect(); this->state = HTTP_HEADERS; ssl_client::close(); From 4ee7abb7116eb48520d0378d936048edca8f1037 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 12 Dec 2024 09:03:40 +0000 Subject: [PATCH 096/112] fix: use exceptions to bail out of parseheader --- src/dpp/discordclient.cpp | 9 +++++---- src/dpp/wsclient.cpp | 12 ++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index f45ed0de93..f232f0c278 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -212,14 +212,17 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) case Z_STREAM_ERROR: this->error(err_compression_stream); this->close(); + return false; return true; case Z_DATA_ERROR: this->error(err_compression_data); this->close(); + return false; return true; case Z_MEM_ERROR: this->error(err_compression_memory); this->close(); + return false; return true; case Z_OK: this->decompressed.append((const char*)decomp_buffer.data(), have); @@ -353,9 +356,8 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) } break; case ft_reconnect: - log(dpp::ll_debug, "Reconnection requested, closing socket " + sessionid); message_queue.clear(); - this->close(); + throw dpp::connection_exception("Reconnection requested, closing socket " + sessionid); break; /* Heartbeat ack */ case ft_heartbeat_ack: @@ -369,8 +371,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) case ft_resume: case ft_request_guild_members: case ft_request_soundboard_sounds: - log(dpp::ll_error, "Received invalid opcode on websocket for session " + sessionid); - break; + throw dpp::connection_exception("Received invalid opcode on websocket for session " + sessionid); } } return true; diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index d4e6d749fb..2180ec5f5b 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -200,7 +200,13 @@ bool websocket_client::handle_buffer(std::string& buffer) } } else if (state == CONNECTED) { /* Process packets until we can't (buffer will erase data until parseheader returns false) */ - while (this->parseheader(buffer)) { } + try { + while (this->parseheader(buffer)) { } + } + catch (const std::exception &e) { + log(ll_debug, "Receiving exception: " + std::string(e.what())); + return false; + } } return true; @@ -274,7 +280,9 @@ bool websocket_client::parseheader(std::string& data) handle_ping(data.substr(payloadstartoffset, len)); } else if ((opcode & ~WS_FINBIT) != OP_PONG) { /* Otherwise, handle everything else apart from a PONG. */ /* Pass this frame to the deriving class */ - this->handle_frame(data.substr(payloadstartoffset, len), static_cast(opcode & ~WS_FINBIT)); + if (!this->handle_frame(data.substr(payloadstartoffset, len), static_cast(opcode & ~WS_FINBIT))) { + return false; + } } /* Remove this frame from the input buffer */ From ae0565c099bbe887befaed5f64f7f12ef13c4b36 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 12 Dec 2024 15:09:05 +0000 Subject: [PATCH 097/112] fix: missing boolean --- src/dpp/socketengines/kqueue.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dpp/socketengines/kqueue.cpp b/src/dpp/socketengines/kqueue.cpp index 77fd81d385..eef50da65e 100644 --- a/src/dpp/socketengines/kqueue.cpp +++ b/src/dpp/socketengines/kqueue.cpp @@ -118,6 +118,7 @@ struct DPP_EXPORT socket_engine_kqueue : public socket_engine_base { EV_SET(&ke, e.fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, nullptr); kevent(kqueue_handle, &ke, 1, nullptr, 0, nullptr); } + return true; } bool register_socket(const socket_events& e) final { From c3b5257c3cab0fb176312eedc80afa33f7636849 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 12 Dec 2024 23:17:02 +0000 Subject: [PATCH 098/112] tidy up casting in decompression code --- include/dpp/discordclient.h | 2 +- src/dpp/discordclient.cpp | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 2b08949971..309119f197 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -309,7 +309,7 @@ class DPP_EXPORT discord_client : public websocket_client * a vector of length zero, but when compression is * enabled it will be resized to a DECOMP_BUFFER_SIZE buffer. */ - std::vector decomp_buffer; + std::vector decomp_buffer; /** * @brief Decompressed string diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index f232f0c278..d06a5ba2d1 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -190,7 +190,7 @@ void discord_client::run() bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) { - auto& data = (std::string&)buffer; + auto& data = const_cast(buffer); /* gzip compression is a special case */ if (compressed) { @@ -202,7 +202,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) zlib->d_stream.next_in = (Bytef*)buffer.data(); zlib->d_stream.avail_in = static_cast(buffer.size()); do { - zlib->d_stream.next_out = reinterpret_cast(decomp_buffer.data()); + zlib->d_stream.next_out = static_cast(decomp_buffer.data()); zlib->d_stream.avail_out = DECOMP_BUFFER_SIZE; int ret = inflate(&(zlib->d_stream), Z_NO_FLUSH); size_t have = DECOMP_BUFFER_SIZE - zlib->d_stream.avail_out; @@ -213,21 +213,18 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) this->error(err_compression_stream); this->close(); return false; - return true; case Z_DATA_ERROR: this->error(err_compression_data); this->close(); return false; - return true; case Z_MEM_ERROR: this->error(err_compression_memory); this->close(); return false; - return true; case Z_OK: - this->decompressed.append((const char*)decomp_buffer.data(), have); + this->decompressed.append(decomp_buffer.begin(), decomp_buffer.begin() + have); this->decompressed_total += have; - break; + break; default: /* Stub */ break; From 438f9a492a34a65855f7eeeb6851c052c79d86fe Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 10:27:13 +0000 Subject: [PATCH 099/112] move some things into constexpr constants --- include/dpp/discordclient.h | 14 ++++-- include/dpp/restrequest.h | 1 - src/dpp/cluster.cpp | 5 +-- src/dpp/discordclient.cpp | 85 ++++++++++++++++++++----------------- 4 files changed, 59 insertions(+), 46 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 309119f197..1906a6397b 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -37,17 +37,17 @@ #include #include +namespace dpp { + /** * @brief Discord API version for shard websockets and HTTPS API requests */ -#define DISCORD_API_VERSION "10" +#define DISCORD_API_VERSION "10" /** * @brief HTTPS Request base path for API calls */ -#define API_PATH "/api/v" DISCORD_API_VERSION - -namespace dpp { +#define API_PATH "/api/v" DISCORD_API_VERSION /* Forward declarations */ class cluster; @@ -64,6 +64,12 @@ class zlibcontext; */ constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; +/** + * @brief How many seconds to wait between (re)connections. DO NOT change this. + * It is mandated by the Discord API spec! + */ +constexpr time_t RECONNECT_INTERVAL = 5; + /** * @brief Represents different event opcodes sent and received on a shard websocket * diff --git a/include/dpp/restrequest.h b/include/dpp/restrequest.h index 2fcff85a77..2dcb4f56cf 100644 --- a/include/dpp/restrequest.h +++ b/include/dpp/restrequest.h @@ -293,5 +293,4 @@ template inline void rest_request_vector(dpp::cluster* c, const char* b }); } - } diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 05665cf6db..dec942781d 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -199,8 +199,8 @@ dpp::utility::uptime cluster::uptime() } void cluster::add_reconnect(uint32_t shard_id) { - reconnections[shard_id] = time(nullptr) + 5; - log(ll_trace, "Reconnecting shard " + std::to_string(shard_id) + " in 5 seconds..."); + reconnections[shard_id] = time(nullptr) + RECONNECT_INTERVAL; + log(ll_trace, "Reconnecting shard " + std::to_string(shard_id) + " in " + std::to_string(RECONNECT_INTERVAL) + " seconds..."); } void cluster::start(start_type return_after) { @@ -208,7 +208,6 @@ void cluster::start(start_type return_after) { auto event_loop = [this]() -> void { auto reconnect_monitor = start_timer([this](auto t) { time_t now = time(nullptr); - log(ll_trace, "Ticking reconnect monitor with " + std::to_string(reconnections.size()) + " queued reconnections"); for (auto reconnect = reconnections.begin(); reconnect != reconnections.end(); ++reconnect) { auto shard_id = reconnect->first; auto shard_reconnect_time = reconnect->second; diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index d06a5ba2d1..26752b25ac 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -30,10 +30,10 @@ #include #include -#define PATH_UNCOMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json" -#define PATH_COMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json&compress=zlib-stream" -#define PATH_UNCOMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf" -#define PATH_COMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf&compress=zlib-stream" +#define PATH_UNCOMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json" +#define PATH_COMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json&compress=zlib-stream" +#define PATH_UNCOMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf" +#define PATH_COMPRESSED_ETF "/?v=" DISCORD_API_VERSION "&encoding=etf&compress=zlib-stream" #define STRINGIFY(a) STRINGIFY_(a) #define STRINGIFY_(a) #a @@ -43,6 +43,11 @@ namespace dpp { +/** + * @brief Used in IDENTIFY to indicate what a large guild is + */ +constexpr int LARGE_THRESHOLD = 250; + /** * @brief This is an opaque class containing zlib library specific structures. * We define it this way so that the public facing D++ library doesn't require @@ -199,7 +204,8 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) && (uint8_t)buffer[buffer.size() - 1] == 0xFF) { /* Decompress buffer */ decompressed.clear(); - zlib->d_stream.next_in = (Bytef*)buffer.data(); + /* This is safe; zlib requires us to cast away the const. The underlying buffer is unchanged. */ + zlib->d_stream.next_in = reinterpret_cast(const_cast(buffer.data())); zlib->d_stream.avail_in = static_cast(buffer.size()); do { zlib->d_stream.next_out = static_cast(decomp_buffer.data()); @@ -281,29 +287,32 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) switch (op) { case ft_invalid_session: - /* Reset session state and fall through to 10 */ + /* Reset session state and fall through to ft_hello */ op = ft_hello; log(dpp::ll_debug, "Failed to resume session " + sessionid + ", will reidentify"); this->sessionid.clear(); this->last_seq = 0; - /* No break here, falls through to state 10 to cause a reidentify */ + /* No break here, falls through to state ft_hello to cause a re-identify */ [[fallthrough]]; - case ft_hello: + case ft_hello: { /* Need to check carefully for the existence of this before we try to access it! */ - if (j.find("d") != j.end() && j["d"].find("heartbeat_interval") != j["d"].end() && !j["d"]["heartbeat_interval"].is_null()) { - this->heartbeat_interval = j["d"]["heartbeat_interval"].get(); + auto d = j.find("d"); + if (d != j.end()) { + auto heartbeat = d->find("heartbeat_interval"); + if (heartbeat != d->end() && !heartbeat->is_null()) + this->heartbeat_interval = heartbeat->get(); } - if (last_seq && !sessionid.empty()) { + if (last_seq != 0U && !sessionid.empty()) { /* Resume */ log(dpp::ll_debug, "Resuming session " + sessionid + " with seq=" + std::to_string(last_seq)); json obj = { - { "op", ft_resume }, - { "d", { - {"token", this->token }, - {"session_id", this->sessionid }, - {"seq", this->last_seq } - } + {"op", ft_resume}, + {"d", { + {"token", this->token}, + {"session_id", this->sessionid}, + {"seq", this->last_seq} + } } }; this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); @@ -313,39 +322,40 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) auto connect_now = [this]() { log(dpp::ll_debug, "Connecting new session..."); json obj = { - { "op", ft_identify }, - { "d", - { - { "token", this->token }, - { "properties", - { - { "os", STRINGIFY(DPP_OS) }, - { "browser", "D++" }, - { "device", "D++" } - } - }, - { "shard", json::array({ shard_id, max_shards }) }, - { "compress", false }, - { "large_threshold", 250 }, - { "intents", this->intents } - } + {"op", ft_identify}, + {"d", + { + {"token", this->token}, + {"properties", + { + {"os", STRINGIFY(DPP_OS)}, + {"browser", "D++"}, + {"device", "D++"} + } + }, + {"shard", json::array({shard_id, max_shards})}, + {"compress", false}, + {"large_threshold", LARGE_THRESHOLD}, + {"intents", this->intents} + } } }; this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); this->connect_time = creator->last_identify = time(nullptr); reconnects++; }; - if (time(nullptr) < creator->last_identify + 5) { + if (time(nullptr) < creator->last_identify + RECONNECT_INTERVAL) { owner->start_timer([this, connect_now](timer h) { owner->stop_timer(h); connect_now(); - }, (creator->last_identify + 5) - time(nullptr)); + }, (creator->last_identify + RECONNECT_INTERVAL) - time(nullptr)); } else { connect_now(); } } this->last_heartbeat_ack = time(nullptr); websocket_ping = 0; + } break; case ft_dispatch: { std::string event = j["t"]; @@ -354,8 +364,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) break; case ft_reconnect: message_queue.clear(); - throw dpp::connection_exception("Reconnection requested, closing socket " + sessionid); - break; + throw dpp::connection_exception("Reconnection requested, closing session " + sessionid); /* Heartbeat ack */ case ft_heartbeat_ack: this->last_heartbeat_ack = time(nullptr); @@ -376,7 +385,7 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) dpp::utility::uptime discord_client::get_uptime() { - return dpp::utility::uptime(time(nullptr) - connect_time); + return {time(nullptr) - connect_time}; } bool discord_client::is_connected() From 3119752cde6658c7551a6b7b98640f916b26c679 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 11:34:00 +0000 Subject: [PATCH 100/112] refactor: make overridden sslclient stuff have override keyword, tidy up zlib stuff --- include/dpp/discordclient.h | 25 ++++---------- include/dpp/discordvoiceclient.h | 12 +++---- include/dpp/httpsclient.h | 26 +++++---------- include/dpp/wsclient.h | 8 ++--- src/dpp/discordclient.cpp | 57 +++++++++++++------------------- 5 files changed, 48 insertions(+), 80 deletions(-) diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 1906a6397b..3ca87dd8b1 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -328,7 +328,7 @@ class DPP_EXPORT discord_client : public websocket_client * are wrapped within this opaque object so that this header * file does not bring in a dependency on zlib.h. */ - zlibcontext* zlib; + std::unique_ptr zlib{}; /** * @brief Total decompressed received bytes @@ -360,17 +360,6 @@ class DPP_EXPORT discord_client : public websocket_client */ std::string jsonobj_to_string(const nlohmann::json& json); - /** - * @brief Initialise ZLib (websocket compression) - * @throw dpp::exception if ZLib cannot be initialised - */ - void setup_zlib(); - - /** - * @brief Shut down ZLib (websocket compression) - */ - void end_zlib(); - /** * @brief Update the websocket hostname with the resume url * from the last READY event @@ -481,7 +470,7 @@ class DPP_EXPORT discord_client : public websocket_client * @param severity The log level from dpp::loglevel * @param msg The log message to output */ - virtual void log(dpp::loglevel severity, const std::string &msg) const; + virtual void log(dpp::loglevel severity, const std::string &msg) const override; /** * @brief Handle an event (opcode 0) @@ -516,7 +505,7 @@ class DPP_EXPORT discord_client : public websocket_client * @brief Fires every second from the underlying socket I/O loop, used for sending heartbeats * and any queued outbound websocket frames. */ - virtual void one_second_timer(); + virtual void one_second_timer() override; /** * @brief Queue a message to be sent via the websocket @@ -584,7 +573,7 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief Destroy the discord client object */ - virtual ~discord_client(); + virtual ~discord_client() override; /** * @brief Get the decompressed bytes in objectGet decompressed total bytes received @@ -598,13 +587,13 @@ class DPP_EXPORT discord_client : public websocket_client * @param opcode The type of frame, e.g. text or binary * @returns True if a frame has been handled */ - virtual bool handle_frame(const std::string &buffer, ws_opcode opcode); + virtual bool handle_frame(const std::string &buffer, ws_opcode opcode) override; /** * @brief Handle a websocket error. * @param errorcode The error returned from the websocket */ - virtual void error(uint32_t errorcode); + virtual void error(uint32_t errorcode) override; /** * @brief Start and monitor I/O loop. @@ -614,7 +603,7 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief Called when the HTTP socket is closed */ - virtual void on_disconnect(); + virtual void on_disconnect() override; /** * @brief Connect to a voice channel diff --git a/include/dpp/discordvoiceclient.h b/include/dpp/discordvoiceclient.h index 3dd0e64a87..2fefa11524 100644 --- a/include/dpp/discordvoiceclient.h +++ b/include/dpp/discordvoiceclient.h @@ -833,13 +833,13 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @param severity The log level from dpp::loglevel * @param msg The log message to output */ - virtual void log(dpp::loglevel severity, const std::string &msg) const; + virtual void log(dpp::loglevel severity, const std::string &msg) const override; /** * @brief Fires every second from the underlying socket I/O loop, used for sending heartbeats * @throw dpp::exception if the socket needs to disconnect */ - virtual void one_second_timer(); + virtual void one_second_timer() override; /** * @brief voice client is ready to stream audio. @@ -886,7 +886,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Destroy the discord voice client object */ - virtual ~discord_voice_client(); + virtual ~discord_voice_client() override; /** * @brief Handle JSON from the websocket. @@ -895,13 +895,13 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @return bool True if a frame has been handled * @throw dpp::exception If there was an error processing the frame, or connection to UDP socket failed */ - virtual bool handle_frame(const std::string &buffer, ws_opcode opcode); + virtual bool handle_frame(const std::string &buffer, ws_opcode opcode) override; /** * @brief Handle a websocket error. * @param errorcode The error returned from the websocket */ - virtual void error(uint32_t errorcode); + virtual void error(uint32_t errorcode) override; /** * @brief Start and monitor websocket I/O @@ -1260,7 +1260,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Called on websocket disconnection */ - void on_disconnect(); + void on_disconnect() override; }; } diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h index 46e3b73901..a347fc19c4 100644 --- a/include/dpp/httpsclient.h +++ b/include/dpp/httpsclient.h @@ -33,9 +33,7 @@ namespace dpp { static inline const std::string http_version = "DiscordBot (https://github.com/brainboxdotcc/DPP, " - + to_hex(DPP_VERSION_MAJOR, false) + "." - + to_hex(DPP_VERSION_MINOR, false) + "." - + to_hex(DPP_VERSION_PATCH, false) + ")"; + + to_hex(DPP_VERSION_MAJOR, false) + "." + to_hex(DPP_VERSION_MINOR, false) + "." + to_hex(DPP_VERSION_PATCH, false) + ")"; static inline constexpr const char* DISCORD_HOST = "https://discord.com"; @@ -111,7 +109,7 @@ struct http_connect_info { /** * @brief True if the connection should be SSL */ - bool is_ssl; + bool is_ssl{}; /** * @brief The request scheme, e.g. 'https' or 'http' @@ -127,7 +125,7 @@ struct http_connect_info { * @brief The port number, either determined from the scheme, * or from the part of the hostname after a colon ":" character */ - uint16_t port; + uint16_t port{}; }; using https_client_completion_event = std::function; @@ -207,19 +205,11 @@ class DPP_EXPORT https_client : public ssl_client { */ std::multimap response_headers; - /** - * @brief Handle input buffer - * - * @param buffer Buffer to read - * @return returns true if the connection should remain open - */ - bool do_buffer(std::string& buffer); - protected: /** * @brief Start the connection */ - virtual void connect(); + virtual void connect() override; /** * @brief Get request state @@ -270,7 +260,7 @@ class DPP_EXPORT https_client : public ssl_client { /** * @brief Destroy the https client object */ - virtual ~https_client(); + virtual ~https_client() override; /** * @brief Build a multipart content from a set of files and some json @@ -288,17 +278,17 @@ class DPP_EXPORT https_client : public ssl_client { * * @param buffer The buffer contents. Can modify this value removing the head elements when processed. */ - virtual bool handle_buffer(std::string &buffer); + virtual bool handle_buffer(std::string &buffer) override; /** * @brief Close HTTPS socket */ - virtual void close(); + virtual void close() override; /** * @brief Fires every second from the underlying socket I/O loop, used for timeouts */ - virtual void one_second_timer(); + virtual void one_second_timer() override; /** * @brief Get a HTTP response header diff --git a/include/dpp/wsclient.h b/include/dpp/wsclient.h index 11251f2a1c..e7fc875d0a 100644 --- a/include/dpp/wsclient.h +++ b/include/dpp/wsclient.h @@ -155,7 +155,7 @@ class DPP_EXPORT websocket_client : public ssl_client { /** * @brief Connect to websocket server */ - virtual void connect(); + virtual void connect() override; /** * @brief Get websocket state @@ -207,12 +207,12 @@ class DPP_EXPORT websocket_client : public ssl_client { * @brief Processes incoming frames from the SSL socket input buffer. * @param buffer The buffer contents. Can modify this value removing the head elements when processed. */ - virtual bool handle_buffer(std::string& buffer); + virtual bool handle_buffer(std::string& buffer) override; /** * @brief Close websocket */ - virtual void close(); + virtual void close() override; /** * @brief Receives raw frame content only without headers @@ -233,7 +233,7 @@ class DPP_EXPORT websocket_client : public ssl_client { /** * @brief Fires every second from the underlying socket I/O loop, used for sending websocket pings */ - virtual void one_second_timer(); + virtual void one_second_timer() override; /** * @brief Send OP_CLOSE error code 1000 to the other side of the connection. diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 26752b25ac..9c13fde470 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -58,7 +58,24 @@ class zlibcontext { /** * @brief Zlib stream struct */ - z_stream d_stream; + z_stream d_stream{}; + + /** + * @brief Initialise zlib struct via inflateInit() + */ + zlibcontext() { + int error = inflateInit(&d_stream); + if (error != Z_OK) { + throw dpp::connection_exception((exception_error_code)error, "Can't initialise stream compression!"); + } + } + + /** + * @brief Destroy zlib struct via inflateEnd() + */ + ~zlibcontext() { + inflateEnd(&d_stream); + } }; @@ -90,7 +107,6 @@ discord_client::discord_client(discord_client &old, uint64_t sequence, const std protocol(old.protocol), resume_gateway_url(old.resume_gateway_url) { - etf = std::make_unique(etf_parser()); start_connecting(); } @@ -118,11 +134,15 @@ discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint3 protocol(ws_proto), resume_gateway_url(_cluster->default_gateway) { - etf = std::make_unique(etf_parser()); start_connecting(); } void discord_client::start_connecting() { + etf = std::make_unique(etf_parser()); + if (compressed) { + zlib = std::make_unique(); + decomp_buffer.resize(DECOMP_BUFFER_SIZE); + } websocket_client::connect(); } @@ -132,7 +152,6 @@ void discord_client::cleanup() discord_client::~discord_client() { - end_zlib(); } void discord_client::on_disconnect() @@ -151,35 +170,6 @@ uint64_t discord_client::get_decompressed_bytes_in() return decompressed_total; } -void discord_client::setup_zlib() -{ - std::lock_guard lock(zlib_mutex); - if (compressed) { - if (zlib == nullptr) { - zlib = new zlibcontext(); - } - zlib->d_stream.zalloc = (alloc_func)0; - zlib->d_stream.zfree = (free_func)0; - zlib->d_stream.opaque = (voidpf)0; - int error = inflateInit(&(zlib->d_stream)); - if (error != Z_OK) { - throw dpp::connection_exception((exception_error_code)error, "Can't initialise stream compression!"); - } - decomp_buffer.resize(DECOMP_BUFFER_SIZE); - } - -} - -void discord_client::end_zlib() -{ - std::lock_guard lock(zlib_mutex); - if (compressed && zlib) { - inflateEnd(&(zlib->d_stream)); - } - delete zlib; - zlib = nullptr; -} - void discord_client::set_resume_hostname() { hostname = resume_gateway_url; @@ -187,7 +177,6 @@ void discord_client::set_resume_hostname() void discord_client::run() { - setup_zlib(); ready = false; message_queue.clear(); ssl_client::read_loop(); From 31594f1f2e1c30733558a20ab35e415378bb2785 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 12:44:19 +0000 Subject: [PATCH 101/112] split out zlib stuff to its own file --- include/dpp/discordclient.h | 35 +++------------ include/dpp/zlibcontext.h | 85 ++++++++++++++++++++++++++++++++++++ src/dpp/discordclient.cpp | 87 ++++--------------------------------- src/dpp/zlibcontext.cpp | 73 +++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 107 deletions(-) create mode 100644 include/dpp/zlibcontext.h create mode 100644 src/dpp/zlibcontext.cpp diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 3ca87dd8b1..9e0222466a 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -36,6 +36,7 @@ #include #include #include +#include namespace dpp { @@ -52,18 +53,6 @@ namespace dpp { /* Forward declarations */ class cluster; -/** - * @brief This is an opaque class containing zlib library specific structures. - * We define it this way so that the public facing D++ library doesn't require - * the zlib headers be available to build against it. - */ -class zlibcontext; - -/** - * @brief Size of decompression buffer for zlib compressed traffic - */ -constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; - /** * @brief How many seconds to wait between (re)connections. DO NOT change this. * It is mandated by the Discord API spec! @@ -308,15 +297,6 @@ class DPP_EXPORT discord_client : public websocket_client */ bool compressed; - /** - * @brief ZLib decompression buffer - * - * If compression is not in use, this remains set to - * a vector of length zero, but when compression is - * enabled it will be resized to a DECOMP_BUFFER_SIZE buffer. - */ - std::vector decomp_buffer; - /** * @brief Decompressed string */ @@ -330,11 +310,6 @@ class DPP_EXPORT discord_client : public websocket_client */ std::unique_ptr zlib{}; - /** - * @brief Total decompressed received bytes - */ - uint64_t decompressed_total; - /** * @brief Last connect time of cluster */ @@ -573,11 +548,13 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief Destroy the discord client object */ - virtual ~discord_client() override; + virtual ~discord_client() = default; /** - * @brief Get the decompressed bytes in objectGet decompressed total bytes received - * @return uint64_t bytes received + * @brief Get decompressed total bytes received + * + * This will always return 0 if the connection is not compressed + * @return uint64_t compressed bytes received */ uint64_t get_decompressed_bytes_in(); diff --git a/include/dpp/zlibcontext.h b/include/dpp/zlibcontext.h new file mode 100644 index 0000000000..fa8c6092f7 --- /dev/null +++ b/include/dpp/zlibcontext.h @@ -0,0 +1,85 @@ +/************************************************************************************ + * + * 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 + +/** + * @brief Forward declaration for zlib stream type + */ +typedef struct z_stream_s z_stream; + +namespace dpp { + +/** + * @brief Size of decompression buffer for zlib compressed traffic + */ +constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; + +/** + * @brief This is an opaque class containing zlib library specific structures. + * This wraps the C pointers needed for zlib with unique_ptr and gives us a nice + * buffer abstraction so we don't need to wrestle with raw pointers. + */ +class zlibcontext { +public: + /** + * @brief Zlib stream struct. The actual type is defined in zlib.h + * so is only defined in the implementation file. + */ + std::unique_ptr d_stream{}; + + /** + * @brief ZLib decompression buffer. + * This is automatically set to DECOMP_BUFFER_SIZE bytes when + * the class is constructed. + */ + std::vector decomp_buffer{}; + + /** + * @brief Total decompressed received bytes counter + */ + uint64_t decompressed_total{}; + + /** + * @brief Initialise zlib struct via inflateInit() + * and size the buffer + */ + zlibcontext(); + + /** + * @brief Destroy zlib struct via inflateEnd() + */ + ~zlibcontext(); + + /** + * @brief Decompress zlib deflated buffer + * @param buffer input compressed stream + * @param decompressed output decompressed content + * @return an error code on error, or err_no_code_specified (0) on success + */ + exception_error_code decompress(const std::string& buffer, std::string& decompressed); +}; + +} \ No newline at end of file diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 9c13fde470..75f8686c6c 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -3,7 +3,7 @@ * D++, A Lightweight C++ library for Discord * * SPDX-License-Identifier: Apache-2.0 - * Copyright 2021 Craig Edwards and D++ contributors +#include * Copyright 2021 Craig Edwards and D++ contributors * (https://github.com/brainboxdotcc/DPP/graphs/contributors) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,6 @@ #include #include #include -#include #define PATH_UNCOMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json" #define PATH_COMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json&compress=zlib-stream" @@ -48,45 +47,12 @@ namespace dpp { */ constexpr int LARGE_THRESHOLD = 250; -/** - * @brief This is an opaque class containing zlib library specific structures. - * We define it this way so that the public facing D++ library doesn't require - * the zlib headers be available to build against it. - */ -class zlibcontext { -public: - /** - * @brief Zlib stream struct - */ - z_stream d_stream{}; - - /** - * @brief Initialise zlib struct via inflateInit() - */ - zlibcontext() { - int error = inflateInit(&d_stream); - if (error != Z_OK) { - throw dpp::connection_exception((exception_error_code)error, "Can't initialise stream compression!"); - } - } - - /** - * @brief Destroy zlib struct via inflateEnd() - */ - ~zlibcontext() { - inflateEnd(&d_stream); - } -}; - - /** * @brief Resume constructor for websocket client */ discord_client::discord_client(discord_client &old, uint64_t sequence, const std::string& session_id) : websocket_client(old.owner, old.resume_gateway_url, "443", old.compressed ? (old.protocol == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (old.protocol == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), compressed(old.compressed), - zlib(nullptr), - decompressed_total(old.decompressed_total), connect_time(0), ping_start(0.0), etf(nullptr), @@ -113,8 +79,6 @@ discord_client::discord_client(discord_client &old, uint64_t sequence, const std discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), compressed(comp), - zlib(nullptr), - decompressed_total(0), connect_time(0), ping_start(0.0), etf(nullptr), @@ -138,10 +102,9 @@ discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint3 } void discord_client::start_connecting() { - etf = std::make_unique(etf_parser()); + etf = std::make_unique(); if (compressed) { zlib = std::make_unique(); - decomp_buffer.resize(DECOMP_BUFFER_SIZE); } websocket_client::connect(); } @@ -150,10 +113,6 @@ void discord_client::cleanup() { } -discord_client::~discord_client() -{ -} - void discord_client::on_disconnect() { log(ll_trace, "discord_client::on_disconnect()"); @@ -167,7 +126,7 @@ void discord_client::on_disconnect() uint64_t discord_client::get_decompressed_bytes_in() { - return decompressed_total; + return zlib ? zlib->decompressed_total : 0; } void discord_client::set_resume_hostname() @@ -191,40 +150,12 @@ bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) /* Check that we have a complete compressed frame */ if ((uint8_t)buffer[buffer.size() - 4] == 0x00 && (uint8_t)buffer[buffer.size() - 3] == 0x00 && (uint8_t)buffer[buffer.size() - 2] == 0xFF && (uint8_t)buffer[buffer.size() - 1] == 0xFF) { - /* Decompress buffer */ - decompressed.clear(); - /* This is safe; zlib requires us to cast away the const. The underlying buffer is unchanged. */ - zlib->d_stream.next_in = reinterpret_cast(const_cast(buffer.data())); - zlib->d_stream.avail_in = static_cast(buffer.size()); - do { - zlib->d_stream.next_out = static_cast(decomp_buffer.data()); - zlib->d_stream.avail_out = DECOMP_BUFFER_SIZE; - int ret = inflate(&(zlib->d_stream), Z_NO_FLUSH); - size_t have = DECOMP_BUFFER_SIZE - zlib->d_stream.avail_out; - switch (ret) - { - case Z_NEED_DICT: - case Z_STREAM_ERROR: - this->error(err_compression_stream); - this->close(); - return false; - case Z_DATA_ERROR: - this->error(err_compression_data); - this->close(); - return false; - case Z_MEM_ERROR: - this->error(err_compression_memory); - this->close(); - return false; - case Z_OK: - this->decompressed.append(decomp_buffer.begin(), decomp_buffer.begin() + have); - this->decompressed_total += have; - break; - default: - /* Stub */ - break; - } - } while (zlib->d_stream.avail_out == 0); + auto result = zlib->decompress(buffer, decompressed); + if (result != err_no_code_specified) { + this->error(result); + this->close(); + return false; + } data = decompressed; } else { /* No complete compressed frame yet */ diff --git a/src/dpp/zlibcontext.cpp b/src/dpp/zlibcontext.cpp new file mode 100644 index 0000000000..6631c6b223 --- /dev/null +++ b/src/dpp/zlibcontext.cpp @@ -0,0 +1,73 @@ +/************************************************************************************ + * + * 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. + * + ************************************************************************************/ +#include +#include +#include +#include + +namespace dpp { + +zlibcontext::zlibcontext() { + d_stream = std::make_unique(); + std::memset(d_stream.get(), 0, sizeof(z_stream)); + int error = inflateInit(d_stream.get()); + if (error != Z_OK) { + throw dpp::connection_exception((exception_error_code)error, "Can't initialise stream compression!"); + } + decomp_buffer.resize(DECOMP_BUFFER_SIZE); +} + +zlibcontext::~zlibcontext() { + inflateEnd(d_stream.get()); +} + +exception_error_code zlibcontext::decompress(const std::string& buffer, std::string& decompressed) { + decompressed.clear(); + /* This is safe; zlib requires us to cast away the const. The underlying buffer is unchanged. */ + d_stream->next_in = reinterpret_cast(const_cast(buffer.data())); + d_stream->avail_in = static_cast(buffer.size()); + do { + d_stream->next_out = static_cast(decomp_buffer.data()); + d_stream->avail_out = DECOMP_BUFFER_SIZE; + int ret = inflate(d_stream.get(), Z_NO_FLUSH); + size_t have = DECOMP_BUFFER_SIZE - d_stream->avail_out; + switch (ret) + { + case Z_NEED_DICT: + case Z_STREAM_ERROR: + return err_compression_stream; + case Z_DATA_ERROR: + return err_compression_data; + case Z_MEM_ERROR: + return err_compression_memory; + case Z_OK: + decompressed.append(decomp_buffer.begin(), decomp_buffer.begin() + have); + decompressed_total += have; + break; + default: + /* Stub */ + break; + } + } while (d_stream->avail_out == 0); + return err_no_code_specified; +} + +}; \ No newline at end of file From adb25d1cca54c18e69d68de6a2dce323ade1572c Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 12:57:14 +0000 Subject: [PATCH 102/112] refactor: remove raw pointers from dns code --- include/dpp/dns.h | 3 ++- src/dpp/dns.cpp | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/dpp/dns.h b/include/dpp/dns.h index b1a6814065..3d77db5282 100644 --- a/include/dpp/dns.h +++ b/include/dpp/dns.h @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace dpp { @@ -83,7 +84,7 @@ namespace dpp { /** * @brief Cache container type */ - using dns_cache_t = std::unordered_map; + using dns_cache_t = std::unordered_map>; /** * @brief Resolve a hostname to an addrinfo diff --git a/src/dpp/dns.cpp b/src/dpp/dns.cpp index a927d68747..df1d733624 100644 --- a/src/dpp/dns.cpp +++ b/src/dpp/dns.cpp @@ -70,7 +70,7 @@ const dns_cache_entry *resolve_hostname(const std::string &hostname, const std:: exists = true; if (now < iter->second->expire_timestamp) { /* there is a cached entry that is still valid, return it */ - return iter->second; + return iter->second.get(); } } } @@ -82,7 +82,6 @@ const dns_cache_entry *resolve_hostname(const std::string &hostname, const std:: std::unique_lock dns_cache_lock(dns_cache_mutex); iter = dns_cache.find(hostname); if (iter != dns_cache.end()) { /* re-validate iter */ - delete iter->second; dns_cache.erase(iter); } } @@ -108,7 +107,7 @@ const dns_cache_entry *resolve_hostname(const std::string &hostname, const std:: { /* Update cache, requires unique lock */ std::unique_lock dns_cache_lock(dns_cache_mutex); - dns_cache_entry* cache_entry = new dns_cache_entry(); + auto cache_entry = std::make_unique(); for (struct addrinfo* rp = addrs; rp != nullptr; rp = rp->ai_next) { /* Discord only support ipv4, so iterate over any ipv6 results */ @@ -127,11 +126,13 @@ const dns_cache_entry *resolve_hostname(const std::string &hostname, const std:: } cache_entry->expire_timestamp = now + one_hour; - dns_cache[hostname] = cache_entry; + auto r = dns_cache.emplace(hostname, std::move(cache_entry)); /* Now we're done with this horrible struct, free it and return */ freeaddrinfo(addrs); - return cache_entry; + + /* Return either the existing entry, or the newly inserted entry */ + return r.first->second.get(); } } From 686344a089ec1825bfbed008e65841ca55e53b59 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 13:01:22 +0000 Subject: [PATCH 103/112] fix: unique_ptr on g++-8 needs to know sizeof() z_stream up front, so we must use a raw ptr :( --- include/dpp/zlibcontext.h | 2 +- src/dpp/zlibcontext.cpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/dpp/zlibcontext.h b/include/dpp/zlibcontext.h index fa8c6092f7..a1a7f48350 100644 --- a/include/dpp/zlibcontext.h +++ b/include/dpp/zlibcontext.h @@ -48,7 +48,7 @@ class zlibcontext { * @brief Zlib stream struct. The actual type is defined in zlib.h * so is only defined in the implementation file. */ - std::unique_ptr d_stream{}; + z_stream* d_stream{}; /** * @brief ZLib decompression buffer. diff --git a/src/dpp/zlibcontext.cpp b/src/dpp/zlibcontext.cpp index 6631c6b223..b789dd9009 100644 --- a/src/dpp/zlibcontext.cpp +++ b/src/dpp/zlibcontext.cpp @@ -26,17 +26,19 @@ namespace dpp { zlibcontext::zlibcontext() { - d_stream = std::make_unique(); - std::memset(d_stream.get(), 0, sizeof(z_stream)); - int error = inflateInit(d_stream.get()); + d_stream = new z_stream(); + std::memset(d_stream, 0, sizeof(z_stream)); + int error = inflateInit(d_stream); if (error != Z_OK) { + delete d_stream; throw dpp::connection_exception((exception_error_code)error, "Can't initialise stream compression!"); } decomp_buffer.resize(DECOMP_BUFFER_SIZE); } zlibcontext::~zlibcontext() { - inflateEnd(d_stream.get()); + inflateEnd(d_stream); + delete d_stream; } exception_error_code zlibcontext::decompress(const std::string& buffer, std::string& decompressed) { @@ -47,7 +49,7 @@ exception_error_code zlibcontext::decompress(const std::string& buffer, std::str do { d_stream->next_out = static_cast(decomp_buffer.data()); d_stream->avail_out = DECOMP_BUFFER_SIZE; - int ret = inflate(d_stream.get(), Z_NO_FLUSH); + int ret = inflate(d_stream, Z_NO_FLUSH); size_t have = DECOMP_BUFFER_SIZE - d_stream->avail_out; switch (ret) { From 1cd4de9e687af64ee988ef85b6ff4f1024c9db97 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 15:44:03 +0000 Subject: [PATCH 104/112] docs: new webhook example --- .clang-tidy | 2 +- docpages/example_code/webhooks.cpp | 18 +- .../example_programs/the_basics/webhooks.md | 7 +- include/dpp/cluster.h | 20 ++ include/dpp/queues.h | 2 + src/dpp/cluster.cpp | 182 ++++++++++-------- src/dpp/discordclient.cpp | 2 + src/dpp/queues.cpp | 15 +- src/dpp/zlibcontext.cpp | 6 +- 9 files changed, 156 insertions(+), 98 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 010c3e59f6..33014a7878 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,2 @@ # TODO: Discuss about -readability-identifier-length, -readability-avoid-const-params-in-decls -Checks: "-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,llvm-namespace-comment,modernize-*,performance-*,portability-*,readability-*,-bugprone-implicit-widening-of-multiplication-result, -bugprone-easily-swappable-parameters,-readability-identifier-length,-portability-restrict-system-includes,-modernize-use-trailing-return-type,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-avoid-const-params-in-decls,-cppcoreguidelines-owning-memory,-readability-function-cognitive-complexity" +Checks: "-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,llvm-namespace-comment,modernize-*,performance-*,portability-*,readability-*,-bugprone-implicit-widening-of-multiplication-result,-bugprone-easily-swappable-parameters,-readability-identifier-length,-portability-restrict-system-includes,-modernize-use-trailing-return-type,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-avoid-const-params-in-decls,-cppcoreguidelines-owning-memory,-readability-function-cognitive-complexity,-cppcoreguidelines-avoid-do-while" diff --git a/docpages/example_code/webhooks.cpp b/docpages/example_code/webhooks.cpp index ed9cbb840e..acecd7d5ae 100644 --- a/docpages/example_code/webhooks.cpp +++ b/docpages/example_code/webhooks.cpp @@ -1,16 +1,26 @@ #include +#include +#include int main() { - dpp::cluster bot(""); /* Normally, you put your bot token in here, but its not required for webhooks. */ + /* If you just want to fire webhooks, you can instantiate a cluster with no token */ + dpp::cluster bot; - bot.on_log(dpp::utility::cout_logger()); + /* Start the cluster in its own thread */ + bot.start(dpp::st_return); /* Construct a webhook object using the URL you got from Discord */ dpp::webhook wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE"); - /* Send a message with this webhook */ - bot.execute_webhook_sync(wh, dpp::message("Have a great time here :smile:")); + /* Send a message with this webhook asynchronously */ + bot.execute_webhook(wh, dpp::message("Have a great time here :smile:")); + /* Wait here for the webhook to complete, but we could do anything we need here */ + while (bot.active_requests() > 0) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + /* When we return here, the cluster will be terminated */ return 0; } diff --git a/docpages/example_programs/the_basics/webhooks.md b/docpages/example_programs/the_basics/webhooks.md index e121c2169e..abee1a63b1 100644 --- a/docpages/example_programs/the_basics/webhooks.md +++ b/docpages/example_programs/the_basics/webhooks.md @@ -1,9 +1,12 @@ \page webhooks Webhooks -Webhooks are a simple way to post messages from other apps and websites into Discord. They allow getting automated messages and data updates sent to a text channel in your server. [Read more](https://support.discord.com/hc/en-us/articles/228383668) in this article about Webhooks. +Webhooks are a simple way to post messages from other apps and websites into Discord. +They allow getting automated messages and data updates sent to a text channel in your server. [Read more](https://support.discord.com/hc/en-us/articles/228383668) in this article about Webhooks. The following code shows how to send messages in a channel using a webhook. \include{cpp} webhooks.cpp -The above is just a very simple example. You can also send embed messages. All you have to do is to add an embed to the message you want to send. If you want to, you can also send it into a thread. +\note For just sending a webhook, the example above is overkill. If you are here because you searched for 'how to send a Discord webhook in C++', you'll quickly learn that D++ can do much more than just send webhooks! The +above is just a very simple example. You can also send embed messages. All you have to do is to add an embed to the message you want to send. If you want to, you can also send it into a thread. For further examples, check +the rest of the site. diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 413b89fad7..a5b8b8a8ac 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -53,6 +53,8 @@ namespace dpp { +constexpr uint32_t NO_SHARDS = ~0U; + /** * @brief Types of startup for cluster::start() */ @@ -267,6 +269,16 @@ class DPP_EXPORT cluster { */ std::unique_ptr socketengine; + /** + * @brief Constructor for creating a cluster without a token. + * A cluster created without a token has no shards, and just runs the event loop. You can use this to make asynchronous + * HTTP requests via e.g. dpp::cluster::request without having to connect to a websocket to receive shard events. + * @param pool_threads The number of threads to allocate for the thread pool. This defaults to half your system concurrency and if set to a number less than 4, will default to 4. + * All callbacks and events are placed into the thread pool. The bigger you make this pool (but generally no bigger than your number of cores), the more your bot will scale. + * @throw dpp::exception Thrown on windows, if WinSock fails to initialise, or on any other system if a dpp::request_queue fails to construct + */ + cluster(uint32_t pool_threads = std::thread::hardware_concurrency() / 2); + /** * @brief Constructor for creating a cluster. All but the token are optional. * @param token The bot token to use for all HTTP commands and websocket connections @@ -524,6 +536,14 @@ class DPP_EXPORT cluster { */ bool register_command(const std::string& name, const slashcommand_handler_t handler); + /** + * @brief Get the number of currently active HTTP(S) requests active in the cluster. + * This total includes all in-flight API requests and calls to dpp::cluster::request(). + * Note that once a request is passed to the thread pool it is no longer counted here. + * @return Total active request count + */ + size_t active_requests(); + #ifdef DPP_CORO /** * @brief Register a coroutine-based slash command handler. diff --git a/include/dpp/queues.h b/include/dpp/queues.h index a58754e08c..bb12ee4509 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -642,6 +642,8 @@ class DPP_EXPORT request_queue { * @return true if globally rate limited */ bool is_globally_ratelimited() const; + + size_t get_active_request_count() const; }; } diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index dec942781d..d562af8837 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -84,6 +84,10 @@ template bool DPP_EXPORT validate_configuration(); template bool DPP_EXPORT validate_configuration(); +cluster::cluster(uint32_t pool_threads) : cluster("", 0, NO_SHARDS, 1, 1, false, cache_policy::cpol_none, pool_threads) +{ +} + cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, uint32_t _cluster_id, uint32_t _maxclusters, bool comp, cache_policy_t policy, uint32_t pool_threads) : default_gateway("gateway.discord.gg"), rest(nullptr), raw_rest(nullptr), compressed(comp), start_time(0), token(_token), last_identify(time(nullptr) - 5), intents(_intents), numshards(_shards), cluster_id(_cluster_id), maxclusters(_maxclusters), rest_ping(0.0), cache_policy(policy), ws_mode(ws_json) @@ -206,7 +210,7 @@ void cluster::add_reconnect(uint32_t shard_id) { void cluster::start(start_type return_after) { auto event_loop = [this]() -> void { - auto reconnect_monitor = start_timer([this](auto t) { + auto reconnect_monitor = numshards != NO_SHARDS ? start_timer([this](auto t) { time_t now = time(nullptr); for (auto reconnect = reconnections.begin(); reconnect != reconnections.end(); ++reconnect) { auto shard_id = reconnect->first; @@ -258,11 +262,13 @@ void cluster::start(start_type return_after) { log(ll_trace, "Shard " + std::to_string(shard_id) + " not ready to reconnect yet."); } } - }, 5); + }, 5) : 0; while (!this->terminating && socketengine.get()) { socketengine->process_events(); } - stop_timer(reconnect_monitor); + if (reconnect_monitor) { + stop_timer(reconnect_monitor); + } }; if (on_guild_member_add && !(intents & dpp::i_guild_members)) { @@ -281,100 +287,106 @@ void cluster::start(start_type return_after) { log(ll_warning, "You have attached an event to cluster::on_presence_update() but have not specified the privileged intent dpp::i_guild_presences. This event will not fire."); } - /* Start up all shards */ - get_gateway_bot([this, return_after](const auto& response) { + if (numshards != NO_SHARDS) { + /* Start up all shards */ + get_gateway_bot([this, return_after](const auto &response) { - auto throw_if_not_threaded = [this, return_after](exception_error_code error_id, const std::string& msg) { - log(ll_critical, msg); - if (return_after == st_wait) { - throw dpp::connection_exception(error_id, msg); - } - }; + auto throw_if_not_threaded = [this, return_after](exception_error_code error_id, const std::string &msg) { + log(ll_critical, msg); + if (return_after == st_wait) { + throw dpp::connection_exception(error_id, msg); + } + }; - if (response.is_error()) { - if (response.http_info.status == 401) { - throw_if_not_threaded(err_unauthorized, "Invalid bot token (401: Unauthorized when getting gateway shard count)"); - } else { - throw_if_not_threaded(err_auto_shard, "get_gateway_bot: " + response.http_info.body); - } - return; - } - auto g = std::get(response.value); - log(ll_debug, "Cluster: " + std::to_string(g.session_start_remaining) + " of " + std::to_string(g.session_start_total) + " session starts remaining"); - if (g.session_start_remaining < g.shards || g.shards == 0) { - throw_if_not_threaded(err_no_sessions_left, "Discord indicates you cannot start enough sessions to boot this cluster! Cluster startup aborted. Try again later."); - return; - } else if (g. session_start_max_concurrency == 0) { - throw_if_not_threaded(err_auto_shard, "Cluster: Could not determine concurrency, startup aborted!"); - return; - } else if (g.session_start_max_concurrency > 1) { - log(ll_debug, "Cluster: Large bot sharding; Using session concurrency: " + std::to_string(g.session_start_max_concurrency)); - } else if (numshards == 0) { - if (g.shards) { - log(ll_info, "Auto Shard: Bot requires " + std::to_string(g.shards) + std::string(" shard") + ((g.shards > 1) ? "s" : "")); - } else { - throw_if_not_threaded(err_auto_shard, "Auto Shard: Cannot determine number of shards. Cluster startup aborted. Check your connection."); + if (response.is_error()) { + if (response.http_info.status == 401) { + throw_if_not_threaded(err_unauthorized, "Invalid bot token (401: Unauthorized when getting gateway shard count)"); + } else { + throw_if_not_threaded(err_auto_shard, "get_gateway_bot: " + response.http_info.body); + } return; } - numshards = g.shards; - } - start_time = time(nullptr); - log(ll_debug, "Starting with " + std::to_string(numshards) + " shards..."); - - for (uint32_t s = 0; s < numshards; ++s) { - /* Filter out shards that aren't part of the current cluster, if the bot is clustered */ - if (s % maxclusters == cluster_id) { - /* Each discord_client is inserted into the socket engine when we call run() */ - try { - this->shards[s] = new discord_client(this, s, numshards, token, intents, compressed, ws_mode); - this->shards[s]->run(); - } - catch (const std::exception &e) { - throw_if_not_threaded(err_cant_start_shard, "Could not start shard " + std::to_string(s) + ": " + std::string(e.what())); + auto g = std::get(response.value); + log(ll_debug, "Cluster: " + std::to_string(g.session_start_remaining) + " of " + std::to_string(g.session_start_total) + " session starts remaining"); + if (g.session_start_remaining < g.shards || g.shards == 0) { + throw_if_not_threaded(err_no_sessions_left, "Discord indicates you cannot start enough sessions to boot this cluster! Cluster startup aborted. Try again later."); + return; + } else + if (g.session_start_max_concurrency == 0) { + throw_if_not_threaded(err_auto_shard, "Cluster: Could not determine concurrency, startup aborted!"); return; - } - /* Stagger the shard startups, pausing every 'session_start_max_concurrency' shards for 5 seconds. - * This means that for bots that don't have large bot sharding, any number % 1 is always 0, - * so it will pause after every shard. For any with non-zero concurrency it'll pause 5 seconds - * after every batch. - */ - if (((s + 1) % g.session_start_max_concurrency) == 0) { - size_t wait_time = 5; + } else if (g.session_start_max_concurrency > 1) { - /* If large bot sharding, be sure to give the batch of shards time to settle */ - bool all_connected = true; - do { - all_connected = true; - for (auto& shard : this->shards) { - if (!shard.second->ready) { - all_connected = false; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - break; - } + log(ll_debug, "Cluster: Large bot sharding; Using session concurrency: " + std::to_string(g.session_start_max_concurrency)); + } else + if (numshards == 0) { + if (g.shards) { + log(ll_info, "Auto Shard: Bot requires " + std::to_string(g.shards) + std::string(" shard") + ((g.shards > 1) ? "s" : "")); + } else { + throw_if_not_threaded(err_auto_shard, "Auto Shard: Cannot determine number of shards. Cluster startup aborted. Check your connection."); + return; } - } while (all_connected); + numshards = g.shards; + } + log(ll_debug, "Starting with " + std::to_string(numshards) + " shards..."); + start_time = time(nullptr); + + for (uint32_t s = 0; s < numshards; ++s) { + /* Filter out shards that aren't part of the current cluster, if the bot is clustered */ + if (s % maxclusters == cluster_id) { + /* Each discord_client is inserted into the socket engine when we call run() */ + try { + this->shards[s] = new discord_client(this, s, numshards, token, intents, compressed, ws_mode); + this->shards[s]->run(); + } + catch (const std::exception &e) { + throw_if_not_threaded(err_cant_start_shard, "Could not start shard " + std::to_string(s) + ": " + std::string(e.what())); + return; + } + /* Stagger the shard startups, pausing every 'session_start_max_concurrency' shards for 5 seconds. + * This means that for bots that don't have large bot sharding, any number % 1 is always 0, + * so it will pause after every shard. For any with non-zero concurrency it'll pause 5 seconds + * after every batch. + */ + if (((s + 1) % g.session_start_max_concurrency) == 0) { + size_t wait_time = 5; + if (g.session_start_max_concurrency > 1) { + /* If large bot sharding, be sure to give the batch of shards time to settle */ + bool all_connected = true; + do { + all_connected = true; + for (auto &shard: this->shards) { + if (!shard.second->ready) { + all_connected = false; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + break; + } + } + } while (all_connected); + } + std::this_thread::sleep_for(std::chrono::seconds(wait_time)); } - std::this_thread::sleep_for(std::chrono::seconds(wait_time)); } } - } - /* Get all active DM channels and map them to user id -> dm id */ - current_user_get_dms([this](const dpp::confirmation_callback_t& completion) { - if (completion.is_error()) { - log(dpp::ll_debug, "Failed to get bot DM list"); - return; - } - dpp::channel_map dmchannels = std::get(completion.value); - for (auto & c : dmchannels) { - for (auto & u : c.second.recipients) { - set_dm_channel(u, c.second.id); + /* Get all active DM channels and map them to user id -> dm id */ + current_user_get_dms([this](const dpp::confirmation_callback_t &completion) { + if (completion.is_error()) { + log(dpp::ll_debug, "Failed to get bot DM list"); + return; } - } + dpp::channel_map dmchannels = std::get(completion.value); + for (auto &c: dmchannels) { + for (auto &u: c.second.recipients) { + set_dm_channel(u, c.second.id); + } + } + }); + + log(ll_debug, "Shards started."); }); - log(ll_debug, "Shards started."); - }); + } if (return_after == st_return) { engine_thread = std::thread([event_loop]() { @@ -583,4 +595,8 @@ bool cluster::unregister_command(const std::string &name) { return named_commands.erase(name) == 1; } +size_t cluster::active_requests() { + return rest->get_active_request_count() + raw_rest->get_active_request_count(); +} + }; diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 75f8686c6c..070681985d 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -53,6 +53,7 @@ constexpr int LARGE_THRESHOLD = 250; discord_client::discord_client(discord_client &old, uint64_t sequence, const std::string& session_id) : websocket_client(old.owner, old.resume_gateway_url, "443", old.compressed ? (old.protocol == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (old.protocol == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), compressed(old.compressed), + zlib(nullptr), connect_time(0), ping_start(0.0), etf(nullptr), @@ -79,6 +80,7 @@ discord_client::discord_client(discord_client &old, uint64_t sequence, const std discord_client::discord_client(dpp::cluster* _cluster, uint32_t _shard_id, uint32_t _max_shards, const std::string &_token, uint32_t _intents, bool comp, websocket_protocol_t ws_proto) : websocket_client(_cluster, _cluster->default_gateway, "443", comp ? (ws_proto == ws_json ? PATH_COMPRESSED_JSON : PATH_COMPRESSED_ETF) : (ws_proto == ws_json ? PATH_UNCOMPRESSED_JSON : PATH_UNCOMPRESSED_ETF)), compressed(comp), + zlib(nullptr), connect_time(0), ping_start(0.0), etf(nullptr), diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index b4d737082b..07a016630b 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -456,17 +456,24 @@ inline uint32_t hash(const char *s) } /* Post a http_request into a request queue */ -request_queue& request_queue::post_request(std::unique_ptr req) -{ +request_queue& request_queue::post_request(std::unique_ptr req) { if (!terminating) { requests_in[hash(req->endpoint.c_str()) % in_queue_pool_size]->post_request(std::move(req)); } return *this; } -bool request_queue::is_globally_ratelimited() const -{ +bool request_queue::is_globally_ratelimited() const { return this->globally_ratelimited; } +size_t request_queue::get_active_request_count() const { + size_t total{}; + for (auto& pool : requests_in) { + std::scoped_lock lock(pool->in_mutex); + total += pool->requests_in.size(); + } + return total; +} + } diff --git a/src/dpp/zlibcontext.cpp b/src/dpp/zlibcontext.cpp index b789dd9009..7dee967276 100644 --- a/src/dpp/zlibcontext.cpp +++ b/src/dpp/zlibcontext.cpp @@ -25,8 +25,7 @@ namespace dpp { -zlibcontext::zlibcontext() { - d_stream = new z_stream(); +zlibcontext::zlibcontext() : d_stream(new z_stream()) { std::memset(d_stream, 0, sizeof(z_stream)); int error = inflateInit(d_stream); if (error != Z_OK) { @@ -51,8 +50,7 @@ exception_error_code zlibcontext::decompress(const std::string& buffer, std::str d_stream->avail_out = DECOMP_BUFFER_SIZE; int ret = inflate(d_stream, Z_NO_FLUSH); size_t have = DECOMP_BUFFER_SIZE - d_stream->avail_out; - switch (ret) - { + switch (ret) { case Z_NEED_DICT: case Z_STREAM_ERROR: return err_compression_stream; From 549abc80e8a4107ec6d961f8efab1044b1a26f46 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 16:04:03 +0000 Subject: [PATCH 105/112] refactor: remove dpp::sync --- .../classes/Generator/SyncGenerator.php | 131 - include/dpp/cluster.h | 3 +- include/dpp/cluster_sync_calls.h | 3466 ----------------- library/CMakeLists.txt | 6 - src/dpp/cluster.cpp | 27 +- src/dpp/cluster_sync_calls.cpp | 879 ----- 6 files changed, 11 insertions(+), 4501 deletions(-) delete mode 100644 buildtools/classes/Generator/SyncGenerator.php delete mode 100644 include/dpp/cluster_sync_calls.h delete mode 100644 src/dpp/cluster_sync_calls.cpp diff --git a/buildtools/classes/Generator/SyncGenerator.php b/buildtools/classes/Generator/SyncGenerator.php deleted file mode 100644 index a5c40319a9..0000000000 --- a/buildtools/classes/Generator/SyncGenerator.php +++ /dev/null @@ -1,131 +0,0 @@ -generateHeaderStart() . << -#include -#include - -namespace dpp { - - -EOT; - } - - /** - * @inheritDoc - */ - public function checkForChanges(): bool - { - /* Check if we need to re-generate by comparing modification times */ - $us = file_exists('include/dpp/cluster_sync_calls.h') ? filemtime('include/dpp/cluster_sync_calls.h') : 0; - $them = filemtime('include/dpp/cluster.h'); - if ($them <= $us) { - echo "-- No change required.\n"; - return false; - } - - echo "-- Autogenerating include/dpp/cluster_sync_calls.h\n"; - echo "-- Autogenerating src/dpp/cluster_sync_calls.cpp\n"; - return true; - } - - /** - * @inheritDoc - */ - public function generateHeaderDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string - { - return "DPP_DEPRECATED(\"Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html\") $returnType {$currentFunction}_sync($parameters);\n\n"; - } - - /** - * @inheritDoc - */ - public function generateCppDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string - { - return "$returnType cluster::{$currentFunction}_sync($noDefaults) {\n\treturn dpp::sync<$returnType>(this, static_cast(&cluster::$currentFunction)$parameterNames);\n}\n\n"; - } - - /** - * @inheritDoc - */ - public function getCommentArray(): array - { - return [ - " * \memberof dpp::cluster", - " * @throw dpp::rest_exception upon failure to execute REST function", - " * @deprecated This function is deprecated, please use coroutines instead.", - " * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread.", - " * Avoid direct use of this function inside an event handler.", - ]; - } - - /** - * @inheritDoc - */ - public function saveHeader(string $content): void - { - file_put_contents('include/dpp/cluster_sync_calls.h', $content); - } - - /** - * @inheritDoc - */ - public function saveCpp(string $cppcontent): void - { - file_put_contents('src/dpp/cluster_sync_calls.cpp', $cppcontent); - } -} \ No newline at end of file diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index a5b8b8a8ac..0c3a887f1e 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -4040,9 +4040,8 @@ class DPP_EXPORT cluster { */ void channel_set_voice_status(snowflake channel_id, const std::string& status, command_completion_event_t callback = utility::log_error()); -#include #ifdef DPP_CORO -#include + #include #endif }; diff --git a/include/dpp/cluster_sync_calls.h b/include/dpp/cluster_sync_calls.h deleted file mode 100644 index 461736b215..0000000000 --- a/include/dpp/cluster_sync_calls.h +++ /dev/null @@ -1,3466 +0,0 @@ -/************************************************************************************ - * - * D++, A Lightweight C++ library for Discord - * - * Copyright 2022 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. - * - ************************************************************************************/ - - -/* Auto @generated by buildtools/make_sync_struct.php. - * - * DO NOT EDIT BY HAND! - * - * To re-generate this header file re-run the script! - */ -/** - * @brief Create/overwrite global slash commands. - * Any existing global slash commands will be deleted and replaced with these. - * - * @see dpp::cluster::global_bulk_command_create - * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands - * @param commands Vector of slash commands to create/update. - * overwriting existing commands that are registered globally for this application. - * Commands that do not already exist will count toward daily application command create limits. - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_bulk_command_create_sync(const std::vector &commands); - -/** - * @brief Delete all existing global slash commands. - * - * @see dpp::cluster::global_bulk_command_delete - * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_bulk_command_delete_sync(); - -/** - * @brief Create a global slash command (a bot can have a maximum of 100 of these). - * - * @see dpp::cluster::global_command_create - * @see https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - * @param s Slash command to create - * @return slashcommand returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand global_command_create_sync(const slashcommand &s); - -/** - * @brief Get a global slash command - * - * @see dpp::cluster::global_command_get - * @see https://discord.com/developers/docs/interactions/application-commands#get-global-application-command - * @param id The ID of the slash command - * @return slashcommand returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand global_command_get_sync(snowflake id); - -/** - * @brief Delete a global slash command (a bot can have a maximum of 100 of these) - * - * @see dpp::cluster::global_command_delete - * @see https://discord.com/developers/docs/interactions/application-commands#delete-global-application-command - * @param id Slash command to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation global_command_delete_sync(snowflake id); - -/** - * @brief Edit a global slash command (a bot can have a maximum of 100 of these) - * - * @see dpp::cluster::global_command_edit - * @see https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command - * @param s Slash command to change - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation global_command_edit_sync(const slashcommand &s); - -/** - * @brief Get the application's global slash commands - * - * @see dpp::cluster::global_commands_get - * @see https://discord.com/developers/docs/interactions/application-commands#get-global-application-commands - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_commands_get_sync(); - -/** - * @brief Create/overwrite guild slash commands. - * Any existing guild slash commands on this guild will be deleted and replaced with these. - * - * @see dpp::cluster::guild_bulk_command_create - * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands - * @param commands Vector of slash commands to create/update. - * New guild commands will be available in the guild immediately. If the command did not already exist, it will count toward daily application command create limits. - * @param guild_id Guild ID to create/update the slash commands in - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_bulk_command_create_sync(const std::vector &commands, snowflake guild_id); - -/** - * @brief Delete all existing guild slash commands. - * - * @see dpp::cluster::guild_bulk_command_delete - * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands - * @param guild_id Guild ID to delete the slash commands in. - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_bulk_command_delete_sync(snowflake guild_id); - -/** - * @brief Get all slash command permissions of a guild - * - * @see dpp::cluster::guild_commands_get_permissions - * @see https://discord.com/developers/docs/interactions/application-commands#get-application-command-permissions - * @param guild_id Guild ID to get the slash commands permissions for - * @return guild_command_permissions_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions_map guild_commands_get_permissions_sync(snowflake guild_id); - -/** - * @brief Edit/Overwrite the permissions of all existing slash commands in a guild - * - * @note You can only add up to 10 permission overwrites for a command - * - * @see dpp::cluster::guild_bulk_command_edit_permissions - * @see https://discord.com/developers/docs/interactions/application-commands#batch-edit-application-command-permissions - * @warning The endpoint will overwrite all existing permissions for all commands of the application in a guild, including slash commands, user commands, and message commands. Meaning that if you forgot to pass a slash command, the permissions of it might be removed. - * @param commands A vector of slash commands to edit/overwrite the permissions for - * @param guild_id Guild ID to edit permissions of the slash commands in - * @return guild_command_permissions_map returned object on completion - * @deprecated This has been disabled with updates to Permissions v2. You can use guild_command_edit_permissions instead - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions_map guild_bulk_command_edit_permissions_sync(const std::vector &commands, snowflake guild_id); - -/** - * @brief Create a slash command local to a guild - * - * @see dpp::cluster::guild_command_create - * @see https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command - * @note Creating a command with the same name as an existing command for your application will overwrite the old command. - * @param s Slash command to create - * @param guild_id Guild ID to create the slash command in - * @return slashcommand returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand guild_command_create_sync(const slashcommand &s, snowflake guild_id); - -/** - * @brief Delete a slash command local to a guild - * - * @see dpp::cluster::guild_command_delete - * @see https://discord.com/developers/docs/interactions/application-commands#delete-guild-application-command - * @param id Slash command to delete - * @param guild_id Guild ID to delete the slash command in - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_delete_sync(snowflake id, snowflake guild_id); - -/** - * @brief Edit slash command permissions of a guild - * - * @see dpp::cluster::guild_command_edit_permissions - * @see https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions - * @note You can only add up to 10 permission overwrites for a command - * @param s Slash command to edit the permissions for - * @param guild_id Guild ID to edit the slash command in - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_edit_permissions_sync(const slashcommand &s, snowflake guild_id); - -/** - * @brief Get a slash command of a guild - * - * @see dpp::cluster::guild_command_get - * @see https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command - * @note The returned slash commands will not have permissions set, you need to use a permissions getter e.g. dpp::guild_commands_get_permissions to get the guild command permissions - * @param id The ID of the slash command - * @param guild_id Guild ID to get the slash command from - * @return slashcommand returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand guild_command_get_sync(snowflake id, snowflake guild_id); - -/** - * @brief Get the permissions for a slash command of a guild - * - * @see dpp::cluster::guild_command_get_permissions - * @see https://discord.com/developers/docs/interactions/application-commands#get-application-command-permissions - * @param id The ID of the slash command to get the permissions for - * @param guild_id Guild ID to get the permissions of - * @return guild_command_permissions returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions guild_command_get_permissions_sync(snowflake id, snowflake guild_id); - -/** - * @brief Edit a slash command local to a guild - * - * @see dpp::cluster::guild_command_edit - * @see https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command - * @param s Slash command to edit - * @param guild_id Guild ID to edit the slash command in - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_edit_sync(const slashcommand &s, snowflake guild_id); - -/** - * @brief Get the application's slash commands for a guild - * - * @see dpp::cluster::guild_commands_get - * @see https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands - * @note The returned slash commands will not have permissions set, you need to use a permissions getter e.g. dpp::guild_commands_get_permissions to get the guild command permissions - * @param guild_id Guild ID to get the slash commands for - * @return slashcommand_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_commands_get_sync(snowflake guild_id); - -/** - * @brief Respond to a slash command - * - * @see dpp::cluster::interaction_response_create - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response - * @param interaction_id Interaction id to respond to - * @param token Token for the interaction webhook - * @param r Response to send - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_response_create_sync(snowflake interaction_id, const std::string &token, const interaction_response &r); - -/** - * @brief Edit response to a slash command - * - * @see dpp::cluster::interaction_response_edit - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#edit-original-interaction-response - * @param token Token for the interaction webhook - * @param m Message to send - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_response_edit_sync(const std::string &token, const message &m); - -/** - * @brief Get the original response to a slash command - * - * @see dpp::cluster::interaction_response_get_original - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#get-original-interaction-response - * @param token Token for the interaction webhook - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_response_get_original_sync(const std::string &token); - -/** - * @brief Create a followup message to a slash command - * - * @see dpp::cluster::interaction_followup_create - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response - * @param token Token for the interaction webhook - * @param m followup message to create - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_create_sync(const std::string &token, const message &m); - -/** - * @brief Edit original followup message to a slash command - * This is an alias for cluster::interaction_response_edit - * @see dpp::cluster::interaction_followup_edit_original - * @see cluster::interaction_response_edit - * - * @param token Token for the interaction webhook - * @param m message to edit, the ID should be set - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_edit_original_sync(const std::string &token, const message &m); - -/** - * @brief Delete the initial interaction response - * - * @see dpp::cluster::interaction_followup_delete - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#delete-original-interaction-response - * @param token Token for the interaction webhook - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_delete_sync(const std::string &token); - -/** - * @brief Edit followup message to a slash command - * The message ID in the message you pass should be correctly set to that of a followup message you previously sent - * - * @see dpp::cluster::interaction_followup_edit - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#edit-followup-message - * @param token Token for the interaction webhook - * @param m message to edit, the ID should be set - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_edit_sync(const std::string &token, const message &m); - -/** - * @brief Get the followup message to a slash command - * - * @see dpp::cluster::interaction_followup_get - * @see https://discord.com/developers/docs/interactions/receiving-and-responding#get-followup-message - * @param token Token for the interaction webhook - * @param message_id message to retrieve - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_followup_get_sync(const std::string &token, snowflake message_id); - -/** - * @brief Get the original followup message to a slash command - * This is an alias for cluster::interaction_response_get_original - * @see dpp::cluster::interaction_followup_get_original - * @see cluster::interaction_response_get_original - * - * @param token Token for the interaction webhook - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_followup_get_original_sync(const std::string &token); - -/** - * @brief Get all auto moderation rules for a guild - * - * @param guild_id Guild id of the auto moderation rule - * @return automod_rule_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule_map automod_rules_get_sync(snowflake guild_id); - -/** - * @brief Get a single auto moderation rule - * - * @param guild_id Guild id of the auto moderation rule - * @param rule_id Rule id to retrieve - * @return automod_rule returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_get_sync(snowflake guild_id, snowflake rule_id); - -/** - * @brief Create an auto moderation rule - * - * @param guild_id Guild id of the auto moderation rule - * @param r Auto moderation rule to create - * @return automod_rule returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_create_sync(snowflake guild_id, const automod_rule& r); - -/** - * @brief Edit an auto moderation rule - * - * @param guild_id Guild id of the auto moderation rule - * @param r Auto moderation rule to edit. The rule's id must be set. - * @return automod_rule returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_edit_sync(snowflake guild_id, const automod_rule& r); - -/** - * @brief Delete an auto moderation rule - * - * @param guild_id Guild id of the auto moderation rule - * @param rule_id Auto moderation rule id to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation automod_rule_delete_sync(snowflake guild_id, snowflake rule_id); - -/** - * @brief Create a channel - * - * Create a new channel object for the guild. Requires the `MANAGE_CHANNELS` permission. If setting permission overwrites, - * only permissions your bot has in the guild can be allowed/denied. Setting `MANAGE_ROLES` permission in channels is only possible - * for guild administrators. Returns the new channel object on success. Fires a `Channel Create Gateway` event. - * - * All parameters to this endpoint are optional excluding `name` - * - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_create - * @see https://discord.com/developers/docs/resources/channel#create-channel - * @param c Channel to create - * @return channel returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_create_sync(const class channel &c); - -/** - * @brief Remove a permission from a channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_delete_permission - * @see https://discord.com/developers/docs/resources/channel#delete-channel-permission - * @param c Channel to remove permission from - * @param overwrite_id Overwrite to remove, user or channel ID - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_delete_permission_sync(const class channel &c, snowflake overwrite_id); - -/** - * @brief Delete a channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_delete - * @see https://discord.com/developers/docs/resources/channel#deleteclose-channel - * @param channel_id Channel id to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_delete_sync(snowflake channel_id); - -/** - * @brief Edit a channel's permissions - * - * @see dpp::cluster::channel_edit_permissions - * @see https://discord.com/developers/docs/resources/channel#edit-channel-permissions - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param c Channel to set permissions for - * @param overwrite_id Overwrite to change (a user or role ID) - * @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) - * @param deny Bitmask of denied permissions (refer to enum dpp::permissions) - * @param member true if the overwrite_id is a user id, false if it is a channel id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_permissions_sync(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); - -/** - * @brief Edit a channel's permissions - * - * @see dpp::cluster::channel_edit_permissions - * @see https://discord.com/developers/docs/resources/channel#edit-channel-permissions - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param channel_id ID of the channel to set permissions for - * @param overwrite_id Overwrite to change (a user or role ID) - * @param allow Bitmask of allowed permissions (refer to enum dpp::permissions) - * @param deny Bitmask of denied permissions (refer to enum dpp::permissions) - * @param member true if the overwrite_id is a user id, false if it is a channel id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_permissions_sync(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); - -/** - * @brief Edit multiple channels positions - * - * Modify the positions of a set of channel objects for the guild. - * Requires `MANAGE_CHANNELS` permission. Fires multiple `Channel Update Gateway` events. - * Only channels to be modified are required. - * - * @see dpp::cluster::channel_edit_positions - * @see https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions - * @param c Channel to change the position for - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_positions_sync(const std::vector &c); - -/** - * @brief Edit a channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_edit - * @see https://discord.com/developers/docs/resources/channel#modify-channel - * @param c Channel to edit/update - * @return channel returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_edit_sync(const class channel &c); - -/** - * @brief Follow an announcement (news) channel - * @see dpp::cluster::channel_follow_news - * @see https://discord.com/developers/docs/resources/channel#follow-news-channel - * @param c Channel id to follow - * @param target_channel_id Channel to subscribe the channel to - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_follow_news_sync(const class channel &c, snowflake target_channel_id); - -/** - * @brief Get a channel - * - * @see dpp::cluster::channel_get - * @see https://discord.com/developers/docs/resources/channel#get-channel - * @param c Channel ID to retrieve - * @return channel returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_get_sync(snowflake c); - -/** - * @brief Create invite for a channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::channel_invite_create - * @see https://discord.com/developers/docs/resources/channel#create-channel-invite - * @param c Channel to create an invite on - * @param i Invite to create - * @return invite returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite channel_invite_create_sync(const class channel &c, const class invite &i); - -/** - * @brief Get invites for a channel - * - * @see dpp::cluster::channel_invites_get - * @see https://discord.com/developers/docs/resources/invite#get-invites - * @param c Channel to get invites for - * @return invite_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite_map channel_invites_get_sync(const class channel &c); - -/** - * @brief Trigger channel typing indicator - * @see dpp::cluster::channel_typing - * @see https://discord.com/developers/docs/resources/channel#trigger-typing-indicator - * @param c Channel to set as typing on - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_typing_sync(const class channel &c); - -/** - * @brief Trigger channel typing indicator - * @see dpp::cluster::channel_typing - * @see https://discord.com/developers/docs/resources/channel#trigger-typing-indicator - * @param cid Channel ID to set as typing on - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_typing_sync(snowflake cid); - -/** - * @brief Get all channels for a guild - * - * @see dpp::cluster::channels_get - * @see https://discord.com/developers/docs/resources/channel#get-channels - * @param guild_id Guild ID to retrieve channels for - * @return channel_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel_map channels_get_sync(snowflake guild_id); - -/** - * @brief Set the status of a voice channel. - * - * @see dpp::cluster::channel_set_voice_status - * @see https://github.com/discord/discord-api-docs/pull/6400 (please replace soon). - * @param channel_id The channel to update. - * @param status The new status for the channel. - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_set_voice_status_sync(snowflake channel_id, const std::string& status); - -/** - * @brief Create a dm channel - * @see dpp::cluster::create_dm_channel - * @see https://discord.com/developers/docs/resources/user#create-dm - * @param user_id User ID to create DM channel with - * @return channel returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel create_dm_channel_sync(snowflake user_id); - -/** - * @brief Get current user DM channels - * - * @return channel_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel_map current_user_get_dms_sync(); - -/** - * @brief Create a direct message, also create the channel for the direct message if needed - * - * @see dpp::cluster::direct_message_create - * @see https://discord.com/developers/docs/resources/user#create-dm - * @see dpp::cluster::direct_message_create - * @see https://discord.com/developers/docs/resources/channel#create-message - * @param user_id User ID of user to send message to - * @param m Message object - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message direct_message_create_sync(snowflake user_id, const message &m); - -/** - * @brief Adds a recipient to a Group DM using their access token - * @see dpp::cluster::gdm_add - * @see https://discord.com/developers/docs/resources/channel#group-dm-add-recipient - * @param channel_id Channel id to add group DM recipients to - * @param user_id User ID to add - * @param access_token Access token from OAuth2 - * @param nick Nickname of user to apply to the chat - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation gdm_add_sync(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick); - -/** - * @brief Removes a recipient from a Group DM - * @see dpp::cluster::gdm_remove - * @see https://discord.com/developers/docs/resources/channel#group-dm-remove-recipient - * @param channel_id Channel ID of group DM - * @param user_id User ID to remove from group DM - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation gdm_remove_sync(snowflake channel_id, snowflake user_id); - -/** - * @brief Create single emoji. - * You must ensure that the emoji passed contained image data using the emoji::load_image() method. - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::guild_emoji_create - * @see https://discord.com/developers/docs/resources/emoji#create-guild-emoji - * @param guild_id Guild ID to create emoji om - * @param newemoji Emoji to create - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_create_sync(snowflake guild_id, const class emoji& newemoji); - -/** - * @brief Delete a guild emoji - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::guild_emoji_delete - * @see https://discord.com/developers/docs/resources/emoji#delete-guild-emoji - * @param guild_id Guild ID to delete emoji on - * @param emoji_id Emoji ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_emoji_delete_sync(snowflake guild_id, snowflake emoji_id); - -/** - * @brief Edit a single emoji. - * - * You must ensure that the emoji passed contained image data using the emoji::load_image() method. - * @see dpp::cluster::guild_emoji_edit - * @see https://discord.com/developers/docs/resources/emoji#modify-guild-emoji - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to edit emoji on - * @param newemoji Emoji to edit - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_edit_sync(snowflake guild_id, const class emoji& newemoji); - -/** - * @brief Get a single emoji - * - * @see dpp::cluster::guild_emoji_get - * @see https://discord.com/developers/docs/resources/emoji#get-guild-emoji - * @param guild_id Guild ID to get emoji for - * @param emoji_id Emoji ID to get - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_get_sync(snowflake guild_id, snowflake emoji_id); - -/** - * @brief Get all emojis for a guild - * - * @see dpp::cluster::guild_emojis_get - * @see https://discord.com/developers/docs/resources/emoji#list-guild-emojis - * @param guild_id Guild ID to get emojis for - * @return emoji_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map guild_emojis_get_sync(snowflake guild_id); - -/** - * @brief List all Application Emojis - * - * @see dpp::cluster::application_emojis_get - * @see https://discord.com/developers/docs/resources/emoji#list-application-emojis - * @return emoji_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map application_emojis_get_sync(); - -/** - * @brief Get an Application Emoji - * - * @see dpp::cluster::application_emoji_get - * @see https://discord.com/developers/docs/resources/emoji#get-application-emoji - * @param emoji_id The ID of the Emoji to get. - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_get_sync(snowflake emoji_id); - -/** - * @brief Create an Application Emoji - * - * @see dpp::cluster::application_emoji_create - * @see https://discord.com/developers/docs/resources/emoji#create-application-emoji - * @param newemoji The emoji to create - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_create_sync(const class emoji& newemoji); - -/** - * @brief Edit an Application Emoji - * - * @see dpp::cluster::application_emoji_edit - * @see https://discord.com/developers/docs/resources/emoji#modify-application-emoji - * @param newemoji The emoji to edit - * @return emoji returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_edit_sync(const class emoji& newemoji); - -/** - * @brief Delete an Application Emoji - * - * @see dpp::cluster::application_emoji_delete - * @see https://discord.com/developers/docs/resources/emoji#delete-application-emoji - * @param emoji_id The emoji's ID to delete. - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation application_emoji_delete_sync(snowflake emoji_id); - -/** - * @brief Returns all entitlements for a given app, active and expired. - * - * @see dpp::cluster::entitlements_get - * @see https://discord.com/developers/docs/monetization/entitlements#list-entitlements - * @param user_id User ID to look up entitlements for. - * @param sku_ids List of SKU IDs to check entitlements for. - * @param before_id Retrieve entitlements before this entitlement ID. - * @param after_id Retrieve entitlements after this entitlement ID. - * @param limit Number of entitlements to return, 1-100 (default 100). - * @param guild_id Guild ID to look up entitlements for. - * @param exclude_ended Whether ended entitlements should be excluded from the search. - * @return entitlement_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") entitlement_map entitlements_get_sync(snowflake user_id = 0, const std::vector& sku_ids = {}, snowflake before_id = 0, snowflake after_id = 0, uint8_t limit = 100, snowflake guild_id = 0, bool exclude_ended = false); - -/** - * @brief Creates a test entitlement to a given SKU for a given guild or user. - * Discord will act as though that user or guild has entitlement to your premium offering. - * - * @see dpp::cluster::entitlement_test_create - * @see https://discord.com/developers/docs/monetization/entitlements#create-test-entitlement - * @param new_entitlement The entitlement to create. - * Make sure your dpp::entitlement_type (inside your dpp::entitlement object) matches the type of the owner_id - * (if type is guild, owner_id is a guild id), otherwise it won't work! - * @return entitlement returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") entitlement entitlement_test_create_sync(const class entitlement& new_entitlement); - -/** - * @brief Deletes a currently-active test entitlement. - * Discord will act as though that user or guild no longer has entitlement to your premium offering. - * - * @see dpp::cluster::entitlement_test_delete - * @see https://discord.com/developers/docs/monetization/entitlements#delete-test-entitlement - * @param entitlement_id The test entitlement to delete. - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation entitlement_test_delete_sync(snowflake entitlement_id); - -/** - * @brief For One-Time Purchase consumable SKUs, marks a given entitlement for the user as consumed. - * - * @see dpp::cluster::entitlement_consume - * @see https://discord.com/developers/docs/monetization/entitlements#consume-an-entitlement - * @param entitlement_id The entitlement to mark as consumed. - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation entitlement_consume_sync(snowflake entitlement_id); - -/** - * @brief Get the gateway information for the bot using the token - * @see dpp::cluster::get_gateway_bot - * @see https://discord.com/developers/docs/topics/gateway#get-gateway-bot - * @return gateway returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") gateway get_gateway_bot_sync(); - -/** - * @brief Modify current member - * - * Modifies the current member in a guild. - * Fires a `Guild Member Update` Gateway event. - * - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_current_member_edit - * @see https://discord.com/developers/docs/resources/guild#modify-current-member - * @param guild_id Guild ID to change on - * @param nickname New nickname, or empty string to clear nickname - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_current_member_edit_sync(snowflake guild_id, const std::string &nickname); - -/** - * @brief Get the audit log for a guild - * - * @see dpp::cluster::guild_auditlog_get - * @see https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log - * @param guild_id Guild to get the audit log of - * @param user_id Entries from a specific user ID. Set this to `0` will fetch any user - * @param action_type Entries for a specific dpp::audit_type. Set this to `0` will fetch any type - * @param before Entries with ID less than a specific audit log entry ID. Used for paginating - * @param after Entries with ID greater than a specific audit log entry ID. Used for paginating - * @param limit Maximum number of entries (between 1-100) to return - * @return auditlog returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") auditlog guild_auditlog_get_sync(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit); - -/** - * @brief Add guild ban - * - * Create a guild ban, and optionally delete previous messages sent by the banned user. - * Requires the `BAN_MEMBERS` permission. Fires a `Guild Ban Add` Gateway event. - * @see dpp::cluster::guild_ban_add - * @see https://discord.com/developers/docs/resources/guild#create-guild-ban - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to add ban to - * @param user_id User ID to ban - * @param delete_message_seconds How many seconds to delete messages for, between 0 and 604800 (7 days). Defaults to 0 - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_ban_add_sync(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds = 0); - -/** - * @brief Delete guild ban - * - * Remove the ban for a user. Requires the `BAN_MEMBERS` permissions. - * Fires a Guild Ban Remove Gateway event. - * @see dpp::cluster::guild_ban_delete - * @see https://discord.com/developers/docs/resources/guild#remove-guild-ban - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild to delete ban from - * @param user_id User ID to delete ban for - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_ban_delete_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Create a guild - * - * Create a new guild. Returns a guild object on success. `Fires a Guild Create Gateway` event. - * - * When using the roles parameter, the first member of the array is used to change properties of the guild's everyone role. - * If you are trying to bootstrap a guild with additional roles, keep this in mind. The required id field within each role object is an - * integer placeholder, and will be replaced by the API upon consumption. Its purpose is to allow you to overwrite a role's permissions - * in a channel when also passing in channels with the channels array. - * When using the channels parameter, the position field is ignored, and none of the default channels are created. The id field within - * each channel object may be set to an integer placeholder, and will be replaced by the API upon consumption. Its purpose is to - * allow you to create `GUILD_CATEGORY` channels by setting the `parent_id` field on any children to the category's id field. - * Category channels must be listed before any children. - * - * @see dpp::cluster::guild_create - * @see https://discord.com/developers/docs/resources/guild#create-guild - * @note The region field is deprecated and is replaced by channel.rtc_region. This endpoint can be used only by bots in less than 10 guilds. - * @param g Guild to create - * @return guild returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_create_sync(const class guild &g); - -/** - * @brief Delete a guild - * - * Delete a guild permanently. User must be owner. Fires a `Guild Delete Gateway` event. - * - * @see dpp::cluster::guild_delete - * @see https://discord.com/developers/docs/resources/guild#delete-guild - * @param guild_id Guild ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_delete_sync(snowflake guild_id); - -/** - * @brief Delete guild integration - * - * Delete the attached integration object for the guild. Deletes any associated webhooks and kicks the associated bot if there is one. - * Requires the `MANAGE_GUILD` permission. Fires a Guild Integrations Update Gateway event. - * - * @see dpp::cluster::guild_delete_integration - * @see https://discord.com/developers/docs/resources/guild#delete-guild-integration - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to delete integration for - * @param integration_id Integration ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_delete_integration_sync(snowflake guild_id, snowflake integration_id); - -/** - * @brief Edit a guild - * - * Modify a guild's settings. Requires the `MANAGE_GUILD` permission. Returns the updated guild object on success. - * Fires a `Guild Update Gateway` event. - * - * @see dpp::cluster::guild_edit - * @see https://discord.com/developers/docs/resources/guild#modify-guild - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param g Guild to edit - * @return guild returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_edit_sync(const class guild &g); - -/** - * @brief Edit guild widget - * - * Requires the `MANAGE_GUILD` permission. - * - * @see dpp::cluster::guild_edit_widget - * @see https://discord.com/developers/docs/resources/guild#modify-guild-widget - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to edit widget for - * @param gw New guild widget information - * @return guild_widget returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_widget guild_edit_widget_sync(snowflake guild_id, const class guild_widget &gw); - -/** - * @brief Get single guild ban - * - * Requires the `BAN_MEMBERS` permission. - * @see dpp::cluster::guild_get_ban - * @see https://discord.com/developers/docs/resources/guild#get-guild-ban - * @param guild_id Guild ID to get ban for - * @param user_id User ID of ban to retrieve - * @return ban returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") ban guild_get_ban_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Get guild ban list - * - * Requires the `BAN_MEMBERS` permission. - * @see dpp::cluster::guild_get_bans - * @see https://discord.com/developers/docs/resources/guild#get-guild-bans - * @note Provide a user ID to `before` and `after` for pagination. Users will always be returned in ascending order by the user ID. If both before and after are provided, only before is respected. - * @param guild_id Guild ID to get bans for - * @param before If non-zero, all bans for user ids before this user id will be returned up to the limit - * @param after if non-zero, all bans for user ids after this user id will be returned up to the limit - * @param limit the maximum number of bans to retrieve in this call up to a maximum of 1000 - * @return ban_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") ban_map guild_get_bans_sync(snowflake guild_id, snowflake before, snowflake after, snowflake limit); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_get_sync(snowflake guild_id); - -/** - * @brief Get guild integrations - * - * Requires the `MANAGE_GUILD` permission. - * - * @see dpp::cluster::guild_get_integrations - * @see https://discord.com/developers/docs/resources/guild#get-guild-integrations - * @param guild_id Guild ID to get integrations for - * @return integration_map returned object on completion - * - * @note This endpoint returns a maximum of 50 integrations. If a guild has more integrations, they cannot be accessed. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") integration_map guild_get_integrations_sync(snowflake guild_id); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_get_preview_sync(snowflake guild_id); - -/** - * @brief Get guild vanity url, if enabled - * - * Returns a partial dpp::invite object for guilds with that feature enabled. Requires the `MANAGE_GUILD` permission. code will be null if a vanity url for the guild is not set. - * @see dpp::cluster::guild_get_vanity - * @see https://discord.com/developers/docs/resources/guild#get-guild-vanity-url - * @param guild_id Guild to get vanity URL for - * @return invite returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite guild_get_vanity_sync(snowflake guild_id); - -/** - * @brief Get guild widget - * - * Requires the `MANAGE_GUILD` permission. - * - * @see dpp::cluster::guild_get_widget - * @see https://discord.com/developers/docs/resources/guild#get-guild-widget - * @param guild_id Guild ID to get widget for - * @return guild_widget returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_widget guild_get_widget_sync(snowflake guild_id); - -/** - * @brief Modify guild integration - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::guild_modify_integration - * @see https://discord.com/developers/docs/resources/guild#modify-guild-integration - * @param guild_id Guild ID to modify integration for - * @param i Integration to modify - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_modify_integration_sync(snowflake guild_id, const class integration &i); - -/** - * @brief Get prune counts - * - * Returns a prune object indicating the number of members that would be removed in a prune operation. Requires the `KICK_MEMBERS` - * permission. By default, prune will not remove users with roles. You can optionally include specific roles in your prune by providing the - * include_roles parameter. Any inactive user that has a subset of the provided role(s) will be counted in the prune and users with additional - * roles will not. - * - * @see dpp::cluster::guild_get_prune_counts - * @see https://discord.com/developers/docs/resources/guild#get-guild-prune-count - * @param guild_id Guild ID to count for pruning - * @param pruneinfo Pruning info - * @return prune returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") prune guild_get_prune_counts_sync(snowflake guild_id, const struct prune& pruneinfo); - -/** - * @brief Begin guild prune - * - * Begin a prune operation. Requires the `KICK_MEMBERS` permission. Returns a prune object indicating the number of members - * that were removed in the prune operation. For large guilds it's recommended to set the `compute_prune_count` option to false, forcing - * 'pruned' to 0. Fires multiple `Guild Member Remove` Gateway events. - * By default, prune will not remove users with roles. You can optionally include specific roles in your prune by providing the `include_roles` - * parameter. Any inactive user that has a subset of the provided role(s) will be included in the prune and users with additional roles will not. - * - * @see dpp::cluster::guild_begin_prune - * @see https://discord.com/developers/docs/resources/guild#begin-guild-prune - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to prune - * @param pruneinfo Pruning info - * @return prune returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") prune guild_begin_prune_sync(snowflake guild_id, const struct prune& pruneinfo); - -/** - * @brief Change current user nickname - * - * Modifies the nickname of the current user in a guild. - * Fires a `Guild Member Update` Gateway event. - * - * @deprecated Deprecated in favor of Modify Current Member. Will be replaced by dpp::cluster::guild_current_member_edit - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_set_nickname - * @see https://discord.com/developers/docs/resources/guild#modify-current-user-nick - * @param guild_id Guild ID to change nickname on - * @param nickname New nickname, or empty string to clear nickname - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_set_nickname_sync(snowflake guild_id, const std::string &nickname); - -/** - * @brief Sync guild integration - * - * @see dpp::cluster::guild_sync_integration - * @see https://discord.com/developers/docs/resources/guild#sync-guild-integration - * @param guild_id Guild ID to sync integration on - * @param integration_id Integration ID to synchronise - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_sync_integration_sync(snowflake guild_id, snowflake integration_id); - -/** - * @brief Get the guild's onboarding configuration - * - * @see dpp::cluster::guild_get_onboarding - * @see https://discord.com/developers/docs/resources/guild#get-guild-onboarding - * @param guild_id The guild to pull the onboarding configuration from. - * @return onboarding returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") onboarding guild_get_onboarding_sync(snowflake guild_id); - -/** - * @brief Edit the guild's onboarding configuration - * - * Requires the `MANAGE_GUILD` and `MANAGE_ROLES` permissions. - * - * @note Onboarding enforces constraints when enabled. These constraints are that there must be at least 7 Default Channels and at least 5 of them must allow sending messages to the \@everyone role. The `onboarding::mode` field modifies what is considered when enforcing these constraints. - * - * @see dpp::cluster::guild_edit_onboarding - * @see https://discord.com/developers/docs/resources/guild#modify-guild-onboarding - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param o The onboarding object - * @return onboarding returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") onboarding guild_edit_onboarding_sync(const struct onboarding& o); - -/** - * @brief Get the guild's welcome screen - * - * If the welcome screen is not enabled, the `MANAGE_GUILD` permission is required. - * - * @see dpp::cluster::guild_get_welcome_screen - * @see https://discord.com/developers/docs/resources/guild#get-guild-welcome-screen - * @param guild_id The guild ID to get the welcome screen from - * @return dpp::welcome_screen returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dpp::welcome_screen guild_get_welcome_screen_sync(snowflake guild_id); - -/** - * @brief Edit the guild's welcome screen - * - * Requires the `MANAGE_GUILD` permission. May fire a `Guild Update` Gateway event. - * - * @see dpp::cluster::guild_edit_welcome_screen - * @see https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen - * @param guild_id The guild ID to edit the welcome screen for - * @param welcome_screen The welcome screen - * @param enabled Whether the welcome screen should be enabled or disabled - * @return dpp::welcome_screen returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dpp::welcome_screen guild_edit_welcome_screen_sync(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled); - -/** - * @brief Add guild member. Needs a specific oauth2 scope, from which you get the access_token. - * - * Adds a user to the guild, provided you have a valid oauth2 access token for the user with the guilds.join scope. - * Returns the guild_member, which is defaulted if the user is already a member of the guild. Fires a `Guild Member Add` Gateway event. - * - * For guilds with Membership Screening enabled, this endpoint will default to adding new members as pending in the guild member object. - * Members that are pending will have to complete membership screening before they become full members that can talk. - * - * @note All parameters to this endpoint except for access_token are optional. - * The bot must be a member of the guild with `CREATE_INSTANT_INVITE` permission. - * @see dpp::cluster::guild_add_member - * @see https://discord.com/developers/docs/resources/guild#add-guild-member - * @param gm Guild member to add - * @param access_token Access token from Oauth2 scope - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_add_member_sync(const guild_member& gm, const std::string &access_token); - -/** - * @brief Edit the properties of an existing guild member - * - * Modify attributes of a guild member. Returns the guild_member. Fires a `Guild Member Update` Gateway event. - * To remove a timeout, set the `communication_disabled_until` to a non-zero time in the past, e.g. 1. - * When moving members to channels, the API user must have permissions to both connect to the channel and have the `MOVE_MEMBERS` permission. - * For moving and disconnecting users from voice, use dpp::cluster::guild_member_move. - * @see dpp::cluster::guild_edit_member - * @see https://discord.com/developers/docs/resources/guild#modify-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param gm Guild member to edit - * @return guild_member returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member guild_edit_member_sync(const guild_member& gm); - -/** - * @brief Get a guild member - * @see dpp::cluster::guild_get_member - * @see https://discord.com/developers/docs/resources/guild#get-guild-member - * @param guild_id Guild ID to get member for - * @param user_id User ID of member to get - * @return guild_member returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member guild_get_member_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Get all guild members - * - * @note This endpoint is restricted according to whether the `GUILD_MEMBERS` Privileged Intent is enabled for your application. - * @see dpp::cluster::guild_get_members - * @see https://discord.com/developers/docs/resources/guild#get-guild-members - * @param guild_id Guild ID to get all members for - * @param limit max number of members to return (1-1000) - * @param after the highest user id in the previous page - * @return guild_member_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member_map guild_get_members_sync(snowflake guild_id, uint16_t limit, snowflake after); - -/** - * @brief Add role to guild member - * - * Adds a role to a guild member. Requires the `MANAGE_ROLES` permission. - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_add_role - * @see https://discord.com/developers/docs/resources/guild#add-guild-member-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to add a role to - * @param user_id User ID to add role to - * @param role_id Role ID to add to the user - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_add_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id); - -/** - * @brief Remove (kick) a guild member - * - * Remove a member from a guild. Requires `KICK_MEMBERS` permission. - * Fires a `Guild Member Remove` Gateway event. - * @see dpp::cluster::guild_member_delete - * @see https://discord.com/developers/docs/resources/guild#remove-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @deprecated Replaced by dpp::cluster::guild_member_kick - * @param guild_id Guild ID to kick member from - * @param user_id User ID to kick - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_delete_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Remove (kick) a guild member - * - * Remove a member from a guild. Requires `KICK_MEMBERS` permission. - * Fires a `Guild Member Remove` Gateway event. - * @see dpp::cluster::guild_member_kick - * @see https://discord.com/developers/docs/resources/guild#remove-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to kick member from - * @param user_id User ID to kick - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_kick_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Set the timeout of a guild member - * - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_timeout - * @see https://discord.com/developers/docs/resources/guild#modify-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to timeout the member in - * @param user_id User ID to set the timeout for - * @param communication_disabled_until The timestamp when the user's timeout will expire (up to 28 days in the future). Set to 0 to remove the timeout - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_timeout_sync(snowflake guild_id, snowflake user_id, time_t communication_disabled_until); - -/** - * @brief Remove the timeout of a guild member. - * A shortcut for guild_member_timeout(guild_id, user_id, 0, callback) - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_timeout_remove - * @see https://discord.com/developers/docs/resources/guild#modify-guild-member - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to remove the member timeout from - * @param user_id User ID to remove the timeout for - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_timeout_remove_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Remove role from guild member - * - * Removes a role from a guild member. Requires the `MANAGE_ROLES` permission. - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_delete_role - * @see https://discord.com/developers/docs/resources/guild#remove-guild-member-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to remove role from user on - * @param user_id User ID to remove role from - * @param role_id Role to remove - * @return confirmation returned object on completion - * @deprecated Use dpp::cluster::guild_member_remove_role instead - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_delete_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id); - -/** - * @brief Remove role from guild member - * - * Removes a role from a guild member. Requires the `MANAGE_ROLES` permission. - * Fires a `Guild Member Update` Gateway event. - * @see dpp::cluster::guild_member_remove_role - * @see https://discord.com/developers/docs/resources/guild#remove-guild-member-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to remove role from user on - * @param user_id User ID to remove role from - * @param role_id Role to remove - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_member_remove_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id); - -/** - * @brief Moves the guild member to a other voice channel, if member is connected to one. - * Set the `channel_id` to `0` to disconnect the user. - * - * Fires a `Guild Member Update` Gateway event. - * @note When moving members to channels, the API user __must__ have permissions to both connect to the channel and have the `MOVE_MEMBERS` permission. - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_member_move - * @see https://discord.com/developers/docs/resources/guild#modify-guild-member - * @param channel_id Id of the channel to which the user is used. Set to `0` to disconnect the user - * @param guild_id Guild id to which the user is connected - * @param user_id User id, who should be moved - * @return guild_member returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member guild_member_move_sync(const snowflake channel_id, const snowflake guild_id, const snowflake user_id); - -/** - * @brief Search for guild members based on whether their username or nickname starts with the given string. - * - * @note This endpoint is restricted according to whether the `GUILD_MEMBERS` Privileged Intent is enabled for your application. - * @see dpp::cluster::guild_search_members - * @see https://discord.com/developers/docs/resources/guild#search-guild-members - * @param guild_id Guild ID to search in - * @param query Query string to match username(s) and nickname(s) against - * @param limit max number of members to return (1-1000) - * @return guild_member_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_member_map guild_search_members_sync(snowflake guild_id, const std::string& query, uint16_t limit); - -/** - * @brief Get guild invites - * - * Returns a list of invite objects (with invite metadata) for the guild. Requires the `MANAGE_GUILD` permission. - * - * @see dpp::cluster::guild_get_invites - * @see https://discord.com/developers/docs/resources/guild#get-guild-invites - * @param guild_id Guild ID to get invites for - * @return invite_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite_map guild_get_invites_sync(snowflake guild_id); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite invite_delete_sync(const std::string &invitecode); - -/** - * @brief Get details about an invite - * - * @see dpp::cluster::invite_get - * @see https://discord.com/developers/docs/resources/invite#get-invite - * @param invite_code Invite code to get information on - * @return invite returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite invite_get_sync(const std::string &invite_code); - -/** - * @brief Add a reaction to a message. The reaction string must be either an `emojiname:id` or a unicode character. - * - * @see dpp::cluster::message_add_reaction - * @see https://discord.com/developers/docs/resources/channel#create-reaction - * @param m Message to add a reaction to - * @param reaction Reaction to add. Emojis should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_add_reaction_sync(const struct message &m, const std::string &reaction); - -/** - * @brief Add a reaction to a message by id. The reaction string must be either an `emojiname:id` or a unicode character. - * - * @see dpp::cluster::message_add_reaction - * @see https://discord.com/developers/docs/topics/gateway#message-reaction-add - * @param message_id Message to add reactions to - * @param channel_id Channel to add reactions to - * @param reaction Reaction to add. Emojis should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_add_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); - -/** - * @brief Send a message to a channel. The callback function is called when the message has been sent - * - * @see dpp::cluster::message_create - * @see https://discord.com/developers/docs/resources/channel#create-message - * @param m Message to send - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_create_sync(const struct message &m); - -/** - * @brief Crosspost a message. The callback function is called when the message has been sent - * - * @see dpp::cluster::message_crosspost - * @see https://discord.com/developers/docs/resources/channel#crosspost-message - * @param message_id Message to crosspost - * @param channel_id Channel ID to crosspost from - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_crosspost_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Delete all reactions on a message - * - * @see dpp::cluster::message_delete_all_reactions - * @see https://discord.com/developers/docs/resources/channel#delete-all-reactions - * @param m Message to delete reactions from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_all_reactions_sync(const struct message &m); - -/** - * @brief Delete all reactions on a message by id - * - * @see dpp::cluster::message_delete_all_reactions - * @see https://discord.com/developers/docs/resources/channel#delete-all-reactions - * @param message_id Message to delete reactions from - * @param channel_id Channel to delete reactions from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_all_reactions_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Bulk delete messages from a channel. The callback function is called when the message has been edited - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @note If any message provided older than 2 weeks or any duplicate message ID, it will fail. - * - * @see dpp::cluster::message_delete_bulk - * @see https://discord.com/developers/docs/resources/channel#bulk-delete-messages - * @param message_ids List of message IDs to delete (at least 2 and at most 100 message IDs) - * @param channel_id Channel to delete from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_bulk_sync(const std::vector &message_ids, snowflake channel_id); - -/** - * @brief Delete a message from a channel. The callback function is called when the message has been edited - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::message_delete - * @see https://discord.com/developers/docs/resources/channel#delete-message - * @param message_id Message ID to delete - * @param channel_id Channel to delete from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Delete own reaction from a message. The reaction string must be either an `emojiname:id` or a unicode character. - * - * @see dpp::cluster::message_delete_own_reaction - * @see https://discord.com/developers/docs/resources/channel#delete-own-reaction - * @param m Message to delete own reaction from - * @param reaction Reaction to delete. The reaction should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_own_reaction_sync(const struct message &m, const std::string &reaction); - -/** - * @brief Delete own reaction from a message by id. The reaction string must be either an `emojiname:id` or a unicode character. - * - * @see dpp::cluster::message_delete_own_reaction - * @see https://discord.com/developers/docs/resources/channel#delete-own-reaction - * @param message_id Message to delete reactions from - * @param channel_id Channel to delete reactions from - * @param reaction Reaction to delete. The reaction should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_own_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); - -/** - * @brief Delete a user's reaction from a message. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_delete_reaction - * @see https://discord.com/developers/docs/resources/channel#delete-user-reaction - * @param m Message to delete a user's reaction from - * @param user_id User ID who's reaction you want to remove - * @param reaction Reaction to remove. Reactions should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_sync(const struct message &m, snowflake user_id, const std::string &reaction); - -/** - * @brief Delete a user's reaction from a message by id. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_delete_reaction - * @see https://discord.com/developers/docs/resources/channel#delete-user-reaction - * @param message_id Message to delete reactions from - * @param channel_id Channel to delete reactions from - * @param user_id User ID who's reaction you want to remove - * @param reaction Reaction to remove. Reactions should be in the form emojiname:id - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_sync(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction); - -/** - * @brief Delete all reactions on a message using a particular emoji. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_delete_reaction_emoji - * @see https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji - * @param m Message to delete reactions from - * @param reaction Reaction to delete, in the form emojiname:id or a unicode character - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_emoji_sync(const struct message &m, const std::string &reaction); - -/** - * @brief Delete all reactions on a message using a particular emoji by id. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_delete_reaction_emoji - * @see https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji - * @param message_id Message to delete reactions from - * @param channel_id Channel to delete reactions from - * @param reaction Reaction to delete, in the form emojiname:id or a unicode character - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_emoji_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); - -/** - * @brief Edit a message on a channel. The callback function is called when the message has been edited - * - * @see dpp::cluster::message_edit - * @see https://discord.com/developers/docs/resources/channel#edit-message - * @param m Message to edit - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_edit_sync(const struct message &m); - -/** - * @brief Edit the flags of a message on a channel. The callback function is called when the message has been edited - * - * @param m Message to edit the flags of - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_edit_flags_sync(const struct message &m); - -/** - * @brief Get a message - * - * @see dpp::cluster::message_get - * @see https://discord.com/developers/docs/resources/channel#get-channel-message - * @param message_id Message ID - * @param channel_id Channel ID - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_get_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Get reactions on a message for a particular emoji. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_get_reactions - * @see https://discord.com/developers/docs/resources/channel#get-reactions - * @param m Message to get reactions for - * @param reaction Reaction should be in the form emojiname:id or a unicode character - * @param before Reactions before this ID should be retrieved if this is set to non-zero - * @param after Reactions before this ID should be retrieved if this is set to non-zero - * @param limit This number of reactions maximum should be returned - * @return user_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map message_get_reactions_sync(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit); - -/** - * @brief Get reactions on a message for a particular emoji by id. The reaction string must be either an `emojiname:id` or a unicode character - * - * @see dpp::cluster::message_get_reactions - * @see https://discord.com/developers/docs/resources/channel#get-reactions - * @param message_id Message to get reactions for - * @param channel_id Channel to get reactions for - * @param reaction Reaction should be in the form emojiname:id or a unicode character - * @param before Reactions before this ID should be retrieved if this is set to non-zero - * @param after Reactions before this ID should be retrieved if this is set to non-zero - * @param limit This number of reactions maximum should be returned - * @return emoji_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map message_get_reactions_sync(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit); - -/** - * @brief Pin a message - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::message_pin - * @see https://discord.com/developers/docs/resources/channel#pin-message - * @param channel_id Channel id to pin message on - * @param message_id Message id to pin message on - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_pin_sync(snowflake channel_id, snowflake message_id); - -/** - * @brief Get multiple messages. - * - * This function will attempt to fetch as many messages as possible using multiple API calls if needed. - * - * @see dpp::cluster::messages_get - * @see https://discord.com/developers/docs/resources/channel#get-channel-messages - * @param channel_id Channel ID to retrieve messages for - * @param around Messages should be retrieved around this ID if this is set to non-zero - * @param before Messages before this ID should be retrieved if this is set to non-zero - * @param after Messages after this ID should be retrieved if this is set to non-zero - * @param limit This number of messages maximum should be returned, up to a maximum of 100. - * @return message_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message_map messages_get_sync(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit); - -/** - * @brief Unpin a message - * @see dpp::cluster::message_unpin - * @see https://discord.com/developers/docs/resources/channel#unpin-message - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param channel_id Channel id to unpin message on - * @param message_id Message id to unpin message on - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_unpin_sync(snowflake channel_id, snowflake message_id); - -/** - * @brief Get a list of users that voted for this specific answer. - * - * @param m Message that contains the poll to retrieve the answers from - * @param answer_id ID of the answer to retrieve votes from (see poll_answer::answer_id) - * @param after Users after this ID should be retrieved if this is set to non-zero - * @param limit This number of users maximum should be returned, up to 100 - * @return user_map returned object on completion - * @see dpp::cluster::poll_get_answer_voters - * @see https://discord.com/developers/docs/resources/poll#get-answer-voters - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map poll_get_answer_voters_sync(const message& m, uint32_t answer_id, snowflake after, uint64_t limit); - -/** - * @brief Get a list of users that voted for this specific answer. - * - * @param message_id ID of the message with the poll to retrieve the answers from - * @param channel_id ID of the channel with the poll to retrieve the answers from - * @param answer_id ID of the answer to retrieve votes from (see poll_answer::answer_id) - * @param after Users after this ID should be retrieved if this is set to non-zero - * @param limit This number of users maximum should be returned, up to 100 - * @return user_map returned object on completion - * @see dpp::cluster::poll_get_answer_voters - * @see https://discord.com/developers/docs/resources/poll#get-answer-voters - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map poll_get_answer_voters_sync(snowflake message_id, snowflake channel_id, uint32_t answer_id, snowflake after, uint64_t limit); - -/** - * @brief Immediately end a poll. - * - * @param m Message that contains the poll - * @return message returned object on completion - * @see dpp::cluster::poll_end - * @see https://discord.com/developers/docs/resources/poll#end-poll - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message poll_end_sync(const message &m); - -/** - * @brief Immediately end a poll. - * - * @param message_id ID of the message with the poll to end - * @param channel_id ID of the channel with the poll to end - * @return message returned object on completion - * @see dpp::cluster::poll_end - * @see https://discord.com/developers/docs/resources/poll#end-poll - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message poll_end_sync(snowflake message_id, snowflake channel_id); - -/** - * @brief Get a channel's pins - * @see dpp::cluster::channel_pins_get - * @see https://discord.com/developers/docs/resources/channel#get-pinned-messages - * @param channel_id Channel ID to get pins for - * @return message_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message_map channel_pins_get_sync(snowflake channel_id); - -/** - * @brief Create a role on a guild - * - * Create a new role for the guild. Requires the `MANAGE_ROLES` permission. Returns the new role object on success. - * Fires a `Guild Role Create` Gateway event. - * - * @see dpp::cluster::role_create - * @see https://discord.com/developers/docs/resources/guild#create-guild-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param r Role to create (guild ID is encapsulated in the role object) - * @return role returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role role_create_sync(const class role &r); - -/** - * @brief Delete a role - * - * Requires the `MANAGE_ROLES` permission. Fires a `Guild Role Delete` Gateway event. - * - * @see dpp::cluster::role_delete - * @see https://discord.com/developers/docs/resources/guild#delete-guild-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to delete the role on - * @param role_id Role ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation role_delete_sync(snowflake guild_id, snowflake role_id); - -/** - * @brief Edit a role on a guild - * - * Requires the `MANAGE_ROLES` permission. Returns the updated role on success. Fires a `Guild Role Update` Gateway event. - * - * @see dpp::cluster::role_edit - * @see https://discord.com/developers/docs/resources/guild#modify-guild-role - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param r Role to edit - * @return role returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role role_edit_sync(const class role &r); - -/** - * @brief Edit multiple role's position in a guild. Returns a list of all roles of the guild on success. - * - * Modify the positions of a set of role objects for the guild. Requires the `MANAGE_ROLES` permission. - * Fires multiple `Guild Role Update` Gateway events. - * - * @see dpp::cluster::roles_edit_position - * @see https://discord.com/developers/docs/resources/guild#modify-guild-role-positions - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @param guild_id Guild ID to change the roles position on - * @param roles Vector of roles to change the positions of - * @return role_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role_map roles_edit_position_sync(snowflake guild_id, const std::vector &roles); - -/** - * @brief Get a role for a guild - * - * @see dpp::cluster::roles_get - * @see https://discord.com/developers/docs/resources/guild#get-guild-roles - * @param guild_id Guild ID to get role for - * @return role_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role_map roles_get_sync(snowflake guild_id); - -/** - * @brief Get the application's role connection metadata records - * - * @see dpp::cluster::application_role_connection_get - * @see https://discord.com/developers/docs/resources/application-role-connection-metadata#get-application-role-connection-metadata-records - * @param application_id The application ID - * @return application_role_connection returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection application_role_connection_get_sync(snowflake application_id); - -/** - * @brief Update the application's role connection metadata records - * - * @see dpp::cluster::application_role_connection_update - * @see https://discord.com/developers/docs/resources/application-role-connection-metadata#update-application-role-connection-metadata-records - * @param application_id The application ID - * @param connection_metadata The application role connection metadata to update - * @return application_role_connection returned object on completion - * @note An application can have a maximum of 5 metadata records. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection application_role_connection_update_sync(snowflake application_id, const std::vector &connection_metadata); - -/** - * @brief Get user application role connection - * - * @see dpp::cluster::user_application_role_connection_get - * @see https://discord.com/developers/docs/resources/user#get-user-application-role-connection - * @param application_id The application ID - * @return application_role_connection returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection user_application_role_connection_get_sync(snowflake application_id); - -/** - * @brief Update user application role connection - * - * @see dpp::cluster::user_application_role_connection_update - * @see https://discord.com/developers/docs/resources/user#update-user-application-role-connection - * @param application_id The application ID - * @param connection The application role connection to update - * @return application_role_connection returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection user_application_role_connection_update_sync(snowflake application_id, const application_role_connection &connection); - -/** - * @brief Get all scheduled events for a guild - * @see dpp::cluster::guild_events_get - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#list-scheduled-events-for-guild - * @param guild_id Guild to get events for - * @return scheduled_event_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event_map guild_events_get_sync(snowflake guild_id); - -/** - * @brief Create a scheduled event on a guild - * - * @see dpp::cluster::guild_event_create - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#create-guild-scheduled-event - * @param event Event to create (guild ID must be populated) - * @return scheduled_event returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_create_sync(const scheduled_event& event); - -/** - * @brief Delete a scheduled event from a guild - * - * @see dpp::cluster::guild_event_delete - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#delete-guild-scheduled-event - * @param event_id Event ID to delete - * @param guild_id Guild ID of event to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_event_delete_sync(snowflake event_id, snowflake guild_id); - -/** - * @brief Edit/modify a scheduled event on a guild - * - * @see dpp::cluster::guild_event_edit - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#modify-guild-scheduled-event - * @param event Event to create (event ID and guild ID must be populated) - * @return scheduled_event returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_edit_sync(const scheduled_event& event); - -/** - * @brief Get a scheduled event for a guild - * - * @see dpp::cluster::guild_event_get - * @see https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event - * @param guild_id Guild to get event for - * @param event_id Event ID to get - * @return scheduled_event returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_get_sync(snowflake guild_id, snowflake event_id); - -/** - * @brief Returns all SKUs for a given application. - * @note Because of how Discord's SKU and subscription systems work, you will see two SKUs for your premium offering. - * For integration and testing entitlements, you should use the SKU with type: 5. - * - * @see dpp::cluster::skus_get - * @see https://discord.com/developers/docs/monetization/skus#list-skus - * @return sku_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sku_map skus_get_sync(); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_create_sync(const stage_instance& si); - -/** - * @brief Get the stage instance associated with the channel id, if it exists. - * @see dpp::cluster::stage_instance_get - * @see https://discord.com/developers/docs/resources/stage-instance#get-stage-instance - * @param channel_id ID of the associated channel - * @return stage_instance returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_get_sync(const snowflake channel_id); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_edit_sync(const stage_instance& si); - -/** - * @brief Delete a stage instance. - * @see dpp::cluster::stage_instance_delete - * @see https://discord.com/developers/docs/resources/stage-instance#delete-stage-instance - * @param channel_id ID of the associated channel - * @return confirmation returned object on completion - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation stage_instance_delete_sync(const snowflake channel_id); - -/** - * @brief Create a sticker in a guild - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_sticker_create - * @see https://discord.com/developers/docs/resources/sticker#create-guild-sticker - * @param s Sticker to create. Must have its guild ID set. - * @return sticker returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_create_sync(const sticker &s); - -/** - * @brief Delete a sticker from a guild - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_sticker_delete - * @see https://discord.com/developers/docs/resources/sticker#delete-guild-sticker - * @param sticker_id sticker ID to delete - * @param guild_id guild ID to delete from - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_sticker_delete_sync(snowflake sticker_id, snowflake guild_id); - -/** - * @brief Get a guild sticker - * @see dpp::cluster::guild_sticker_get - * @see https://discord.com/developers/docs/resources/sticker#get-guild-sticker - * @param id Id of sticker to get. - * @param guild_id Guild ID of the guild where the sticker is - * @return sticker returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_get_sync(snowflake id, snowflake guild_id); - -/** - * @brief Modify a sticker in a guild - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::guild_sticker_modify - * @see https://discord.com/developers/docs/resources/sticker#modify-guild-sticker - * @param s Sticker to modify. Must have its guild ID and sticker ID set. - * @return sticker returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_modify_sync(const sticker &s); - -/** - * @brief Get all guild stickers - * @see dpp::cluster::guild_stickers_get - * @see https://discord.com/developers/docs/resources/sticker#list-guild-stickers - * @param guild_id Guild ID of the guild where the sticker is - * @return sticker_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker_map guild_stickers_get_sync(snowflake guild_id); - -/** - * @brief Get a nitro sticker - * @see dpp::cluster::nitro_sticker_get - * @see https://discord.com/developers/docs/resources/sticker#get-sticker - * @param id Id of sticker to get. - * @return sticker returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker nitro_sticker_get_sync(snowflake id); - -/** - * @brief Get a list of available sticker packs - * @see dpp::cluster::sticker_packs_get - * @see https://discord.com/developers/docs/resources/sticker#list-sticker-packs - * @return sticker_pack_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker_pack_map sticker_packs_get_sync(); - -/** - * @brief Create a new guild based on a template. - * @note This endpoint can be used only by bots in less than 10 guilds. - * @see dpp::cluster::guild_create_from_template - * @see https://discord.com/developers/docs/resources/guild-template#create-guild-from-guild-template - * @param code Template code to create guild from - * @param name Guild name to create - * @return guild returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_create_from_template_sync(const std::string &code, const std::string &name); - -/** - * @brief Creates a template for the guild - * - * @see dpp::cluster::guild_template_create - * @see https://discord.com/developers/docs/resources/guild-template#create-guild-template - * @param guild_id Guild to create template from - * @param name Template name to create - * @param description Description of template to create - * @return dtemplate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_create_sync(snowflake guild_id, const std::string &name, const std::string &description); - -/** - * @brief Deletes the template - * - * @see dpp::cluster::guild_template_delete - * @see https://discord.com/developers/docs/resources/guild-template#delete-guild-template - * @param guild_id Guild ID of template to delete - * @param code Template code to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_template_delete_sync(snowflake guild_id, const std::string &code); - -/** - * @brief Modifies the template's metadata. - * - * @see dpp::cluster::guild_template_modify - * @see https://discord.com/developers/docs/resources/guild-template#modify-guild-template - * @param guild_id Guild ID of template to modify - * @param code Template code to modify - * @param name New name of template - * @param description New description of template - * @return dtemplate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_modify_sync(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description); - -/** - * @brief Get guild templates - * - * @see dpp::cluster::guild_templates_get - * @see https://discord.com/developers/docs/resources/guild-template#get-guild-templates - * @param guild_id Guild ID to get templates for - * @return dtemplate_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate_map guild_templates_get_sync(snowflake guild_id); - -/** - * @brief Syncs the template to the guild's current state. - * - * @see dpp::cluster::guild_template_sync - * @see https://discord.com/developers/docs/resources/guild-template#sync-guild-template - * @param guild_id Guild to synchronise template for - * @param code Code of template to synchronise - * @return dtemplate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_sync_sync(snowflake guild_id, const std::string &code); - -/** - * @brief Get a template - * @see dpp::cluster::template_get - * @see https://discord.com/developers/docs/resources/guild-template#get-guild-template - * @param code Template code - * @return dtemplate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate template_get_sync(const std::string &code); - -/** - * @brief Join a thread - * @see dpp::cluster::current_user_join_thread - * @see https://discord.com/developers/docs/resources/channel#join-thread - * @param thread_id Thread ID to join - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_join_thread_sync(snowflake thread_id); - -/** - * @brief Leave a thread - * @see dpp::cluster::current_user_leave_thread - * @see https://discord.com/developers/docs/resources/channel#leave-thread - * @param thread_id Thread ID to leave - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_leave_thread_sync(snowflake thread_id); - -/** - * @brief Get all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order. - * @see dpp::cluster::threads_get_active - * @see https://discord.com/developers/docs/resources/guild#list-active-guild-threads - * @param guild_id Guild to get active threads for - * @return active_threads returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") active_threads threads_get_active_sync(snowflake guild_id); - -/** - * @brief Get private archived threads in a channel which current user has joined (Sorted by ID in descending order) - * @see dpp::cluster::threads_get_joined_private_archived - * @see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads - * @param channel_id Channel to get public archived threads for - * @param before_id Get threads before this id - * @param limit Number of threads to get - * @return thread_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_joined_private_archived_sync(snowflake channel_id, snowflake before_id, uint16_t limit); - -/** - * @brief Get private archived threads in a channel (Sorted by archive_timestamp in descending order) - * @see dpp::cluster::threads_get_private_archived - * @see https://discord.com/developers/docs/resources/channel#list-private-archived-threads - * @param channel_id Channel to get public archived threads for - * @param before_timestamp Get threads archived before this timestamp - * @param limit Number of threads to get - * @return thread_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_private_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit); - -/** - * @brief Get public archived threads in a channel (Sorted by archive_timestamp in descending order) - * @see dpp::cluster::threads_get_public_archived - * @see https://discord.com/developers/docs/resources/channel#list-public-archived-threads - * @param channel_id Channel to get public archived threads for - * @param before_timestamp Get threads archived before this timestamp - * @param limit Number of threads to get - * @return thread_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_public_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit); - -/** - * @brief Get a thread member - * @see dpp::cluster::thread_member_get - * @see https://discord.com/developers/docs/resources/channel#get-thread-member - * @param thread_id Thread to get member for - * @param user_id ID of the user to get - * @return thread_member returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_member thread_member_get_sync(const snowflake thread_id, const snowflake user_id); - -/** - * @brief Get members of a thread - * @see dpp::cluster::thread_members_get - * @see https://discord.com/developers/docs/resources/channel#list-thread-members - * @param thread_id Thread to get members for - * @return thread_member_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_member_map thread_members_get_sync(snowflake thread_id); - -/** - * @brief Create a thread in a forum or media channel - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::thread_create_in_forum - * @see https://discord.com/developers/docs/resources/channel#start-thread-in-forum-channel - * @param thread_name Name of the forum thread - * @param channel_id Forum channel in which thread to create - * @param msg The message to start the thread with - * @param auto_archive_duration Duration to automatically archive the thread after recent activity - * @param rate_limit_per_user amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages, manage_thread, or manage_channel, are unaffected - * @param applied_tags List of IDs of forum tags (dpp::forum_tag) to apply to this thread - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_in_forum_sync(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags = {}); - -/** - * @brief Create a thread - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::thread_create - * @see https://discord.com/developers/docs/resources/channel#start-thread-without-message - * @param thread_name Name of the thread - * @param channel_id Channel in which thread to create - * @param auto_archive_duration Duration after which thread auto-archives. Can be set to - 60, 1440 (for boosted guilds can also be: 4320, 10080) - * @param thread_type Type of thread - CHANNEL_PUBLIC_THREAD, CHANNEL_ANNOUNCEMENT_THREAD, CHANNEL_PRIVATE_THREAD - * @param invitable whether non-moderators can add other non-moderators to a thread; only available when creating a private thread - * @param rate_limit_per_user amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages, manage_thread, or manage_channel, are unaffected - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_sync(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user); - -/** - * @brief Edit a thread - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * - * @see dpp::cluster::thread_edit - * @see https://discord.com/developers/docs/topics/threads#editing-deleting-threads - * @param t Thread to edit - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_edit_sync(const thread &t); - -/** - * @brief Create a thread with a message (Discord: ID of a thread is same as message ID) - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::thread_create_with_message - * @see https://discord.com/developers/docs/resources/channel#start-thread-from-message - * @param thread_name Name of the thread - * @param channel_id Channel in which thread to create - * @param message_id message to start thread with - * @param auto_archive_duration Duration after which thread auto-archives. Can be set to - 60, 1440 (for boosted guilds can also be: 4320, 10080) - * @param rate_limit_per_user amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages, manage_thread, or manage_channel, are unaffected - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_with_message_sync(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user); - -/** - * @brief Add a member to a thread - * @see dpp::cluster::thread_member_add - * @see https://discord.com/developers/docs/resources/channel#add-thread-member - * @param thread_id Thread ID to add to - * @param user_id Member ID to add - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation thread_member_add_sync(snowflake thread_id, snowflake user_id); - -/** - * @brief Remove a member from a thread - * @see dpp::cluster::thread_member_remove - * @see https://discord.com/developers/docs/resources/channel#remove-thread-member - * @param thread_id Thread ID to remove from - * @param user_id Member ID to remove - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation thread_member_remove_sync(snowflake thread_id, snowflake user_id); - -/** - * @brief Get the thread specified by thread_id. This uses the same call as dpp::cluster::channel_get but returns a thread object. - * @see dpp::cluster::thread_get - * @see https://discord.com/developers/docs/resources/channel#get-channel - * @param thread_id The id of the thread to obtain. - * @return thread returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_get_sync(snowflake thread_id); - -/** - * @brief Edit current (bot) user. - * - * Modify the requester's user account settings. Returns a dpp::user object on success. - * Fires a User Update Gateway event. - * - * @note There appears to be no limit to the image size, however, if your image cannot be processed/uploaded in time, you will receive a malformed http request. - * - * @see dpp::cluster::current_user_edit - * @see https://discord.com/developers/docs/resources/user#modify-current-user - * @param nickname Nickname to set - * @param avatar_blob Avatar data to upload - * @param avatar_type Type of image for avatar. It can be one of `i_gif`, `i_jpg` or `i_png`. - * @param banner_blob Banner data to upload - * @param banner_type Type of image for Banner. It can be one of `i_gif`, `i_jpg` or `i_png`. - * @return user returned object on completion - * @throw dpp::length_exception Image data is larger than the maximum size of 256 kilobytes - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user current_user_edit_sync(const std::string &nickname, const std::string& avatar_blob = "", const image_type avatar_type = i_png, const std::string& banner_blob = "", const image_type banner_type = i_png); - -/** - * @brief Get current (bot) application - * - * @see dpp::cluster::current_application_get - * @see https://discord.com/developers/docs/topics/oauth2#get-current-bot-application-information - * @return application returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application current_application_get_sync(); - -/** - * @brief Get current (bot) user - * - * @see dpp::cluster::current_user_get - * @see https://discord.com/developers/docs/resources/user#get-current-user - * @return user_identified returned object on completion - * @note The user_identified object is a subclass of dpp::user which contains further details if you have the oauth2 identify or email scopes. - * If you do not have these scopes, these fields are empty. You can safely convert a user_identified to user with `dynamic_cast`. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified current_user_get_sync(); - -/** - * @brief Set the bot's voice state on a stage channel - * - * **Caveats** - * - * There are currently several caveats for this endpoint: - * - * - `channel_id` must currently point to a stage channel. - * - current user must already have joined `channel_id`. - * - You must have the `MUTE_MEMBERS` permission to unsuppress yourself. You can always suppress yourself. - * - You must have the `REQUEST_TO_SPEAK` permission to request to speak. You can always clear your own request to speak. - * - You are able to set `request_to_speak_timestamp` to any present or future time. - * - * @see dpp::cluster::current_user_set_voice_state - * @see https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state - * @param guild_id Guild to set voice state on - * @param channel_id Stage channel to set voice state on - * @return confirmation returned object on completion - * @param suppress True if the user's audio should be suppressed, false if it should not - * @param request_to_speak_timestamp The time at which we requested to speak, or 0 to clear the request. The time set here must be the current time or in the future. - * @throw std::logic_exception You attempted to set a request_to_speak_timestamp in the past which is not the value of 0. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_set_voice_state_sync(snowflake guild_id, snowflake channel_id, bool suppress = false, time_t request_to_speak_timestamp = 0); - -/** - * @brief Get the bot's voice state in a guild without a Gateway connection - * - * @see dpp::cluster::current_user_get_voice_state - * @see https://discord.com/developers/docs/resources/voice#get-current-user-voice-state - * @param guild_id Guild to get the voice state for - * @return voicestate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voicestate current_user_get_voice_state_sync(snowflake guild_id); - -/** - * @brief Set a user's voice state on a stage channel - * - * **Caveats** - * - * There are currently several caveats for this endpoint: - * - * - `channel_id` must currently point to a stage channel. - * - User must already have joined `channel_id`. - * - You must have the `MUTE_MEMBERS` permission. (Since suppression is the only thing that is available currently) - * - When unsuppressed, non-bot users will have their `request_to_speak_timestamp` set to the current time. Bot users will not. - * - When suppressed, the user will have their `request_to_speak_timestamp` removed. - * - * @see dpp::cluster::user_set_voice_state - * @see https://discord.com/developers/docs/resources/guild#modify-user-voice-state - * @param user_id The user to set the voice state of - * @param guild_id Guild to set voice state on - * @param channel_id Stage channel to set voice state on - * @return confirmation returned object on completion - * @param suppress True if the user's audio should be suppressed, false if it should not - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation user_set_voice_state_sync(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress = false); - -/** - * @brief Get a user's voice state in a guild without a Gateway connection - * - * @see dpp::cluster::user_get_voice_state - * @see https://discord.com/developers/docs/resources/voice#get-user-voice-state - * @param guild_id Guild to get the voice state for - * @param user_id The user to get the voice state of - * @return voicestate returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voicestate user_get_voice_state_sync(snowflake guild_id, snowflake user_id); - -/** - * @brief Get current user's connections (linked accounts, e.g. steam, xbox). - * This call requires the oauth2 `connections` scope and cannot be executed - * against a bot token. - * @see dpp::cluster::current_user_connections_get - * @see https://discord.com/developers/docs/resources/user#get-user-connections - * @return connection_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") connection_map current_user_connections_get_sync(); - -/** - * @brief Get current (bot) user guilds - * @see dpp::cluster::current_user_get_guilds - * @see https://discord.com/developers/docs/resources/user#get-current-user-guilds - * @return guild_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_map current_user_get_guilds_sync(); - -/** - * @brief Leave a guild - * @see dpp::cluster::current_user_leave_guild - * @see https://discord.com/developers/docs/resources/user#leave-guild - * @param guild_id Guild ID to leave - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_leave_guild_sync(snowflake guild_id); - -/** - * @brief Get a user by id, without using the cache - * - * @see dpp::cluster::user_get - * @see https://discord.com/developers/docs/resources/user#get-user - * @param user_id User ID to retrieve - * @return user_identified returned object on completion - * @note The user_identified object is a subclass of dpp::user which contains further details if you have the oauth2 identify or email scopes. - * If you do not have these scopes, these fields are empty. You can safely convert a user_identified to user with `dynamic_cast`. - * @note unless you want something special from `dpp::user_identified` or you've turned off caching, you have no need to call this. - * Call `dpp::find_user` instead that looks up the user in the cache rather than a REST call. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified user_get_sync(snowflake user_id); - -/** - * @brief Get a user by id, checking in the cache first - * - * @see dpp::cluster::user_get_cached - * @see https://discord.com/developers/docs/resources/user#get-user - * @param user_id User ID to retrieve - * @return user_identified returned object on completion - * @note The user_identified object is a subclass of dpp::user which contains further details if you have the oauth2 identify or email scopes. - * If you do not have these scopes, these fields are empty. You can safely convert a user_identified to user with `dynamic_cast`. - * @note If the user is found in the cache, special values set in `dpp::user_identified` will be undefined. This call should be used - * where you want to for example resolve a user who may no longer be in the bot's guilds, for something like a ban log message. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified user_get_cached_sync(snowflake user_id); - -/** - * @brief Get all voice regions - * @see dpp::cluster::get_voice_regions - * @see https://discord.com/developers/docs/resources/voice#list-voice-regions - * @return voiceregion_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voiceregion_map get_voice_regions_sync(); - -/** - * @brief Get guild voice regions. - * - * Voice regions per guild are somewhat deprecated in preference of per-channel voice regions. - * Returns a list of voice region objects for the guild. Unlike the similar /voice route, this returns VIP servers when - * the guild is VIP-enabled. - * - * @see dpp::cluster::guild_get_voice_regions - * @see https://discord.com/developers/docs/resources/guild#get-guild-voice-regions - * @param guild_id Guild ID to get voice regions for - * @return voiceregion_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voiceregion_map guild_get_voice_regions_sync(snowflake guild_id); - - -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook create_webhook_sync(const class webhook &wh); - -/** - * @brief Delete a webhook - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::delete_webhook - * @see https://discord.com/developers/docs/resources/webhook#delete-webhook - * @param webhook_id Webhook ID to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_sync(snowflake webhook_id); - -/** - * @brief Delete webhook message - * - * @see dpp::cluster::delete_webhook_message - * @see https://discord.com/developers/docs/resources/webhook#delete-webhook-message - * @param wh Webhook to delete message for - * @param message_id Message ID to delete - * @param thread_id ID of the thread the message is in - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); - -/** - * @brief Delete webhook with token - * @see dpp::cluster::delete_webhook_with_token - * @see https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token - * @param webhook_id Webhook ID to delete - * @param token Token of webhook to delete - * @return confirmation returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_with_token_sync(snowflake webhook_id, const std::string &token); - -/** - * @brief Edit webhook - * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. - * @see dpp::cluster::edit_webhook - * @see https://discord.com/developers/docs/resources/webhook#modify-webhook - * @param wh Webhook to edit - * @return webhook returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook edit_webhook_sync(const class webhook& wh); - -/** - * @brief Edit webhook message - * - * When the content field is edited, the mentions array in the message object will be reconstructed from scratch based on - * the new content. The allowed_mentions field of the edit request controls how this happens. If there is no explicit - * allowed_mentions in the edit request, the content will be parsed with default allowances, that is, without regard to - * whether or not an allowed_mentions was present in the request that originally created the message. - * - * @see dpp::cluster::edit_webhook_message - * @see https://discord.com/developers/docs/resources/webhook#edit-webhook-message - * @note the attachments array must contain all attachments that should be present after edit, including retained and new attachments provided in the request body. - * @param wh Webhook to edit message for - * @param m New message - * @param thread_id ID of the thread the message is in - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message edit_webhook_message_sync(const class webhook &wh, const struct message &m, snowflake thread_id = 0); - -/** - * @brief Edit webhook with token (token is encapsulated in the webhook object) - * @see dpp::cluster::edit_webhook_with_token - * @see https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token - * @param wh Webhook to edit (should include token) - * @return webhook returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook edit_webhook_with_token_sync(const class webhook& wh); - -/** - * @brief Execute webhook - * - * @see dpp::cluster::execute_webhook - * @see https://discord.com/developers/docs/resources/webhook#execute-webhook - * @param wh Webhook to execute - * @param m Message to send - * @param wait waits for server confirmation of message send before response, and returns the created message body - * @param thread_id Send a message to the specified thread within a webhook's channel. The thread will automatically be unarchived - * @param thread_name Name of thread to create (requires the webhook channel to be a forum channel) - * @return message returned object on completion - * @note If the webhook channel is a forum channel, you must provide either `thread_id` or `thread_name`. If `thread_id` is provided, the message will send in that thread. If `thread_name` is provided, a thread with that name will be created in the forum channel. - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message execute_webhook_sync(const class webhook &wh, const struct message &m, bool wait = false, snowflake thread_id = 0, const std::string& thread_name = ""); - -/** - * @brief Get channel webhooks - * @see dpp::cluster::get_channel_webhooks - * @see https://discord.com/developers/docs/resources/webhook#get-guild-webhooks - * @param channel_id Channel ID to get webhooks for - * @return webhook_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook_map get_channel_webhooks_sync(snowflake channel_id); - -/** - * @brief Get guild webhooks - * @see dpp::cluster::get_guild_webhooks - * @see https://discord.com/developers/docs/resources/webhook#get-guild-webhooks - * @param guild_id Guild ID to get webhooks for - * @return webhook_map returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook_map get_guild_webhooks_sync(snowflake guild_id); - -/** - * @brief Get webhook - * @see dpp::cluster::get_webhook - * @see https://discord.com/developers/docs/resources/webhook#get-webhook - * @param webhook_id Webhook ID to get - * @return webhook returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook get_webhook_sync(snowflake webhook_id); - -/** - * @brief Get webhook message - * - * @see dpp::cluster::get_webhook_message - * @see https://discord.com/developers/docs/resources/webhook#get-webhook-message - * @param wh Webhook to get the original message for - * @param message_id The message ID - * @param thread_id ID of the thread the message is in - * @return message returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message get_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); - -/** - * @brief Get webhook using token - * @see dpp::cluster::get_webhook_with_token - * @see https://discord.com/developers/docs/resources/webhook#get-webhook-with-token - * @param webhook_id Webhook ID to retrieve - * @param token Token of webhook - * @return webhook returned object on completion - * \memberof dpp::cluster - * @throw dpp::rest_exception upon failure to execute REST function - * @deprecated This function is deprecated, please use coroutines instead. - * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. - * Avoid direct use of this function inside an event handler. - */ -DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook get_webhook_with_token_sync(snowflake webhook_id, const std::string &token); - - -/* End of auto-generated definitions */ diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 4d1933ae0c..d7905a11d1 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -122,12 +122,6 @@ if (UNIX) if (${HAS_PHP} STREQUAL "0") message("-- Checking for update to autogenerated files") - # target for rebuild of cluster::*_sync() functions - execute_process( - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." - COMMAND php buildtools/make_struct.php "\\Dpp\\Generator\\SyncGenerator" - ) - set_source_files_properties( "${CMAKE_CURRENT_SOURCE_DIR}/../include/dpp/cluster_sync_calls.h" PROPERTIES GENERATED TRUE ) # target for unicode_emojis.h execute_process( WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index d562af8837..9d551a30f1 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -311,23 +311,16 @@ void cluster::start(start_type return_after) { if (g.session_start_remaining < g.shards || g.shards == 0) { throw_if_not_threaded(err_no_sessions_left, "Discord indicates you cannot start enough sessions to boot this cluster! Cluster startup aborted. Try again later."); return; - } else - if (g.session_start_max_concurrency == 0) { - throw_if_not_threaded(err_auto_shard, "Cluster: Could not determine concurrency, startup aborted!"); - return; - } else - if (g.session_start_max_concurrency > 1) { - log(ll_debug, "Cluster: Large bot sharding; Using session concurrency: " + std::to_string(g.session_start_max_concurrency)); - } else - if (numshards == 0) { - if (g.shards) { - log(ll_info, "Auto Shard: Bot requires " + std::to_string(g.shards) + std::string(" shard") + ((g.shards > 1) ? "s" : "")); - } else { - throw_if_not_threaded(err_auto_shard, "Auto Shard: Cannot determine number of shards. Cluster startup aborted. Check your connection."); - return; - } - numshards = g.shards; - } + } else if (g.session_start_max_concurrency == 0) { + throw_if_not_threaded(err_auto_shard, "Cluster: Could not determine concurrency, startup aborted!"); + return; + } else if (g.session_start_max_concurrency > 1) { + log(ll_debug, "Cluster: Large bot sharding; Using session concurrency: " + std::to_string(g.session_start_max_concurrency)); + } + if (numshards == 0) { + log(ll_info, "Auto Shard: Bot requires " + std::to_string(g.shards) + std::string(" shard") + ((g.shards > 1) ? "s" : "")); + numshards = g.shards; + } log(ll_debug, "Starting with " + std::to_string(numshards) + " shards..."); start_time = time(nullptr); diff --git a/src/dpp/cluster_sync_calls.cpp b/src/dpp/cluster_sync_calls.cpp deleted file mode 100644 index c49c9d9a3e..0000000000 --- a/src/dpp/cluster_sync_calls.cpp +++ /dev/null @@ -1,879 +0,0 @@ -/************************************************************************************ - * - * D++, A Lightweight C++ library for Discord - * - * Copyright 2022 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. - * - ************************************************************************************/ - - -/* Auto @generated by buildtools/make_sync_struct.php. - * - * DO NOT EDIT BY HAND! - * - * To re-generate this header file re-run the script! - */ - -#include -#include -#include - -namespace dpp { - -slashcommand_map cluster::global_bulk_command_create_sync(const std::vector &commands) { - return dpp::sync(this, static_cast &, command_completion_event_t)>(&cluster::global_bulk_command_create), commands); -} - -slashcommand_map cluster::global_bulk_command_delete_sync() { - return dpp::sync(this, static_cast(&cluster::global_bulk_command_delete)); -} - -slashcommand cluster::global_command_create_sync(const slashcommand &s) { - return dpp::sync(this, static_cast(&cluster::global_command_create), s); -} - -slashcommand cluster::global_command_get_sync(snowflake id) { - return dpp::sync(this, static_cast(&cluster::global_command_get), id); -} - -confirmation cluster::global_command_delete_sync(snowflake id) { - return dpp::sync(this, static_cast(&cluster::global_command_delete), id); -} - -confirmation cluster::global_command_edit_sync(const slashcommand &s) { - return dpp::sync(this, static_cast(&cluster::global_command_edit), s); -} - -slashcommand_map cluster::global_commands_get_sync() { - return dpp::sync(this, static_cast(&cluster::global_commands_get)); -} - -slashcommand_map cluster::guild_bulk_command_create_sync(const std::vector &commands, snowflake guild_id) { - return dpp::sync(this, static_cast &, snowflake, command_completion_event_t)>(&cluster::guild_bulk_command_create), commands, guild_id); -} - -slashcommand_map cluster::guild_bulk_command_delete_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_bulk_command_delete), guild_id); -} - -guild_command_permissions_map cluster::guild_commands_get_permissions_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_commands_get_permissions), guild_id); -} - -guild_command_permissions_map cluster::guild_bulk_command_edit_permissions_sync(const std::vector &commands, snowflake guild_id) { - return dpp::sync(this, static_cast &, snowflake, command_completion_event_t)>(&cluster::guild_bulk_command_edit_permissions), commands, guild_id); -} - -slashcommand cluster::guild_command_create_sync(const slashcommand &s, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_create), s, guild_id); -} - -confirmation cluster::guild_command_delete_sync(snowflake id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_delete), id, guild_id); -} - -confirmation cluster::guild_command_edit_permissions_sync(const slashcommand &s, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_edit_permissions), s, guild_id); -} - -slashcommand cluster::guild_command_get_sync(snowflake id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_get), id, guild_id); -} - -guild_command_permissions cluster::guild_command_get_permissions_sync(snowflake id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_get_permissions), id, guild_id); -} - -confirmation cluster::guild_command_edit_sync(const slashcommand &s, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_command_edit), s, guild_id); -} - -slashcommand_map cluster::guild_commands_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_commands_get), guild_id); -} - -confirmation cluster::interaction_response_create_sync(snowflake interaction_id, const std::string &token, const interaction_response &r) { - return dpp::sync(this, static_cast(&cluster::interaction_response_create), interaction_id, token, r); -} - -confirmation cluster::interaction_response_edit_sync(const std::string &token, const message &m) { - return dpp::sync(this, static_cast(&cluster::interaction_response_edit), token, m); -} - -message cluster::interaction_response_get_original_sync(const std::string &token) { - return dpp::sync(this, static_cast(&cluster::interaction_response_get_original), token); -} - -confirmation cluster::interaction_followup_create_sync(const std::string &token, const message &m) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_create), token, m); -} - -confirmation cluster::interaction_followup_edit_original_sync(const std::string &token, const message &m) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_edit_original), token, m); -} - -confirmation cluster::interaction_followup_delete_sync(const std::string &token) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_delete), token); -} - -confirmation cluster::interaction_followup_edit_sync(const std::string &token, const message &m) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_edit), token, m); -} - -message cluster::interaction_followup_get_sync(const std::string &token, snowflake message_id) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_get), token, message_id); -} - -message cluster::interaction_followup_get_original_sync(const std::string &token) { - return dpp::sync(this, static_cast(&cluster::interaction_followup_get_original), token); -} - -automod_rule_map cluster::automod_rules_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::automod_rules_get), guild_id); -} - -automod_rule cluster::automod_rule_get_sync(snowflake guild_id, snowflake rule_id) { - return dpp::sync(this, static_cast(&cluster::automod_rule_get), guild_id, rule_id); -} - -automod_rule cluster::automod_rule_create_sync(snowflake guild_id, const automod_rule& r) { - return dpp::sync(this, static_cast(&cluster::automod_rule_create), guild_id, r); -} - -automod_rule cluster::automod_rule_edit_sync(snowflake guild_id, const automod_rule& r) { - return dpp::sync(this, static_cast(&cluster::automod_rule_edit), guild_id, r); -} - -confirmation cluster::automod_rule_delete_sync(snowflake guild_id, snowflake rule_id) { - return dpp::sync(this, static_cast(&cluster::automod_rule_delete), guild_id, rule_id); -} - -channel cluster::channel_create_sync(const class channel &c) { - return dpp::sync(this, static_cast(&cluster::channel_create), c); -} - -confirmation cluster::channel_delete_permission_sync(const class channel &c, snowflake overwrite_id) { - return dpp::sync(this, static_cast(&cluster::channel_delete_permission), c, overwrite_id); -} - -confirmation cluster::channel_delete_sync(snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::channel_delete), channel_id); -} - -confirmation cluster::channel_edit_permissions_sync(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return dpp::sync(this, static_cast(&cluster::channel_edit_permissions), c, overwrite_id, allow, deny, member); -} - -confirmation cluster::channel_edit_permissions_sync(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return dpp::sync(this, static_cast(&cluster::channel_edit_permissions), channel_id, overwrite_id, allow, deny, member); -} - -confirmation cluster::channel_edit_positions_sync(const std::vector &c) { - return dpp::sync(this, static_cast &, command_completion_event_t)>(&cluster::channel_edit_positions), c); -} - -channel cluster::channel_edit_sync(const class channel &c) { - return dpp::sync(this, static_cast(&cluster::channel_edit), c); -} - -confirmation cluster::channel_follow_news_sync(const class channel &c, snowflake target_channel_id) { - return dpp::sync(this, static_cast(&cluster::channel_follow_news), c, target_channel_id); -} - -channel cluster::channel_get_sync(snowflake c) { - return dpp::sync(this, static_cast(&cluster::channel_get), c); -} - -invite cluster::channel_invite_create_sync(const class channel &c, const class invite &i) { - return dpp::sync(this, static_cast(&cluster::channel_invite_create), c, i); -} - -invite_map cluster::channel_invites_get_sync(const class channel &c) { - return dpp::sync(this, static_cast(&cluster::channel_invites_get), c); -} - -confirmation cluster::channel_typing_sync(const class channel &c) { - return dpp::sync(this, static_cast(&cluster::channel_typing), c); -} - -confirmation cluster::channel_typing_sync(snowflake cid) { - return dpp::sync(this, static_cast(&cluster::channel_typing), cid); -} - -channel_map cluster::channels_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::channels_get), guild_id); -} - -confirmation cluster::channel_set_voice_status_sync(snowflake channel_id, const std::string& status) { - return dpp::sync(this, static_cast(&cluster::channel_set_voice_status), channel_id, status); -} - -channel cluster::create_dm_channel_sync(snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::create_dm_channel), user_id); -} - -channel_map cluster::current_user_get_dms_sync() { - return dpp::sync(this, static_cast(&cluster::current_user_get_dms)); -} - -message cluster::direct_message_create_sync(snowflake user_id, const message &m) { - return dpp::sync(this, static_cast(&cluster::direct_message_create), user_id, m); -} - -confirmation cluster::gdm_add_sync(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick) { - return dpp::sync(this, static_cast(&cluster::gdm_add), channel_id, user_id, access_token, nick); -} - -confirmation cluster::gdm_remove_sync(snowflake channel_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::gdm_remove), channel_id, user_id); -} - -emoji cluster::guild_emoji_create_sync(snowflake guild_id, const class emoji& newemoji) { - return dpp::sync(this, static_cast(&cluster::guild_emoji_create), guild_id, newemoji); -} - -confirmation cluster::guild_emoji_delete_sync(snowflake guild_id, snowflake emoji_id) { - return dpp::sync(this, static_cast(&cluster::guild_emoji_delete), guild_id, emoji_id); -} - -emoji cluster::guild_emoji_edit_sync(snowflake guild_id, const class emoji& newemoji) { - return dpp::sync(this, static_cast(&cluster::guild_emoji_edit), guild_id, newemoji); -} - -emoji cluster::guild_emoji_get_sync(snowflake guild_id, snowflake emoji_id) { - return dpp::sync(this, static_cast(&cluster::guild_emoji_get), guild_id, emoji_id); -} - -emoji_map cluster::guild_emojis_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_emojis_get), guild_id); -} - -emoji_map cluster::application_emojis_get_sync() { - return dpp::sync(this, static_cast(&cluster::application_emojis_get)); -} - -emoji cluster::application_emoji_get_sync(snowflake emoji_id) { - return dpp::sync(this, static_cast(&cluster::application_emoji_get), emoji_id); -} - -emoji cluster::application_emoji_create_sync(const class emoji& newemoji) { - return dpp::sync(this, static_cast(&cluster::application_emoji_create), newemoji); -} - -emoji cluster::application_emoji_edit_sync(const class emoji& newemoji) { - return dpp::sync(this, static_cast(&cluster::application_emoji_edit), newemoji); -} - -confirmation cluster::application_emoji_delete_sync(snowflake emoji_id) { - return dpp::sync(this, static_cast(&cluster::application_emoji_delete), emoji_id); -} - -entitlement_map cluster::entitlements_get_sync(snowflake user_id, const std::vector& sku_ids, snowflake before_id, snowflake after_id, uint8_t limit, snowflake guild_id, bool exclude_ended) { - return dpp::sync(this, static_cast&, snowflake, snowflake, uint8_t, snowflake, bool, command_completion_event_t)>(&cluster::entitlements_get), user_id, sku_ids, before_id, after_id, limit, guild_id, exclude_ended); -} - -entitlement cluster::entitlement_test_create_sync(const class entitlement& new_entitlement) { - return dpp::sync(this, static_cast(&cluster::entitlement_test_create), new_entitlement); -} - -confirmation cluster::entitlement_test_delete_sync(const class snowflake entitlement_id) { - return dpp::sync(this, static_cast(&cluster::entitlement_test_delete), entitlement_id); -} - -confirmation cluster::entitlement_consume_sync(const class snowflake entitlement_id) { - return dpp::sync(this, static_cast(&cluster::entitlement_consume), entitlement_id); -} - -gateway cluster::get_gateway_bot_sync() { - return dpp::sync(this, static_cast(&cluster::get_gateway_bot)); -} - -confirmation cluster::guild_current_member_edit_sync(snowflake guild_id, const std::string &nickname) { - return dpp::sync(this, static_cast(&cluster::guild_current_member_edit), guild_id, nickname); -} - -auditlog cluster::guild_auditlog_get_sync(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit) { - return dpp::sync(this, static_cast(&cluster::guild_auditlog_get), guild_id, user_id, action_type, before, after, limit); -} - -confirmation cluster::guild_ban_add_sync(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds) { - return dpp::sync(this, static_cast(&cluster::guild_ban_add), guild_id, user_id, delete_message_seconds); -} - -confirmation cluster::guild_ban_delete_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_ban_delete), guild_id, user_id); -} - -guild cluster::guild_create_sync(const class guild &g) { - return dpp::sync(this, static_cast(&cluster::guild_create), g); -} - -confirmation cluster::guild_delete_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_delete), guild_id); -} - -confirmation cluster::guild_delete_integration_sync(snowflake guild_id, snowflake integration_id) { - return dpp::sync(this, static_cast(&cluster::guild_delete_integration), guild_id, integration_id); -} - -guild cluster::guild_edit_sync(const class guild &g) { - return dpp::sync(this, static_cast(&cluster::guild_edit), g); -} - -guild_widget cluster::guild_edit_widget_sync(snowflake guild_id, const class guild_widget &gw) { - return dpp::sync(this, static_cast(&cluster::guild_edit_widget), guild_id, gw); -} - -ban cluster::guild_get_ban_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_ban), guild_id, user_id); -} - -ban_map cluster::guild_get_bans_sync(snowflake guild_id, snowflake before, snowflake after, snowflake limit) { - return dpp::sync(this, static_cast(&cluster::guild_get_bans), guild_id, before, after, limit); -} - -guild cluster::guild_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get), guild_id); -} - -integration_map cluster::guild_get_integrations_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_integrations), guild_id); -} - -guild cluster::guild_get_preview_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_preview), guild_id); -} - -invite cluster::guild_get_vanity_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_vanity), guild_id); -} - -guild_widget cluster::guild_get_widget_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_widget), guild_id); -} - -confirmation cluster::guild_modify_integration_sync(snowflake guild_id, const class integration &i) { - return dpp::sync(this, static_cast(&cluster::guild_modify_integration), guild_id, i); -} - -prune cluster::guild_get_prune_counts_sync(snowflake guild_id, const struct prune& pruneinfo) { - return dpp::sync(this, static_cast(&cluster::guild_get_prune_counts), guild_id, pruneinfo); -} - -prune cluster::guild_begin_prune_sync(snowflake guild_id, const struct prune& pruneinfo) { - return dpp::sync(this, static_cast(&cluster::guild_begin_prune), guild_id, pruneinfo); -} - -confirmation cluster::guild_set_nickname_sync(snowflake guild_id, const std::string &nickname) { - return dpp::sync(this, static_cast(&cluster::guild_set_nickname), guild_id, nickname); -} - -confirmation cluster::guild_sync_integration_sync(snowflake guild_id, snowflake integration_id) { - return dpp::sync(this, static_cast(&cluster::guild_sync_integration), guild_id, integration_id); -} - -onboarding cluster::guild_get_onboarding_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_onboarding), guild_id); -} - -onboarding cluster::guild_edit_onboarding_sync(const struct onboarding& o) { - return dpp::sync(this, static_cast(&cluster::guild_edit_onboarding), o); -} - -dpp::welcome_screen cluster::guild_get_welcome_screen_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_welcome_screen), guild_id); -} - -dpp::welcome_screen cluster::guild_edit_welcome_screen_sync(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled) { - return dpp::sync(this, static_cast(&cluster::guild_edit_welcome_screen), guild_id, welcome_screen, enabled); -} - -confirmation cluster::guild_add_member_sync(const guild_member& gm, const std::string &access_token) { - return dpp::sync(this, static_cast(&cluster::guild_add_member), gm, access_token); -} - -guild_member cluster::guild_edit_member_sync(const guild_member& gm) { - return dpp::sync(this, static_cast(&cluster::guild_edit_member), gm); -} - -guild_member cluster::guild_get_member_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_member), guild_id, user_id); -} - -guild_member_map cluster::guild_get_members_sync(snowflake guild_id, uint16_t limit, snowflake after) { - return dpp::sync(this, static_cast(&cluster::guild_get_members), guild_id, limit, after); -} - -confirmation cluster::guild_member_add_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_add_role), guild_id, user_id, role_id); -} - -confirmation cluster::guild_member_delete_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_delete), guild_id, user_id); -} - -confirmation cluster::guild_member_kick_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_kick), guild_id, user_id); -} - -confirmation cluster::guild_member_timeout_sync(snowflake guild_id, snowflake user_id, time_t communication_disabled_until) { - return dpp::sync(this, static_cast(&cluster::guild_member_timeout), guild_id, user_id, communication_disabled_until); -} - -confirmation cluster::guild_member_timeout_remove_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_timeout_remove), guild_id, user_id); -} - -confirmation cluster::guild_member_delete_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_delete_role), guild_id, user_id, role_id); -} - -confirmation cluster::guild_member_remove_role_sync(snowflake guild_id, snowflake user_id, snowflake role_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_remove_role), guild_id, user_id, role_id); -} - -guild_member cluster::guild_member_move_sync(const snowflake channel_id, const snowflake guild_id, const snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::guild_member_move), channel_id, guild_id, user_id); -} - -guild_member_map cluster::guild_search_members_sync(snowflake guild_id, const std::string& query, uint16_t limit) { - return dpp::sync(this, static_cast(&cluster::guild_search_members), guild_id, query, limit); -} - -invite_map cluster::guild_get_invites_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_invites), guild_id); -} - -invite cluster::invite_delete_sync(const std::string &invitecode) { - return dpp::sync(this, static_cast(&cluster::invite_delete), invitecode); -} - -invite cluster::invite_get_sync(const std::string &invite_code) { - return dpp::sync(this, static_cast(&cluster::invite_get), invite_code); -} - -confirmation cluster::message_add_reaction_sync(const struct message &m, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_add_reaction), m, reaction); -} - -confirmation cluster::message_add_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_add_reaction), message_id, channel_id, reaction); -} - -message cluster::message_create_sync(const message &m) { - return dpp::sync(this, static_cast(&cluster::message_create), m); -} - -message cluster::message_crosspost_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::message_crosspost), message_id, channel_id); -} - -confirmation cluster::message_delete_all_reactions_sync(const struct message &m) { - return dpp::sync(this, static_cast(&cluster::message_delete_all_reactions), m); -} - -confirmation cluster::message_delete_all_reactions_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::message_delete_all_reactions), message_id, channel_id); -} - -confirmation cluster::message_delete_bulk_sync(const std::vector& message_ids, snowflake channel_id) { - return dpp::sync(this, static_cast&, snowflake, command_completion_event_t)>(&cluster::message_delete_bulk), message_ids, channel_id); -} - -confirmation cluster::message_delete_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::message_delete), message_id, channel_id); -} - -confirmation cluster::message_delete_own_reaction_sync(const struct message &m, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_own_reaction), m, reaction); -} - -confirmation cluster::message_delete_own_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_own_reaction), message_id, channel_id, reaction); -} - -confirmation cluster::message_delete_reaction_sync(const struct message &m, snowflake user_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_reaction), m, user_id, reaction); -} - -confirmation cluster::message_delete_reaction_sync(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_reaction), message_id, channel_id, user_id, reaction); -} - -confirmation cluster::message_delete_reaction_emoji_sync(const struct message &m, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_reaction_emoji), m, reaction); -} - -confirmation cluster::message_delete_reaction_emoji_sync(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return dpp::sync(this, static_cast(&cluster::message_delete_reaction_emoji), message_id, channel_id, reaction); -} - -message cluster::message_edit_sync(const message &m) { - return dpp::sync(this, static_cast(&cluster::message_edit), m); -} - -message cluster::message_edit_flags_sync(const message &m) { - return dpp::sync(this, static_cast(&cluster::message_edit_flags), m); -} - -message cluster::message_get_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::message_get), message_id, channel_id); -} - -user_map cluster::message_get_reactions_sync(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return dpp::sync(this, static_cast(&cluster::message_get_reactions), m, reaction, before, after, limit); -} - -emoji_map cluster::message_get_reactions_sync(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return dpp::sync(this, static_cast(&cluster::message_get_reactions), message_id, channel_id, reaction, before, after, limit); -} - -confirmation cluster::message_pin_sync(snowflake channel_id, snowflake message_id) { - return dpp::sync(this, static_cast(&cluster::message_pin), channel_id, message_id); -} - -message_map cluster::messages_get_sync(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit) { - return dpp::sync(this, static_cast(&cluster::messages_get), channel_id, around, before, after, limit); -} - -confirmation cluster::message_unpin_sync(snowflake channel_id, snowflake message_id) { - return dpp::sync(this, static_cast(&cluster::message_unpin), channel_id, message_id); -} - -user_map cluster::poll_get_answer_voters_sync(const message& m, uint32_t answer_id, snowflake after, uint64_t limit) { - return dpp::sync(this, static_cast(&cluster::poll_get_answer_voters), m, answer_id, after, limit); -} - -user_map cluster::poll_get_answer_voters_sync(snowflake message_id, snowflake channel_id, uint32_t answer_id, snowflake after, uint64_t limit) { - return dpp::sync(this, static_cast(&cluster::poll_get_answer_voters), message_id, channel_id, answer_id, after, limit); -} - -message cluster::poll_end_sync(const message &m) { - return dpp::sync(this, static_cast(&cluster::poll_end), m); -} - -message cluster::poll_end_sync(snowflake message_id, snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::poll_end), message_id, channel_id); -} - -message_map cluster::channel_pins_get_sync(snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::channel_pins_get), channel_id); -} - -role cluster::role_create_sync(const class role &r) { - return dpp::sync(this, static_cast(&cluster::role_create), r); -} - -confirmation cluster::role_delete_sync(snowflake guild_id, snowflake role_id) { - return dpp::sync(this, static_cast(&cluster::role_delete), guild_id, role_id); -} - -role cluster::role_edit_sync(const class role &r) { - return dpp::sync(this, static_cast(&cluster::role_edit), r); -} - -role_map cluster::roles_edit_position_sync(snowflake guild_id, const std::vector &roles) { - return dpp::sync(this, static_cast &, command_completion_event_t)>(&cluster::roles_edit_position), guild_id, roles); -} - -role_map cluster::roles_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::roles_get), guild_id); -} - -application_role_connection cluster::application_role_connection_get_sync(snowflake application_id) { - return dpp::sync(this, static_cast(&cluster::application_role_connection_get), application_id); -} - -application_role_connection cluster::application_role_connection_update_sync(snowflake application_id, const std::vector &connection_metadata) { - return dpp::sync(this, static_cast &, command_completion_event_t)>(&cluster::application_role_connection_update), application_id, connection_metadata); -} - -application_role_connection cluster::user_application_role_connection_get_sync(snowflake application_id) { - return dpp::sync(this, static_cast(&cluster::user_application_role_connection_get), application_id); -} - -application_role_connection cluster::user_application_role_connection_update_sync(snowflake application_id, const application_role_connection &connection) { - return dpp::sync(this, static_cast(&cluster::user_application_role_connection_update), application_id, connection); -} - -scheduled_event_map cluster::guild_events_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_events_get), guild_id); -} - -scheduled_event cluster::guild_event_create_sync(const scheduled_event& event) { - return dpp::sync(this, static_cast(&cluster::guild_event_create), event); -} - -confirmation cluster::guild_event_delete_sync(snowflake event_id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_event_delete), event_id, guild_id); -} - -scheduled_event cluster::guild_event_edit_sync(const scheduled_event& event) { - return dpp::sync(this, static_cast(&cluster::guild_event_edit), event); -} - -scheduled_event cluster::guild_event_get_sync(snowflake guild_id, snowflake event_id) { - return dpp::sync(this, static_cast(&cluster::guild_event_get), guild_id, event_id); -} - -sku_map cluster::skus_get_sync() { - return dpp::sync(this, static_cast(&cluster::skus_get)); -} - -stage_instance cluster::stage_instance_create_sync(const stage_instance& si) { - return dpp::sync(this, static_cast(&cluster::stage_instance_create), si); -} - -stage_instance cluster::stage_instance_get_sync(const snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::stage_instance_get), channel_id); -} - -stage_instance cluster::stage_instance_edit_sync(const stage_instance& si) { - return dpp::sync(this, static_cast(&cluster::stage_instance_edit), si); -} - -confirmation cluster::stage_instance_delete_sync(const snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::stage_instance_delete), channel_id); -} - -sticker cluster::guild_sticker_create_sync(const sticker &s) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_create), s); -} - -confirmation cluster::guild_sticker_delete_sync(snowflake sticker_id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_delete), sticker_id, guild_id); -} - -sticker cluster::guild_sticker_get_sync(snowflake id, snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_get), id, guild_id); -} - -sticker cluster::guild_sticker_modify_sync(const sticker &s) { - return dpp::sync(this, static_cast(&cluster::guild_sticker_modify), s); -} - -sticker_map cluster::guild_stickers_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_stickers_get), guild_id); -} - -sticker cluster::nitro_sticker_get_sync(snowflake id) { - return dpp::sync(this, static_cast(&cluster::nitro_sticker_get), id); -} - -sticker_pack_map cluster::sticker_packs_get_sync() { - return dpp::sync(this, static_cast(&cluster::sticker_packs_get)); -} - -guild cluster::guild_create_from_template_sync(const std::string &code, const std::string &name) { - return dpp::sync(this, static_cast(&cluster::guild_create_from_template), code, name); -} - -dtemplate cluster::guild_template_create_sync(snowflake guild_id, const std::string &name, const std::string &description) { - return dpp::sync(this, static_cast(&cluster::guild_template_create), guild_id, name, description); -} - -confirmation cluster::guild_template_delete_sync(snowflake guild_id, const std::string &code) { - return dpp::sync(this, static_cast(&cluster::guild_template_delete), guild_id, code); -} - -dtemplate cluster::guild_template_modify_sync(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description) { - return dpp::sync(this, static_cast(&cluster::guild_template_modify), guild_id, code, name, description); -} - -dtemplate_map cluster::guild_templates_get_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_templates_get), guild_id); -} - -dtemplate cluster::guild_template_sync_sync(snowflake guild_id, const std::string &code) { - return dpp::sync(this, static_cast(&cluster::guild_template_sync), guild_id, code); -} - -dtemplate cluster::template_get_sync(const std::string &code) { - return dpp::sync(this, static_cast(&cluster::template_get), code); -} - -confirmation cluster::current_user_join_thread_sync(snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::current_user_join_thread), thread_id); -} - -confirmation cluster::current_user_leave_thread_sync(snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::current_user_leave_thread), thread_id); -} - -active_threads cluster::threads_get_active_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::threads_get_active), guild_id); -} - -thread_map cluster::threads_get_joined_private_archived_sync(snowflake channel_id, snowflake before_id, uint16_t limit) { - return dpp::sync(this, static_cast(&cluster::threads_get_joined_private_archived), channel_id, before_id, limit); -} - -thread_map cluster::threads_get_private_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return dpp::sync(this, static_cast(&cluster::threads_get_private_archived), channel_id, before_timestamp, limit); -} - -thread_map cluster::threads_get_public_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return dpp::sync(this, static_cast(&cluster::threads_get_public_archived), channel_id, before_timestamp, limit); -} - -thread_member cluster::thread_member_get_sync(const snowflake thread_id, const snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::thread_member_get), thread_id, user_id); -} - -thread_member_map cluster::thread_members_get_sync(snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::thread_members_get), thread_id); -} - -thread cluster::thread_create_in_forum_sync(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags) { - return dpp::sync(this, static_cast, command_completion_event_t)>(&cluster::thread_create_in_forum), thread_name, channel_id, msg, auto_archive_duration, rate_limit_per_user, applied_tags); -} - -thread cluster::thread_create_sync(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user) { - return dpp::sync(this, static_cast(&cluster::thread_create), thread_name, channel_id, auto_archive_duration, thread_type, invitable, rate_limit_per_user); -} - -thread cluster::thread_edit_sync(const thread &t) { - return dpp::sync(this, static_cast(&cluster::thread_edit), t); -} - -thread cluster::thread_create_with_message_sync(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user) { - return dpp::sync(this, static_cast(&cluster::thread_create_with_message), thread_name, channel_id, message_id, auto_archive_duration, rate_limit_per_user); -} - -confirmation cluster::thread_member_add_sync(snowflake thread_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::thread_member_add), thread_id, user_id); -} - -confirmation cluster::thread_member_remove_sync(snowflake thread_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::thread_member_remove), thread_id, user_id); -} - -thread cluster::thread_get_sync(snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::thread_get), thread_id); -} - -user cluster::current_user_edit_sync(const std::string &nickname, const std::string& avatar_blob, const image_type avatar_type, const std::string& banner_blob, const image_type banner_type) { - return dpp::sync(this, static_cast(&cluster::current_user_edit), nickname, avatar_blob, avatar_type, banner_blob, banner_type); -} - -application cluster::current_application_get_sync() { - return dpp::sync(this, static_cast(&cluster::current_application_get)); -} - -user_identified cluster::current_user_get_sync() { - return dpp::sync(this, static_cast(&cluster::current_user_get)); -} - -confirmation cluster::current_user_set_voice_state_sync(snowflake guild_id, snowflake channel_id, bool suppress, time_t request_to_speak_timestamp) { - return dpp::sync(this, static_cast(&cluster::current_user_set_voice_state), guild_id, channel_id, suppress, request_to_speak_timestamp); -} - -voicestate cluster::current_user_get_voice_state_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::current_user_get_voice_state), guild_id); -} - -confirmation cluster::user_set_voice_state_sync(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress) { - return dpp::sync(this, static_cast(&cluster::user_set_voice_state), user_id, guild_id, channel_id, suppress); -} - -voicestate cluster::user_get_voice_state_sync(snowflake guild_id, snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::user_get_voice_state), guild_id, user_id); -} - -connection_map cluster::current_user_connections_get_sync() { - return dpp::sync(this, static_cast(&cluster::current_user_connections_get)); -} - -guild_map cluster::current_user_get_guilds_sync() { - return dpp::sync(this, static_cast(&cluster::current_user_get_guilds)); -} - -confirmation cluster::current_user_leave_guild_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::current_user_leave_guild), guild_id); -} - -user_identified cluster::user_get_sync(snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::user_get), user_id); -} - -user_identified cluster::user_get_cached_sync(snowflake user_id) { - return dpp::sync(this, static_cast(&cluster::user_get_cached), user_id); -} - -voiceregion_map cluster::get_voice_regions_sync() { - return dpp::sync(this, static_cast(&cluster::get_voice_regions)); -} - -voiceregion_map cluster::guild_get_voice_regions_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::guild_get_voice_regions), guild_id); -} - -webhook cluster::create_webhook_sync(const class webhook &wh) { - return dpp::sync(this, static_cast(&cluster::create_webhook), wh); -} - -confirmation cluster::delete_webhook_sync(snowflake webhook_id) { - return dpp::sync(this, static_cast(&cluster::delete_webhook), webhook_id); -} - -confirmation cluster::delete_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::delete_webhook_message), wh, message_id, thread_id); -} - -confirmation cluster::delete_webhook_with_token_sync(snowflake webhook_id, const std::string &token) { - return dpp::sync(this, static_cast(&cluster::delete_webhook_with_token), webhook_id, token); -} - -webhook cluster::edit_webhook_sync(const class webhook& wh) { - return dpp::sync(this, static_cast(&cluster::edit_webhook), wh); -} - -message cluster::edit_webhook_message_sync(const class webhook &wh, const struct message& m, snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::edit_webhook_message), wh, m, thread_id); -} - -webhook cluster::edit_webhook_with_token_sync(const class webhook& wh) { - return dpp::sync(this, static_cast(&cluster::edit_webhook_with_token), wh); -} - -message cluster::execute_webhook_sync(const class webhook &wh, const struct message& m, bool wait, snowflake thread_id, const std::string& thread_name) { - return dpp::sync(this, static_cast(&cluster::execute_webhook), wh, m, wait, thread_id, thread_name); -} - -webhook_map cluster::get_channel_webhooks_sync(snowflake channel_id) { - return dpp::sync(this, static_cast(&cluster::get_channel_webhooks), channel_id); -} - -webhook_map cluster::get_guild_webhooks_sync(snowflake guild_id) { - return dpp::sync(this, static_cast(&cluster::get_guild_webhooks), guild_id); -} - -webhook cluster::get_webhook_sync(snowflake webhook_id) { - return dpp::sync(this, static_cast(&cluster::get_webhook), webhook_id); -} - -message cluster::get_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return dpp::sync(this, static_cast(&cluster::get_webhook_message), wh, message_id, thread_id); -} - -webhook cluster::get_webhook_with_token_sync(snowflake webhook_id, const std::string &token) { - return dpp::sync(this, static_cast(&cluster::get_webhook_with_token), webhook_id, token); -} - - -}; - -/* End of auto-generated definitions */ From 2c1836695c8c30b08fd497421298e653994ee2f9 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 16:09:29 +0000 Subject: [PATCH 106/112] refactor: remove sync.h --- include/dpp/cluster.h | 1 - include/dpp/dpp.h | 1 - include/dpp/restresults.h | 1 - include/dpp/sync.h | 81 --------------------------------------- 4 files changed, 84 deletions(-) delete mode 100644 include/dpp/sync.h diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 0c3a887f1e..731f63d2df 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include diff --git a/include/dpp/dpp.h b/include/dpp/dpp.h index bbafecf9dd..2d44ee9040 100644 --- a/include/dpp/dpp.h +++ b/include/dpp/dpp.h @@ -70,7 +70,6 @@ #include #include #include -#include #include #include #include diff --git a/include/dpp/restresults.h b/include/dpp/restresults.h index 05a227d47f..902a72ada4 100644 --- a/include/dpp/restresults.h +++ b/include/dpp/restresults.h @@ -38,7 +38,6 @@ #include #include #include -#include #include #include #include diff --git a/include/dpp/sync.h b/include/dpp/sync.h deleted file mode 100644 index fdce669b2d..0000000000 --- a/include/dpp/sync.h +++ /dev/null @@ -1,81 +0,0 @@ -/************************************************************************************ - * - * D++, A Lightweight C++ library for Discord - * - * Copyright 2022 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 - -namespace dpp { - -/** - * @brief Call a D++ REST function synchronously. - * - * Synchronously calling a REST function means *IT WILL BLOCK* - This is a Bad Thing™ and strongly discouraged. - * There are very few circumstances you actually need this. If you do need to use this, you'll know it. - * - * Example: - * - * ```cpp - * dpp::message m = dpp::sync(&bot, &dpp::cluster::message_create, dpp::message(channel_id, "moo.")); - * ``` - * - * @warning As previously mentioned, this template will block. It is ill-advised to call this outside of - * a separate thread and this should never be directly used in any event such as dpp::cluster::on_interaction_create! - * @tparam T type of expected return value, should match up with the method called - * @tparam F Type of class method in dpp::cluster to call. - * @tparam Ts Function parameters in method call - * @param c A pointer to dpp::cluster object - * @param func pointer to class method in dpp::cluster to call. This can call any - * dpp::cluster member function who's last parameter is a dpp::command_completion_event_t callback type. - * @param args Zero or more arguments for the method call - * @return An instantiated object of type T - * @throw dpp::rest_exception On failure of the method call, an exception is thrown - */ -template T sync(class cluster* c, F func, Ts&&... args) { - std::promise _p; - std::future _f = _p.get_future(); - /* (obj ->* func) is the obscure syntax for calling a method pointer on an object instance */ - (c ->* func)(std::forward(args)..., [&_p](const auto& cc) { - try { - if (cc.is_error()) { - const auto& error = cc.get_error(); - throw dpp::rest_exception((exception_error_code)error.code, error.message); - } else { - try { - _p.set_value(std::get(cc.value)); - } catch (const std::exception& e) { - throw dpp::rest_exception(err_unknown, e.what()); - } - } - } catch (const dpp::rest_exception&) { - _p.set_exception(std::current_exception()); - } - }); - - /* Blocking calling thread until rest request is completed. - * Exceptions encountered on the other thread are re-thrown. - */ - return _f.get(); -} - -} From aba8aef9008845376f73add3e36c8fb1031f6802 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 23:08:59 +0000 Subject: [PATCH 107/112] refactor: remove some deprecated and unused fields --- .../classes/Generator/CoroGenerator.php | 4 +-- include/dpp/cluster.h | 3 +- include/dpp/cluster_coro_calls.h | 2 +- include/dpp/queues.h | 34 +++++++------------ src/dpp/cluster.cpp | 4 +-- src/dpp/cluster_coro_calls.cpp | 4 +-- src/dpp/queues.cpp | 12 +++---- 7 files changed, 24 insertions(+), 39 deletions(-) diff --git a/buildtools/classes/Generator/CoroGenerator.php b/buildtools/classes/Generator/CoroGenerator.php index 7749054bc9..1a68809a70 100644 --- a/buildtools/classes/Generator/CoroGenerator.php +++ b/buildtools/classes/Generator/CoroGenerator.php @@ -116,7 +116,7 @@ public function getCommentArray(): array */ public function saveHeader(string $content): void { - $content .= "[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap &headers = {}, const std::string &protocol = \"1.1\", time_t request_timeout = 5);\n\n"; + $content .= "[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap &headers = {}, const std::string &protocol = \"1.1\");\n\n"; file_put_contents('include/dpp/cluster_coro_calls.h', $content); } @@ -125,7 +125,7 @@ public function saveHeader(string $content): void */ public function saveCpp(string $cppcontent): void { - $cppcontent .= "dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol, time_t request_timeout) {\n\treturn async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers, protocol, request_timeout); }};\n} + $cppcontent .= "dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol) {\n\treturn async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers, protocol); }};\n} #endif "; diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 731f63d2df..d3cfc3b693 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -1551,9 +1551,8 @@ class DPP_EXPORT cluster { * @param mimetype MIME type of POST data * @param headers Headers to send with the request * @param protocol HTTP protocol to use (1.1 and 1.0 are supported) - * @param request_timeout How many seconds before the connection is considered failed if not finished */ - void request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1", time_t request_timeout = 5); + void request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1"); /** * @brief Respond to a slash command diff --git a/include/dpp/cluster_coro_calls.h b/include/dpp/cluster_coro_calls.h index ffeefbc1c0..f7d44dcf32 100644 --- a/include/dpp/cluster_coro_calls.h +++ b/include/dpp/cluster_coro_calls.h @@ -2648,5 +2648,5 @@ /* End of auto-generated definitions */ -[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1", time_t request_timeout = 5); +[[nodiscard]] async co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1"); diff --git a/include/dpp/queues.h b/include/dpp/queues.h index bb12ee4509..90ac8a4028 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -37,7 +37,7 @@ namespace dpp { /** * @brief Error values. Most of these are currently unused in https_client. */ -enum http_error { +enum http_error : uint8_t { /** * @brief Request successful. */ @@ -303,18 +303,6 @@ class DPP_EXPORT http_request { */ std::string protocol; - /** - * @brief How many seconds before the connection is considered failed if not finished - * - * @deprecated Please now use dpp::cluster::request_timeout - */ - DPP_DEPRECATED("Please now use dpp::cluster::request_timeout") time_t request_timeout; - - /** - * @brief Unique ptr to self - */ - std::unique_ptr me{nullptr}; - /** * @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request(). * @param _endpoint The API endpoint, e.g. /api/guilds @@ -354,21 +342,14 @@ class DPP_EXPORT http_request { * @param _mimetype POST data mime type * @param _headers HTTP headers to send * @param http_protocol HTTP protocol - * @param _request_timeout How many seconds before the connection is considered failed if not finished */ - http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap &_headers = {}, const std::string &http_protocol = "1.1", time_t _request_timeout = 5); + http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap &_headers = {}, const std::string &http_protocol = "1.1"); /** * @brief Destroy the http request object */ ~http_request(); - /** - * @brief stash unique ptr to self for moving into outbound queue when done - * @param self unique self - */ - void stash_self(std::unique_ptr self); - /** * @brief Call the completion callback, if the request is complete. * @param c callback to call @@ -377,15 +358,20 @@ class DPP_EXPORT http_request { /** * @brief Execute the HTTP request and mark the request complete. + * @param processor Request concurrency queue this request was created by * @param owner creating cluster */ http_request_completion_t run(class request_concurrency_queue* processor, class cluster* owner); - /** @brief Returns true if the request is complete */ + /** + * @brief Returns true if the request is complete + * @return True if completed + * */ bool is_completed(); /** * @brief Get the HTTPS client used to perform this request, or nullptr if there is none + * @return https_client object */ https_client* get_client() const; }; @@ -643,6 +629,10 @@ class DPP_EXPORT request_queue { */ bool is_globally_ratelimited() const; + /** + * @brief Returns the number of active requests on this queue + * @return Total number of active requests + */ size_t get_active_request_count() const; }; diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 9d551a30f1..f9b004da58 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -503,8 +503,8 @@ void cluster::post_rest_multipart(const std::string &endpoint, const std::string } -void cluster::request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol, time_t request_timeout) { - raw_rest->post_request(std::make_unique(url, callback, method, postdata, mimetype, headers, protocol, request_timeout)); +void cluster::request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol) { + raw_rest->post_request(std::make_unique(url, callback, method, postdata, mimetype, headers, protocol)); } gateway::gateway() : shards(0), session_start_total(0), session_start_remaining(0), session_start_reset_after(0), session_start_max_concurrency(0) { diff --git a/src/dpp/cluster_coro_calls.cpp b/src/dpp/cluster_coro_calls.cpp index 3ec3aeff93..d04ad63c99 100644 --- a/src/dpp/cluster_coro_calls.cpp +++ b/src/dpp/cluster_coro_calls.cpp @@ -879,8 +879,8 @@ async cluster::co_get_webhook_with_token(snowflake webh }; /* End of auto-generated definitions */ -dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol, time_t request_timeout) { - return async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers, protocol, request_timeout); }}; +dpp::async dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol) { + return async{ [&, this] (C &&cc) { return this->request(url, method, std::forward(cc), postdata, mimetype, headers, protocol); }}; } #endif diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 07a016630b..80da28786e 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -83,7 +83,7 @@ struct compare_request { } http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method _method, const std::string &audit_reason, const std::string &filename, const std::string &filecontent, const std::string &filemimetype, const std::string &http_protocol) - : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(_method), reason(audit_reason), mimetype("application/json"), waiting(false), protocol(http_protocol), request_timeout(5) + : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(_method), reason(audit_reason), mimetype("application/json"), waiting(false), protocol(http_protocol) { if (!filename.empty()) { file_name.push_back(filename); @@ -97,13 +97,13 @@ http_request::http_request(const std::string &_endpoint, const std::string &_par } http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method method, const std::string &audit_reason, const std::vector &filename, const std::vector &filecontent, const std::vector &filemimetypes, const std::string &http_protocol) - : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(method), reason(audit_reason), file_name(filename), file_content(filecontent), file_mimetypes(filemimetypes), mimetype("application/json"), waiting(false), protocol(http_protocol), request_timeout(5) + : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(method), reason(audit_reason), file_name(filename), file_content(filecontent), file_mimetypes(filemimetypes), mimetype("application/json"), waiting(false), protocol(http_protocol) { } -http_request::http_request(const std::string &_url, http_completion_event completion, http_method _method, const std::string &_postdata, const std::string &_mimetype, const std::multimap &_headers, const std::string &http_protocol, time_t _request_timeout) - : complete_handler(completion), completed(false), non_discord(true), endpoint(_url), postdata(_postdata), method(_method), mimetype(_mimetype), req_headers(_headers), waiting(false), protocol(http_protocol), request_timeout(_request_timeout) +http_request::http_request(const std::string &_url, http_completion_event completion, http_method _method, const std::string &_postdata, const std::string &_mimetype, const std::multimap &_headers, const std::string &http_protocol) + : complete_handler(completion), completed(false), non_discord(true), endpoint(_url), postdata(_postdata), method(_method), mimetype(_mimetype), req_headers(_headers), waiting(false), protocol(http_protocol) { } @@ -282,10 +282,6 @@ http_request_completion_t http_request::run(request_concurrency_queue* processor return rv; } -void http_request::stash_self(std::unique_ptr self) { - me = std::move(self); -} - request_queue::request_queue(class cluster* owner, uint32_t request_concurrency) : creator(owner), terminating(false), globally_ratelimited(false), globally_limited_until(0), in_queue_pool_size(request_concurrency) { /* Create request_concurrency timer instances */ From 06113a4c28840e4f28534506424bd76e5dad307f Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Fri, 13 Dec 2024 23:38:47 +0000 Subject: [PATCH 108/112] fix; when putting events into the thread pool work queue, we must take a copy of the cluster ptr, as the client ptr may be invalid by the time the event is triggered (shards now get reallocated) --- src/dpp/events/automod_rule_create.cpp | 4 +-- src/dpp/events/automod_rule_delete.cpp | 4 +-- src/dpp/events/automod_rule_execute.cpp | 4 +-- src/dpp/events/automod_rule_update.cpp | 4 +-- src/dpp/events/channel_create.cpp | 4 +-- src/dpp/events/channel_delete.cpp | 4 +-- src/dpp/events/channel_pins_update.cpp | 4 +-- src/dpp/events/channel_update.cpp | 4 +-- src/dpp/events/entitlement_create.cpp | 4 +-- src/dpp/events/entitlement_delete.cpp | 4 +-- src/dpp/events/entitlement_update.cpp | 4 +-- .../events/guild_audit_log_entry_create.cpp | 4 +-- src/dpp/events/guild_ban_add.cpp | 4 +-- src/dpp/events/guild_ban_remove.cpp | 4 +-- src/dpp/events/guild_create.cpp | 4 +-- src/dpp/events/guild_delete.cpp | 4 +-- src/dpp/events/guild_emojis_update.cpp | 4 +-- src/dpp/events/guild_integrations_update.cpp | 4 +-- src/dpp/events/guild_join_request_delete.cpp | 4 +-- src/dpp/events/guild_member_add.cpp | 8 ++--- src/dpp/events/guild_member_remove.cpp | 4 +-- src/dpp/events/guild_member_update.cpp | 13 ++++---- src/dpp/events/guild_members_chunk.cpp | 4 +-- src/dpp/events/guild_role_create.cpp | 8 ++--- src/dpp/events/guild_role_delete.cpp | 8 ++--- src/dpp/events/guild_role_update.cpp | 8 ++--- .../events/guild_scheduled_event_create.cpp | 4 +-- .../events/guild_scheduled_event_delete.cpp | 4 +-- .../events/guild_scheduled_event_update.cpp | 4 +-- .../events/guild_scheduled_event_user_add.cpp | 4 +-- .../guild_scheduled_event_user_remove.cpp | 4 +-- src/dpp/events/guild_stickers_update.cpp | 4 +-- src/dpp/events/guild_update.cpp | 4 +-- src/dpp/events/integration_create.cpp | 4 +-- src/dpp/events/integration_delete.cpp | 4 +-- src/dpp/events/integration_update.cpp | 4 +-- src/dpp/events/interaction_create.cpp | 32 +++++++++---------- src/dpp/events/invite_create.cpp | 4 +-- src/dpp/events/invite_delete.cpp | 4 +-- src/dpp/events/logger.cpp | 2 +- src/dpp/events/message_create.cpp | 4 +-- src/dpp/events/message_delete.cpp | 4 +-- src/dpp/events/message_delete_bulk.cpp | 4 +-- src/dpp/events/message_poll_vote_add.cpp | 4 +-- src/dpp/events/message_poll_vote_remove.cpp | 4 +-- src/dpp/events/message_reaction_add.cpp | 4 +-- src/dpp/events/message_reaction_remove.cpp | 4 +-- .../events/message_reaction_remove_all.cpp | 4 +-- .../events/message_reaction_remove_emoji.cpp | 4 +-- src/dpp/events/message_update.cpp | 4 +-- src/dpp/events/presence_update.cpp | 4 +-- src/dpp/events/ready.cpp | 4 +-- src/dpp/events/resumed.cpp | 4 +-- src/dpp/events/stage_instance_create.cpp | 4 +-- src/dpp/events/stage_instance_delete.cpp | 4 +-- src/dpp/events/stage_instance_update.cpp | 4 +-- src/dpp/events/thread_create.cpp | 4 +-- src/dpp/events/thread_delete.cpp | 4 +-- src/dpp/events/thread_list_sync.cpp | 4 +-- src/dpp/events/thread_member_update.cpp | 4 +-- src/dpp/events/thread_members_update.cpp | 4 +-- src/dpp/events/thread_update.cpp | 4 +-- src/dpp/events/typing_start.cpp | 4 +-- src/dpp/events/user_update.cpp | 8 ++--- src/dpp/events/voice_server_update.cpp | 4 +-- src/dpp/events/voice_state_update.cpp | 4 +-- src/dpp/events/webhooks_update.cpp | 4 +-- 67 files changed, 161 insertions(+), 162 deletions(-) diff --git a/src/dpp/events/automod_rule_create.cpp b/src/dpp/events/automod_rule_create.cpp index 3ee13113aa..49849cc05f 100644 --- a/src/dpp/events/automod_rule_create.cpp +++ b/src/dpp/events/automod_rule_create.cpp @@ -41,8 +41,8 @@ void automod_rule_create::handle(discord_client* client, json &j, const std::str json& d = j["d"]; automod_rule_create_t arc(client, raw); arc.created = automod_rule().fill_from_json(&d); - client->creator->queue_work(0, [client, arc]() { - client->creator->on_automod_rule_create.call(arc); + client->creator->queue_work(0, [c = client->creator, arc]() { + c->on_automod_rule_create.call(arc); }); } } diff --git a/src/dpp/events/automod_rule_delete.cpp b/src/dpp/events/automod_rule_delete.cpp index 9919d3705b..bd44fb9cf0 100644 --- a/src/dpp/events/automod_rule_delete.cpp +++ b/src/dpp/events/automod_rule_delete.cpp @@ -39,8 +39,8 @@ void automod_rule_delete::handle(discord_client* client, json &j, const std::str json& d = j["d"]; automod_rule_delete_t ard(client, raw); ard.deleted = automod_rule().fill_from_json(&d); - client->creator->queue_work(0, [client, ard]() { - client->creator->on_automod_rule_delete.call(ard); + client->creator->queue_work(0, [c = client->creator, ard]() { + c->on_automod_rule_delete.call(ard); }); } } diff --git a/src/dpp/events/automod_rule_execute.cpp b/src/dpp/events/automod_rule_execute.cpp index 482d9aa521..ef27d7d7f8 100644 --- a/src/dpp/events/automod_rule_execute.cpp +++ b/src/dpp/events/automod_rule_execute.cpp @@ -49,8 +49,8 @@ void automod_rule_execute::handle(discord_client* client, json &j, const std::st are.content = string_not_null(&d, "content"); are.matched_keyword = string_not_null(&d, "matched_keyword"); are.matched_content = string_not_null(&d, "matched_content"); - client->creator->queue_work(0, [client, are]() { - client->creator->on_automod_rule_execute.call(are); + client->creator->queue_work(0, [c = client->creator, are]() { + c->on_automod_rule_execute.call(are); }); } } diff --git a/src/dpp/events/automod_rule_update.cpp b/src/dpp/events/automod_rule_update.cpp index fa052e8979..097d3ab58b 100644 --- a/src/dpp/events/automod_rule_update.cpp +++ b/src/dpp/events/automod_rule_update.cpp @@ -39,8 +39,8 @@ void automod_rule_update::handle(discord_client* client, json &j, const std::str json& d = j["d"]; automod_rule_update_t aru(client, raw); aru.updated = automod_rule().fill_from_json(&d); - client->creator->queue_work(0, [client, aru]() { - client->creator->on_automod_rule_update.call(aru); + client->creator->queue_work(0, [c = client->creator, aru]() { + c->on_automod_rule_update.call(aru); }); } } diff --git a/src/dpp/events/channel_create.cpp b/src/dpp/events/channel_create.cpp index 7d86a21929..2b931bec60 100644 --- a/src/dpp/events/channel_create.cpp +++ b/src/dpp/events/channel_create.cpp @@ -70,8 +70,8 @@ void channel_create::handle(discord_client* client, json &j, const std::string & dpp::channel_create_t cc(client, raw); cc.created = c; cc.creating_guild = g; - client->creator->queue_work(1, [client, cc]() { - client->creator->on_channel_create.call(cc); + client->creator->queue_work(1, [c = client->creator, cc]() { + c->on_channel_create.call(cc); }); } } diff --git a/src/dpp/events/channel_delete.cpp b/src/dpp/events/channel_delete.cpp index e8238ed056..9d6677140b 100644 --- a/src/dpp/events/channel_delete.cpp +++ b/src/dpp/events/channel_delete.cpp @@ -51,8 +51,8 @@ void channel_delete::handle(discord_client* client, json &j, const std::string & channel_delete_t cd(client, raw); cd.deleted = c; cd.deleting_guild = g; - client->creator->queue_work(1, [client, cd]() { - client->creator->on_channel_delete.call(cd); + client->creator->queue_work(1, [c = client->creator, cd]() { + c->on_channel_delete.call(cd); }); } } diff --git a/src/dpp/events/channel_pins_update.cpp b/src/dpp/events/channel_pins_update.cpp index 602ee7948e..f9012aea36 100644 --- a/src/dpp/events/channel_pins_update.cpp +++ b/src/dpp/events/channel_pins_update.cpp @@ -43,8 +43,8 @@ void channel_pins_update::handle(discord_client* client, json &j, const std::str cpu.pin_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); cpu.pin_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); cpu.timestamp = ts_not_null(&d, "last_pin_timestamp"); - client->creator->queue_work(0, [client, cpu]() { - client->creator->on_channel_pins_update.call(cpu); + client->creator->queue_work(0, [c = client->creator, cpu]() { + c->on_channel_pins_update.call(cpu); }); } diff --git a/src/dpp/events/channel_update.cpp b/src/dpp/events/channel_update.cpp index ccfe812206..44cb24c5c9 100644 --- a/src/dpp/events/channel_update.cpp +++ b/src/dpp/events/channel_update.cpp @@ -53,8 +53,8 @@ void channel_update::handle(discord_client* client, json &j, const std::string & dpp::channel_update_t cu(client, raw); cu.updated = c; cu.updating_guild = dpp::find_guild(c->guild_id); - client->creator->queue_work(1, [client, cu]() { - client->creator->on_channel_update.call(cu); + client->creator->queue_work(1, [c = client->creator, cu]() { + c->on_channel_update.call(cu); }); } } diff --git a/src/dpp/events/entitlement_create.cpp b/src/dpp/events/entitlement_create.cpp index a51803182b..3e42b675d6 100644 --- a/src/dpp/events/entitlement_create.cpp +++ b/src/dpp/events/entitlement_create.cpp @@ -41,8 +41,8 @@ void entitlement_create::handle(discord_client* client, json &j, const std::stri dpp::entitlement_create_t entitlement_event(client, raw); entitlement_event.created = ent; - client->creator->queue_work(0, [client, entitlement_event]() { - client->creator->on_entitlement_create.call(entitlement_event); + client->creator->queue_work(0, [c = client->creator, entitlement_event]() { + c->on_entitlement_create.call(entitlement_event); }); } } diff --git a/src/dpp/events/entitlement_delete.cpp b/src/dpp/events/entitlement_delete.cpp index 5c27f352a0..196ed0774a 100644 --- a/src/dpp/events/entitlement_delete.cpp +++ b/src/dpp/events/entitlement_delete.cpp @@ -41,8 +41,8 @@ void entitlement_delete::handle(discord_client* client, json &j, const std::stri dpp::entitlement_delete_t entitlement_event(client, raw); entitlement_event.deleted = ent; - client->creator->queue_work(0, [client, entitlement_event]() { - client->creator->on_entitlement_delete.call(entitlement_event); + client->creator->queue_work(0, [c = client->creator, entitlement_event]() { + c->on_entitlement_delete.call(entitlement_event); }); } } diff --git a/src/dpp/events/entitlement_update.cpp b/src/dpp/events/entitlement_update.cpp index 2a02396512..150b2e0c0b 100644 --- a/src/dpp/events/entitlement_update.cpp +++ b/src/dpp/events/entitlement_update.cpp @@ -41,8 +41,8 @@ void entitlement_update::handle(discord_client* client, json &j, const std::stri dpp::entitlement_update_t entitlement_event(client, raw); entitlement_event.updating_entitlement = ent; - client->creator->queue_work(0, [client, entitlement_event]() { - client->creator->on_entitlement_update.call(entitlement_event); + client->creator->queue_work(0, [c = client->creator, entitlement_event]() { + c->on_entitlement_update.call(entitlement_event); }); } } diff --git a/src/dpp/events/guild_audit_log_entry_create.cpp b/src/dpp/events/guild_audit_log_entry_create.cpp index b172a5175b..15b5fc280f 100644 --- a/src/dpp/events/guild_audit_log_entry_create.cpp +++ b/src/dpp/events/guild_audit_log_entry_create.cpp @@ -37,8 +37,8 @@ void guild_audit_log_entry_create::handle(discord_client* client, json &j, const if (!client->creator->on_guild_audit_log_entry_create.empty()) { dpp::guild_audit_log_entry_create_t ec(client, raw); ec.entry.fill_from_json(&d); - client->creator->queue_work(2, [client, ec]() { - client->creator->on_guild_audit_log_entry_create.call(ec); + client->creator->queue_work(2, [c = client->creator, ec]() { + c->on_guild_audit_log_entry_create.call(ec); }); } } diff --git a/src/dpp/events/guild_ban_add.cpp b/src/dpp/events/guild_ban_add.cpp index e507140d93..82a2df68aa 100644 --- a/src/dpp/events/guild_ban_add.cpp +++ b/src/dpp/events/guild_ban_add.cpp @@ -44,8 +44,8 @@ void guild_ban_add::handle(discord_client* client, json &j, const std::string &r dpp::guild_ban_add_t gba(client, raw); gba.banning_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); gba.banned = dpp::user().fill_from_json(&(d["user"])); - client->creator->queue_work(1, [client, gba]() { - client->creator->on_guild_ban_add.call(gba); + client->creator->queue_work(1, [c = client->creator, gba]() { + c->on_guild_ban_add.call(gba); }); } } diff --git a/src/dpp/events/guild_ban_remove.cpp b/src/dpp/events/guild_ban_remove.cpp index 23c967841d..7a45be894d 100644 --- a/src/dpp/events/guild_ban_remove.cpp +++ b/src/dpp/events/guild_ban_remove.cpp @@ -44,8 +44,8 @@ void guild_ban_remove::handle(discord_client* client, json &j, const std::string dpp::guild_ban_remove_t gbr(client, raw); gbr.unbanning_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); gbr.unbanned = dpp::user().fill_from_json(&(d["user"])); - client->creator->queue_work(1, [client, gbr]() { - client->creator->on_guild_ban_remove.call(gbr); + client->creator->queue_work(1, [c = client->creator, gbr]() { + c->on_guild_ban_remove.call(gbr); }); } } diff --git a/src/dpp/events/guild_create.cpp b/src/dpp/events/guild_create.cpp index ea43521815..5e6c3a7177 100644 --- a/src/dpp/events/guild_create.cpp +++ b/src/dpp/events/guild_create.cpp @@ -204,8 +204,8 @@ void guild_create::handle(discord_client* client, json &j, const std::string &ra } } - client->creator->queue_work(0, [client, gc]() { - client->creator->on_guild_create.call(gc); + client->creator->queue_work(0, [c = client->creator, gc]() { + c->on_guild_create.call(gc); }); } } diff --git a/src/dpp/events/guild_delete.cpp b/src/dpp/events/guild_delete.cpp index bcba7efc48..c9b475b4a2 100644 --- a/src/dpp/events/guild_delete.cpp +++ b/src/dpp/events/guild_delete.cpp @@ -90,8 +90,8 @@ void guild_delete::handle(discord_client* client, json &j, const std::string &ra dpp::guild_delete_t gd(client, raw); gd.deleted = guild_del; gd.guild_id = guild_del.id; - client->creator->queue_work(0, [client, gd]() { - client->creator->on_guild_delete.call(gd); + client->creator->queue_work(0, [c = client->creator, gd]() { + c->on_guild_delete.call(gd); }); } } diff --git a/src/dpp/events/guild_emojis_update.cpp b/src/dpp/events/guild_emojis_update.cpp index 57977e29b5..98125a9603 100644 --- a/src/dpp/events/guild_emojis_update.cpp +++ b/src/dpp/events/guild_emojis_update.cpp @@ -74,8 +74,8 @@ void guild_emojis_update::handle(discord_client* client, json &j, const std::str dpp::guild_emojis_update_t geu(client, raw); geu.emojis = emojis; geu.updating_guild = g; - client->creator->queue_work(1, [client, geu]() { - client->creator->on_guild_emojis_update.call(geu); + client->creator->queue_work(1, [c = client->creator, geu]() { + c->on_guild_emojis_update.call(geu); }); } } diff --git a/src/dpp/events/guild_integrations_update.cpp b/src/dpp/events/guild_integrations_update.cpp index 54feea42d9..2d93c448b3 100644 --- a/src/dpp/events/guild_integrations_update.cpp +++ b/src/dpp/events/guild_integrations_update.cpp @@ -41,8 +41,8 @@ void guild_integrations_update::handle(class discord_client* client, json &j, co json& d = j["d"]; dpp::guild_integrations_update_t giu(client, raw); giu.updating_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); - client->creator->queue_work(1, [client, giu]() { - client->creator->on_guild_integrations_update.call(giu); + client->creator->queue_work(1, [c = client->creator, giu]() { + c->on_guild_integrations_update.call(giu); }); } } diff --git a/src/dpp/events/guild_join_request_delete.cpp b/src/dpp/events/guild_join_request_delete.cpp index 9822aff0a0..7b13745282 100644 --- a/src/dpp/events/guild_join_request_delete.cpp +++ b/src/dpp/events/guild_join_request_delete.cpp @@ -41,8 +41,8 @@ void guild_join_request_delete::handle(class discord_client* client, json &j, co dpp::guild_join_request_delete_t grd(client, raw); grd.user_id = snowflake_not_null(&d, "user_id"); grd.guild_id = snowflake_not_null(&d, "guild_id"); - client->creator->queue_work(1, [client, grd]() { - client->creator->on_guild_join_request_delete.call(grd); + client->creator->queue_work(1, [c = client->creator, grd]() { + c->on_guild_join_request_delete.call(grd); }); } } diff --git a/src/dpp/events/guild_member_add.cpp b/src/dpp/events/guild_member_add.cpp index 78e4b72c5a..a245e7658a 100644 --- a/src/dpp/events/guild_member_add.cpp +++ b/src/dpp/events/guild_member_add.cpp @@ -47,8 +47,8 @@ void guild_member_add::handle(discord_client* client, json &j, const std::string gmr.added = gm; if (!client->creator->on_guild_member_add.empty()) { gmr.adding_guild = g; - client->creator->queue_work(1, [client, gmr]() { - client->creator->on_guild_member_add.call(gmr); + client->creator->queue_work(1, [c = client->creator, gmr]() { + c->on_guild_member_add.call(gmr); }); } } else { @@ -71,8 +71,8 @@ void guild_member_add::handle(discord_client* client, json &j, const std::string } if (!client->creator->on_guild_member_add.empty()) { gmr.adding_guild = g; - client->creator->queue_work(1, [client, gmr]() { - client->creator->on_guild_member_add.call(gmr); + client->creator->queue_work(1, [c = client->creator, gmr]() { + c->on_guild_member_add.call(gmr); }); } } diff --git a/src/dpp/events/guild_member_remove.cpp b/src/dpp/events/guild_member_remove.cpp index e376275fab..acc5552c34 100644 --- a/src/dpp/events/guild_member_remove.cpp +++ b/src/dpp/events/guild_member_remove.cpp @@ -45,8 +45,8 @@ void guild_member_remove::handle(discord_client* client, json &j, const std::str gmr.removing_guild = dpp::find_guild(gmr.guild_id); if (!client->creator->on_guild_member_remove.empty()) { - client->creator->queue_work(1, [client, gmr]() { - client->creator->on_guild_member_remove.call(gmr); + client->creator->queue_work(1, [c = client->creator, gmr]() { + c->on_guild_member_remove.call(gmr); }); } diff --git a/src/dpp/events/guild_member_update.cpp b/src/dpp/events/guild_member_update.cpp index 022980ef08..dd81d91e4c 100644 --- a/src/dpp/events/guild_member_update.cpp +++ b/src/dpp/events/guild_member_update.cpp @@ -21,11 +21,8 @@ ************************************************************************************/ #include #include -#include -#include #include - namespace dpp::events { @@ -50,10 +47,10 @@ void guild_member_update::handle(discord_client* client, json &j, const std::str auto& user = d; // d contains roles and other member stuff already m.fill_from_json(&user, guild_id, u.id); gmu.updated = m; + client->creator->queue_work(1, [c = client->creator, gmu]() { + c->on_guild_member_update.call(gmu); + }); } - client->creator->queue_work(1, [client, gmu]() { - client->creator->on_guild_member_update.call(gmu); - }); } else { dpp::user* u = dpp::find_user(from_string(d["user"]["id"].get())); if (u) { @@ -68,7 +65,9 @@ void guild_member_update::handle(discord_client* client, json &j, const std::str dpp::guild_member_update_t gmu(client, raw); gmu.updating_guild = g; gmu.updated = m; - client->creator->on_guild_member_update.call(gmu); + client->creator->queue_work(0, [c = client->creator, gmu]() { + c->on_guild_member_update.call(gmu); + }); } } } diff --git a/src/dpp/events/guild_members_chunk.cpp b/src/dpp/events/guild_members_chunk.cpp index 32da895e82..26f5785d7c 100644 --- a/src/dpp/events/guild_members_chunk.cpp +++ b/src/dpp/events/guild_members_chunk.cpp @@ -67,8 +67,8 @@ void guild_members_chunk::handle(discord_client* client, json &j, const std::str dpp::guild_members_chunk_t gmc(client, raw); gmc.adding = g; gmc.members = &um; - client->creator->queue_work(1, [client, gmc]() { - client->creator->on_guild_members_chunk.call(gmc); + client->creator->queue_work(1, [c = client->creator, gmc]() { + c->on_guild_members_chunk.call(gmc); }); } } diff --git a/src/dpp/events/guild_role_create.cpp b/src/dpp/events/guild_role_create.cpp index 8e4a692837..2d5ad162ce 100644 --- a/src/dpp/events/guild_role_create.cpp +++ b/src/dpp/events/guild_role_create.cpp @@ -49,8 +49,8 @@ void guild_role_create::handle(discord_client* client, json &j, const std::strin dpp::guild_role_create_t grc(client, raw); grc.creating_guild = g; grc.created = &r; - client->creator->queue_work(1, [client, grc]() { - client->creator->on_guild_role_create.call(grc); + client->creator->queue_work(1, [c = client->creator, grc]() { + c->on_guild_role_create.call(grc); }); } } else { @@ -68,8 +68,8 @@ void guild_role_create::handle(discord_client* client, json &j, const std::strin dpp::guild_role_create_t grc(client, raw); grc.creating_guild = g; grc.created = r; - client->creator->queue_work(1, [client, grc]() { - client->creator->on_guild_role_create.call(grc); + client->creator->queue_work(1, [c = client->creator, grc]() { + c->on_guild_role_create.call(grc); }); } } diff --git a/src/dpp/events/guild_role_delete.cpp b/src/dpp/events/guild_role_delete.cpp index f6e707acc2..d23136bd4e 100644 --- a/src/dpp/events/guild_role_delete.cpp +++ b/src/dpp/events/guild_role_delete.cpp @@ -48,8 +48,8 @@ void guild_role_delete::handle(discord_client* client, json &j, const std::strin grd.deleting_guild = g; grd.role_id = role_id; grd.deleted = nullptr; - client->creator->queue_work(1, [client, grd]() { - client->creator->on_guild_role_delete.call(grd); + client->creator->queue_work(1, [c = client->creator, grd]() { + c->on_guild_role_delete.call(grd); }); } } else { @@ -59,8 +59,8 @@ void guild_role_delete::handle(discord_client* client, json &j, const std::strin grd.deleting_guild = g; grd.deleted = r; grd.role_id = role_id; - client->creator->queue_work(1, [client, grd]() { - client->creator->on_guild_role_delete.call(grd); + client->creator->queue_work(1, [c = client->creator, grd]() { + c->on_guild_role_delete.call(grd); }); } if (r) { diff --git a/src/dpp/events/guild_role_update.cpp b/src/dpp/events/guild_role_update.cpp index 6f8ca5a691..20f8573217 100644 --- a/src/dpp/events/guild_role_update.cpp +++ b/src/dpp/events/guild_role_update.cpp @@ -47,8 +47,8 @@ void guild_role_update::handle(discord_client* client, json &j, const std::strin dpp::guild_role_update_t gru(client, raw); gru.updating_guild = g; gru.updated = &r; - client->creator->queue_work(1, [client, gru]() { - client->creator->on_guild_role_update.call(gru); + client->creator->queue_work(1, [c = client->creator, gru]() { + c->on_guild_role_update.call(gru); }); } } else { @@ -60,8 +60,8 @@ void guild_role_update::handle(discord_client* client, json &j, const std::strin dpp::guild_role_update_t gru(client, raw); gru.updating_guild = g; gru.updated = r; - client->creator->queue_work(1, [client, gru]() { - client->creator->on_guild_role_update.call(gru); + client->creator->queue_work(1, [c = client->creator, gru]() { + c->on_guild_role_update.call(gru); }); } } diff --git a/src/dpp/events/guild_scheduled_event_create.cpp b/src/dpp/events/guild_scheduled_event_create.cpp index 0ada5b1c3e..7c72cd24ab 100644 --- a/src/dpp/events/guild_scheduled_event_create.cpp +++ b/src/dpp/events/guild_scheduled_event_create.cpp @@ -41,8 +41,8 @@ void guild_scheduled_event_create::handle(discord_client* client, json &j, const if (!client->creator->on_guild_scheduled_event_create.empty()) { dpp::guild_scheduled_event_create_t ec(client, raw); ec.created.fill_from_json(&d); - client->creator->queue_work(1, [client, ec]() { - client->creator->on_guild_scheduled_event_create.call(ec); + client->creator->queue_work(1, [c = client->creator, ec]() { + c->on_guild_scheduled_event_create.call(ec); }); } } diff --git a/src/dpp/events/guild_scheduled_event_delete.cpp b/src/dpp/events/guild_scheduled_event_delete.cpp index 0db680efbf..ae914d38f6 100644 --- a/src/dpp/events/guild_scheduled_event_delete.cpp +++ b/src/dpp/events/guild_scheduled_event_delete.cpp @@ -42,8 +42,8 @@ void guild_scheduled_event_delete::handle(discord_client* client, json &j, const if (!client->creator->on_guild_scheduled_event_delete.empty()) { dpp::guild_scheduled_event_delete_t ed(client, raw); ed.deleted.fill_from_json(&d); - client->creator->queue_work(1, [client, ed]() { - client->creator->on_guild_scheduled_event_delete.call(ed); + client->creator->queue_work(1, [c = client->creator, ed]() { + c->on_guild_scheduled_event_delete.call(ed); }); } } diff --git a/src/dpp/events/guild_scheduled_event_update.cpp b/src/dpp/events/guild_scheduled_event_update.cpp index 87affe33fc..0f606d25a4 100644 --- a/src/dpp/events/guild_scheduled_event_update.cpp +++ b/src/dpp/events/guild_scheduled_event_update.cpp @@ -42,8 +42,8 @@ void guild_scheduled_event_update::handle(discord_client* client, json &j, const if (!client->creator->on_guild_scheduled_event_update.empty()) { dpp::guild_scheduled_event_update_t eu(client, raw); eu.updated.fill_from_json(&d); - client->creator->queue_work(1, [client, eu]() { - client->creator->on_guild_scheduled_event_update.call(eu); + client->creator->queue_work(1, [c = client->creator, eu]() { + c->on_guild_scheduled_event_update.call(eu); }); } } diff --git a/src/dpp/events/guild_scheduled_event_user_add.cpp b/src/dpp/events/guild_scheduled_event_user_add.cpp index 714d4f4c1a..bae5c7419f 100644 --- a/src/dpp/events/guild_scheduled_event_user_add.cpp +++ b/src/dpp/events/guild_scheduled_event_user_add.cpp @@ -43,8 +43,8 @@ void guild_scheduled_event_user_add::handle(discord_client* client, json &j, con eua.guild_id = snowflake_not_null(&d, "guild_id"); eua.user_id = snowflake_not_null(&d, "user_id"); eua.event_id = snowflake_not_null(&d, "guild_scheduled_event_id"); - client->creator->queue_work(1, [client, eua]() { - client->creator->on_guild_scheduled_event_user_add.call(eua); + client->creator->queue_work(1, [c = client->creator, eua]() { + c->on_guild_scheduled_event_user_add.call(eua); }); } } diff --git a/src/dpp/events/guild_scheduled_event_user_remove.cpp b/src/dpp/events/guild_scheduled_event_user_remove.cpp index 685bd5cfcc..5ebfc34333 100644 --- a/src/dpp/events/guild_scheduled_event_user_remove.cpp +++ b/src/dpp/events/guild_scheduled_event_user_remove.cpp @@ -43,8 +43,8 @@ void guild_scheduled_event_user_remove::handle(discord_client* client, json &j, eur.guild_id = snowflake_not_null(&d, "guild_id"); eur.user_id = snowflake_not_null(&d, "user_id"); eur.event_id = snowflake_not_null(&d, "guild_scheduled_event_id"); - client->creator->queue_work(1, [client, eur]() { - client->creator->on_guild_scheduled_event_user_remove.call(eur); + client->creator->queue_work(1, [c = client->creator, eur]() { + c->on_guild_scheduled_event_user_remove.call(eur); }); } } diff --git a/src/dpp/events/guild_stickers_update.cpp b/src/dpp/events/guild_stickers_update.cpp index 5bf7831c0b..dfc43d30d2 100644 --- a/src/dpp/events/guild_stickers_update.cpp +++ b/src/dpp/events/guild_stickers_update.cpp @@ -49,8 +49,8 @@ void guild_stickers_update::handle(discord_client* client, json &j, const std::s gsu.stickers.emplace_back(s); } gsu.updating_guild = g; - client->creator->queue_work(1, [client, gsu]() { - client->creator->on_guild_stickers_update.call(gsu); + client->creator->queue_work(1, [c = client->creator, gsu]() { + c->on_guild_stickers_update.call(gsu); }); } } diff --git a/src/dpp/events/guild_update.cpp b/src/dpp/events/guild_update.cpp index 0b75360076..4398279587 100644 --- a/src/dpp/events/guild_update.cpp +++ b/src/dpp/events/guild_update.cpp @@ -67,8 +67,8 @@ void guild_update::handle(discord_client* client, json &j, const std::string &ra if (!client->creator->on_guild_update.empty()) { dpp::guild_update_t gu(client, raw); gu.updated = g; - client->creator->queue_work(1, [client, gu]() { - client->creator->on_guild_update.call(gu); + client->creator->queue_work(1, [c = client->creator, gu]() { + c->on_guild_update.call(gu); }); } } diff --git a/src/dpp/events/integration_create.cpp b/src/dpp/events/integration_create.cpp index b7101482a7..48fd9be7e8 100644 --- a/src/dpp/events/integration_create.cpp +++ b/src/dpp/events/integration_create.cpp @@ -42,8 +42,8 @@ void integration_create::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; dpp::integration_create_t ic(client, raw); ic.created_integration = dpp::integration().fill_from_json(&d); - client->creator->queue_work(1, [client, ic]() { - client->creator->on_integration_create.call(ic); + client->creator->queue_work(1, [c = client->creator, ic]() { + c->on_integration_create.call(ic); }); } } diff --git a/src/dpp/events/integration_delete.cpp b/src/dpp/events/integration_delete.cpp index 94a6acde24..d93063e5dc 100644 --- a/src/dpp/events/integration_delete.cpp +++ b/src/dpp/events/integration_delete.cpp @@ -41,8 +41,8 @@ void integration_delete::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; dpp::integration_delete_t id(client, raw); id.deleted_integration = dpp::integration().fill_from_json(&d); - client->creator->queue_work(1, [client, id]() { - client->creator->on_integration_delete.call(id); + client->creator->queue_work(1, [c = client->creator, id]() { + c->on_integration_delete.call(id); }); } } diff --git a/src/dpp/events/integration_update.cpp b/src/dpp/events/integration_update.cpp index 9fed3bb708..814ac81634 100644 --- a/src/dpp/events/integration_update.cpp +++ b/src/dpp/events/integration_update.cpp @@ -41,8 +41,8 @@ void integration_update::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; dpp::integration_update_t iu(client, raw); iu.updated_integration = dpp::integration().fill_from_json(&d); - client->creator->queue_work(1, [client, iu]() { - client->creator->on_integration_update.call(iu); + client->creator->queue_work(1, [c = client->creator, iu]() { + c->on_integration_update.call(iu); }); } } diff --git a/src/dpp/events/interaction_create.cpp b/src/dpp/events/interaction_create.cpp index 6c46349a7f..51d00fe958 100644 --- a/src/dpp/events/interaction_create.cpp +++ b/src/dpp/events/interaction_create.cpp @@ -102,8 +102,8 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri message_context_menu_t mcm(client, raw); mcm.command = i; mcm.set_message(i.resolved.messages.begin()->second); - client->creator->queue_work(1, [client, mcm]() { - client->creator->on_message_context_menu.call(mcm); + client->creator->queue_work(1, [c = client->creator, mcm]() { + c->on_message_context_menu.call(mcm); }); } } else if (cmd_data.type == ctxm_user && !client->creator->on_user_context_menu.empty()) { @@ -112,15 +112,15 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri user_context_menu_t ucm(client, raw); ucm.command = i; ucm.set_user(i.resolved.users.begin()->second); - client->creator->queue_work(1, [client, ucm]() { - client->creator->on_user_context_menu.call(ucm); + client->creator->queue_work(1, [c = client->creator, ucm]() { + c->on_user_context_menu.call(ucm); }); } } else if (cmd_data.type == ctxm_chat_input && !client->creator->on_slashcommand.empty()) { dpp::slashcommand_t sc(client, raw); sc.command = i; - client->creator->queue_work(1, [client, sc]() { - client->creator->on_slashcommand.call(sc); + client->creator->queue_work(1, [c = client->creator, sc]() { + c->on_slashcommand.call(sc); }); } if (!client->creator->on_interaction_create.empty()) { @@ -130,8 +130,8 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri */ dpp::interaction_create_t ic(client, raw); ic.command = i; - client->creator->queue_work(1, [client, ic]() { - client->creator->on_interaction_create.call(ic); + client->creator->queue_work(1, [c = client->creator, ic]() { + c->on_interaction_create.call(ic); }); } } else if (i.type == it_modal_submit) { @@ -142,8 +142,8 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri for (auto & c : d["data"]["components"]) { fs.components.push_back(dpp::component().fill_from_json(&c)); } - client->creator->queue_work(1, [client, fs]() { - client->creator->on_form_submit.call(fs); + client->creator->queue_work(1, [c = client->creator, fs]() { + c->on_form_submit.call(fs); }); } } else if (i.type == it_autocomplete) { @@ -154,8 +154,8 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri ac.name = string_not_null(&(d["data"]), "name"); fill_options(d["data"]["options"], ac.options); ac.command = i; - client->creator->queue_work(1, [client, ac]() { - client->creator->on_autocomplete.call(ac); + client->creator->queue_work(1, [c = client->creator, ac]() { + c->on_autocomplete.call(ac); }); } } else if (i.type == it_component_button) { @@ -166,8 +166,8 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri ic.command = i; ic.custom_id = bi.custom_id; ic.component_type = bi.component_type; - client->creator->queue_work(1, [client, ic]() { - client->creator->on_button_click.call(ic); + client->creator->queue_work(1, [c = client->creator, ic]() { + c->on_button_click.call(ic); }); } } else if (bi.component_type == cot_selectmenu || bi.component_type == cot_user_selectmenu || @@ -179,8 +179,8 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri ic.custom_id = bi.custom_id; ic.component_type = bi.component_type; ic.values = bi.values; - client->creator->queue_work(1, [client, ic]() { - client->creator->on_select_click.call(ic); + client->creator->queue_work(1, [c = client->creator, ic]() { + c->on_select_click.call(ic); }); } } diff --git a/src/dpp/events/invite_create.cpp b/src/dpp/events/invite_create.cpp index dccdcf377d..d06b38eaa2 100644 --- a/src/dpp/events/invite_create.cpp +++ b/src/dpp/events/invite_create.cpp @@ -41,8 +41,8 @@ void invite_create::handle(discord_client* client, json &j, const std::string &r json& d = j["d"]; dpp::invite_create_t ci(client, raw); ci.created_invite = dpp::invite().fill_from_json(&d); - client->creator->queue_work(1, [client, ci]() { - client->creator->on_invite_create.call(ci); + client->creator->queue_work(1, [c = client->creator, ci]() { + c->on_invite_create.call(ci); }); } } diff --git a/src/dpp/events/invite_delete.cpp b/src/dpp/events/invite_delete.cpp index cb9eb5acf2..5a77618e14 100644 --- a/src/dpp/events/invite_delete.cpp +++ b/src/dpp/events/invite_delete.cpp @@ -41,8 +41,8 @@ void invite_delete::handle(discord_client* client, json &j, const std::string &r json& d = j["d"]; dpp::invite_delete_t cd(client, raw); cd.deleted_invite = dpp::invite().fill_from_json(&d); - client->creator->queue_work(1, [client, cd]() { - client->creator->on_invite_delete.call(cd); + client->creator->queue_work(1, [c = client->creator, cd]() { + c->on_invite_delete.call(cd); }); } } diff --git a/src/dpp/events/logger.cpp b/src/dpp/events/logger.cpp index 5a9dcf5dd5..33c6222433 100644 --- a/src/dpp/events/logger.cpp +++ b/src/dpp/events/logger.cpp @@ -40,7 +40,7 @@ void logger::handle(discord_client* client, json &j, const std::string &raw) { dpp::log_t logmsg(client, raw); logmsg.severity = (dpp::loglevel)from_string(raw.substr(0, raw.find(';'))); logmsg.message = raw.substr(raw.find(';') + 1, raw.length()); - client->creator->on_log.call(logmsg); + c->on_log.call(logmsg); } } diff --git a/src/dpp/events/message_create.cpp b/src/dpp/events/message_create.cpp index 9c79d11d57..ddd97292e0 100644 --- a/src/dpp/events/message_create.cpp +++ b/src/dpp/events/message_create.cpp @@ -39,12 +39,12 @@ void message_create::handle(discord_client* client, json &j, const std::string & if (!client->creator->on_message_create.empty()) { json js = j; - client->creator->queue_work(1, [client, js, raw]() { + client->creator->queue_work(1, [c = client->creator, js, raw]() { json d = js["d"]; dpp::message_create_t msg(client, raw); msg.msg = message(client->owner).fill_from_json(&d, client->creator->cache_policy); msg.msg.owner = client->creator; - client->creator->on_message_create.call(msg); + c->on_message_create.call(msg); }); } } diff --git a/src/dpp/events/message_delete.cpp b/src/dpp/events/message_delete.cpp index c76f5fc5a1..e3bde8f88d 100644 --- a/src/dpp/events/message_delete.cpp +++ b/src/dpp/events/message_delete.cpp @@ -43,8 +43,8 @@ void message_delete::handle(discord_client* client, json &j, const std::string & msg.id = snowflake_not_null(&d, "id"); msg.guild_id = snowflake_not_null(&d, "guild_id"); msg.channel_id = snowflake_not_null(&d, "channel_id"); - client->creator->queue_work(1, [client, msg]() { - client->creator->on_message_delete.call(msg); + client->creator->queue_work(1, [c = client->creator, msg]() { + c->on_message_delete.call(msg); }); } diff --git a/src/dpp/events/message_delete_bulk.cpp b/src/dpp/events/message_delete_bulk.cpp index 554d4f6f59..ae53d1501b 100644 --- a/src/dpp/events/message_delete_bulk.cpp +++ b/src/dpp/events/message_delete_bulk.cpp @@ -44,8 +44,8 @@ void message_delete_bulk::handle(discord_client* client, json &j, const std::str for (auto& m : d["ids"]) { msg.deleted.push_back(from_string(m.get())); } - client->creator->queue_work(1, [client, msg]() { - client->creator->on_message_delete_bulk.call(msg); + client->creator->queue_work(1, [c = client->creator, msg]() { + c->on_message_delete_bulk.call(msg); }); } diff --git a/src/dpp/events/message_poll_vote_add.cpp b/src/dpp/events/message_poll_vote_add.cpp index de957e9f41..0e9e730b14 100644 --- a/src/dpp/events/message_poll_vote_add.cpp +++ b/src/dpp/events/message_poll_vote_add.cpp @@ -46,8 +46,8 @@ void message_poll_vote_add::handle(discord_client* client, json &j, const std::s vote.channel_id = snowflake_not_null(&j, "channel_id"); vote.guild_id = snowflake_not_null(&j, "guild_id"); vote.answer_id = int32_not_null(&j, "answer_id"); - client->creator->queue_work(1, [client, vote]() { - client->creator->on_message_poll_vote_add.call(vote); + client->creator->queue_work(1, [c = client->creator, vote]() { + c->on_message_poll_vote_add.call(vote); }); } } diff --git a/src/dpp/events/message_poll_vote_remove.cpp b/src/dpp/events/message_poll_vote_remove.cpp index 3d4cb0eb13..9bf0d0d6eb 100644 --- a/src/dpp/events/message_poll_vote_remove.cpp +++ b/src/dpp/events/message_poll_vote_remove.cpp @@ -46,8 +46,8 @@ void message_poll_vote_remove::handle(discord_client* client, json &j, const std vote.channel_id = snowflake_not_null(&j, "channel_id"); vote.guild_id = snowflake_not_null(&j, "guild_id"); vote.answer_id = int32_not_null(&j, "answer_id"); - client->creator->queue_work(1, [client, vote]() { - client->creator->on_message_poll_vote_remove.call(vote); + client->creator->queue_work(1, [c = client->creator, vote]() { + c->on_message_poll_vote_remove.call(vote); }); } } diff --git a/src/dpp/events/message_reaction_add.cpp b/src/dpp/events/message_reaction_add.cpp index ccb28ace04..52acbcffb8 100644 --- a/src/dpp/events/message_reaction_add.cpp +++ b/src/dpp/events/message_reaction_add.cpp @@ -50,8 +50,8 @@ void message_reaction_add::handle(discord_client* client, json &j, const std::st mra.message_author_id = snowflake_not_null(&d, "message_author_id"); mra.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mra.channel_id && mra.message_id) { - client->creator->queue_work(1, [client, mra]() { - client->creator->on_message_reaction_add.call(mra); + client->creator->queue_work(1, [c = client->creator, mra]() { + c->on_message_reaction_add.call(mra); }); } } diff --git a/src/dpp/events/message_reaction_remove.cpp b/src/dpp/events/message_reaction_remove.cpp index 794f84c583..380f776828 100644 --- a/src/dpp/events/message_reaction_remove.cpp +++ b/src/dpp/events/message_reaction_remove.cpp @@ -48,8 +48,8 @@ void message_reaction_remove::handle(discord_client* client, json &j, const std: mrr.message_id = snowflake_not_null(&d, "message_id"); mrr.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mrr.channel_id && mrr.message_id) { - client->creator->queue_work(1, [client, mrr]() { - client->creator->on_message_reaction_remove.call(mrr); + client->creator->queue_work(1, [c = client->creator, mrr]() { + c->on_message_reaction_remove.call(mrr); }); } } diff --git a/src/dpp/events/message_reaction_remove_all.cpp b/src/dpp/events/message_reaction_remove_all.cpp index dd0ae2de61..5c71f85485 100644 --- a/src/dpp/events/message_reaction_remove_all.cpp +++ b/src/dpp/events/message_reaction_remove_all.cpp @@ -45,8 +45,8 @@ void message_reaction_remove_all::handle(discord_client* client, json &j, const mrra.reacting_channel = dpp::find_channel(mrra.channel_id); mrra.message_id = snowflake_not_null(&d, "message_id"); if (mrra.channel_id && mrra.message_id) { - client->creator->queue_work(1, [client, mrra]() { - client->creator->on_message_reaction_remove_all.call(mrra); + client->creator->queue_work(1, [c = client->creator, mrra]() { + c->on_message_reaction_remove_all.call(mrra); }); } } diff --git a/src/dpp/events/message_reaction_remove_emoji.cpp b/src/dpp/events/message_reaction_remove_emoji.cpp index 4de8ae7326..1c38b5fadd 100644 --- a/src/dpp/events/message_reaction_remove_emoji.cpp +++ b/src/dpp/events/message_reaction_remove_emoji.cpp @@ -46,8 +46,8 @@ void message_reaction_remove_emoji::handle(discord_client* client, json &j, cons mrre.message_id = snowflake_not_null(&d, "message_id"); mrre.reacting_emoji = dpp::emoji().fill_from_json(&(d["emoji"])); if (mrre.channel_id && mrre.message_id) { - client->creator->queue_work(1, [client, mrre]() { - client->creator->on_message_reaction_remove_emoji.call(mrre); + client->creator->queue_work(1, [c = client->creator, mrre]() { + c->on_message_reaction_remove_emoji.call(mrre); }); } } diff --git a/src/dpp/events/message_update.cpp b/src/dpp/events/message_update.cpp index 9f3408dba0..6f6f5bf028 100644 --- a/src/dpp/events/message_update.cpp +++ b/src/dpp/events/message_update.cpp @@ -43,8 +43,8 @@ void message_update::handle(discord_client* client, json &j, const std::string & dpp::message m(client->creator); m.fill_from_json(&d); msg.msg = m; - client->creator->queue_work(1, [client, msg]() { - client->creator->on_message_update.call(msg); + client->creator->queue_work(1, [c = client->creator, msg]() { + c->on_message_update.call(msg); }); } diff --git a/src/dpp/events/presence_update.cpp b/src/dpp/events/presence_update.cpp index 24ab26d111..cacbe2aa1a 100644 --- a/src/dpp/events/presence_update.cpp +++ b/src/dpp/events/presence_update.cpp @@ -40,8 +40,8 @@ void presence_update::handle(discord_client* client, json &j, const std::string json& d = j["d"]; dpp::presence_update_t pu(client, raw); pu.rich_presence = dpp::presence().fill_from_json(&d); - client->creator->queue_work(1, [client, pu]() { - client->creator->on_presence_update.call(pu); + client->creator->queue_work(1, [c = client->creator, pu]() { + c->on_presence_update.call(pu); }); } } diff --git a/src/dpp/events/ready.cpp b/src/dpp/events/ready.cpp index b319b91f69..251f455a57 100644 --- a/src/dpp/events/ready.cpp +++ b/src/dpp/events/ready.cpp @@ -75,8 +75,8 @@ void ready::handle(discord_client* client, json &j, const std::string &raw) { r.guilds.emplace_back(snowflake_not_null(&guild, "id")); } r.guild_count = r.guilds.size(); - client->creator->queue_work(1, [client, r]() { - client->creator->on_ready.call(r); + client->creator->queue_work(1, [c = client->creator, r]() { + c->on_ready.call(r); }); } } diff --git a/src/dpp/events/resumed.cpp b/src/dpp/events/resumed.cpp index 4fbe77023a..ad66831d92 100644 --- a/src/dpp/events/resumed.cpp +++ b/src/dpp/events/resumed.cpp @@ -44,8 +44,8 @@ void resumed::handle(discord_client* client, json &j, const std::string &raw) { dpp::resumed_t r(client, raw); r.session_id = client->sessionid; r.shard_id = client->shard_id; - client->creator->queue_work(1, [client, r]() { - client->creator->on_resumed.call(r); + client->creator->queue_work(1, [c = client->creator, r]() { + c->on_resumed.call(r); }); } } diff --git a/src/dpp/events/stage_instance_create.cpp b/src/dpp/events/stage_instance_create.cpp index 47f50382a5..f6049b36c3 100644 --- a/src/dpp/events/stage_instance_create.cpp +++ b/src/dpp/events/stage_instance_create.cpp @@ -41,8 +41,8 @@ void stage_instance_create::handle(discord_client* client, json &j, const std::s json& d = j["d"]; dpp::stage_instance_create_t sic(client, raw); sic.created.fill_from_json(&d); - client->creator->queue_work(1, [client, sic]() { - client->creator->on_stage_instance_create.call(sic); + client->creator->queue_work(1, [c = client->creator, sic]() { + c->on_stage_instance_create.call(sic); }); } } diff --git a/src/dpp/events/stage_instance_delete.cpp b/src/dpp/events/stage_instance_delete.cpp index c5f6dbb26e..15cd1a0522 100644 --- a/src/dpp/events/stage_instance_delete.cpp +++ b/src/dpp/events/stage_instance_delete.cpp @@ -39,8 +39,8 @@ void stage_instance_delete::handle(discord_client* client, json &j, const std::s json& d = j["d"]; dpp::stage_instance_delete_t sid(client, raw); sid.deleted.fill_from_json(&d); - client->creator->queue_work(1, [client, sid]() { - client->creator->on_stage_instance_delete.call(sid); + client->creator->queue_work(1, [c = client->creator, sid]() { + c->on_stage_instance_delete.call(sid); }); } } diff --git a/src/dpp/events/stage_instance_update.cpp b/src/dpp/events/stage_instance_update.cpp index 29b4cf066d..6f1febb7b9 100644 --- a/src/dpp/events/stage_instance_update.cpp +++ b/src/dpp/events/stage_instance_update.cpp @@ -41,8 +41,8 @@ void stage_instance_update::handle(discord_client* client, json &j, const std::s json& d = j["d"]; dpp::stage_instance_update_t siu(client, raw); siu.updated.fill_from_json(&d); - client->creator->queue_work(1, [client, siu]() { - client->creator->on_stage_instance_update.call(siu); + client->creator->queue_work(1, [c = client->creator, siu]() { + c->on_stage_instance_update.call(siu); }); } } diff --git a/src/dpp/events/thread_create.cpp b/src/dpp/events/thread_create.cpp index 51a59779e5..01d3ad3022 100644 --- a/src/dpp/events/thread_create.cpp +++ b/src/dpp/events/thread_create.cpp @@ -42,8 +42,8 @@ void thread_create::handle(discord_client* client, json& j, const std::string& r dpp::thread_create_t tc(client, raw); tc.created = t; tc.creating_guild = g; - client->creator->queue_work(1, [client, tc]() { - client->creator->on_thread_create.call(tc); + client->creator->queue_work(1, [c = client->creator, tc]() { + c->on_thread_create.call(tc); }); } } diff --git a/src/dpp/events/thread_delete.cpp b/src/dpp/events/thread_delete.cpp index f9fd5cd2b8..4bcab8084d 100644 --- a/src/dpp/events/thread_delete.cpp +++ b/src/dpp/events/thread_delete.cpp @@ -42,8 +42,8 @@ void thread_delete::handle(discord_client* client, json& j, const std::string& r dpp::thread_delete_t td(client, raw); td.deleted = t; td.deleting_guild = g; - client->creator->queue_work(1, [client, td]() { - client->creator->on_thread_delete.call(td); + client->creator->queue_work(1, [c = client->creator, td]() { + c->on_thread_delete.call(td); }); } } diff --git a/src/dpp/events/thread_list_sync.cpp b/src/dpp/events/thread_list_sync.cpp index 45dbbd1b9e..718f834783 100644 --- a/src/dpp/events/thread_list_sync.cpp +++ b/src/dpp/events/thread_list_sync.cpp @@ -52,8 +52,8 @@ void thread_list_sync::handle(discord_client* client, json& j, const std::string tls.members.push_back(thread_member().fill_from_json(&tm)); } } - client->creator->queue_work(1, [client, tls]() { - client->creator->on_thread_list_sync.call(tls); + client->creator->queue_work(1, [c = client->creator, tls]() { + c->on_thread_list_sync.call(tls); }); } } diff --git a/src/dpp/events/thread_member_update.cpp b/src/dpp/events/thread_member_update.cpp index 84a97e5603..39c3bafbff 100644 --- a/src/dpp/events/thread_member_update.cpp +++ b/src/dpp/events/thread_member_update.cpp @@ -34,8 +34,8 @@ void thread_member_update::handle(discord_client* client, json& j, const std::st json& d = j["d"]; dpp::thread_member_update_t tm(client, raw); tm.updated = thread_member().fill_from_json(&d); - client->creator->queue_work(1, [client, tm]() { - client->creator->on_thread_member_update.call(tm); + client->creator->queue_work(1, [c = client->creator, tm]() { + c->on_thread_member_update.call(tm); }); } } diff --git a/src/dpp/events/thread_members_update.cpp b/src/dpp/events/thread_members_update.cpp index aba9bedf98..8968772011 100644 --- a/src/dpp/events/thread_members_update.cpp +++ b/src/dpp/events/thread_members_update.cpp @@ -52,8 +52,8 @@ void thread_members_update::handle(discord_client* client, json& j, const std::s client->creator->log(dpp::ll_error, std::string("thread_members_update: {}") + e.what()); } } - client->creator->queue_work(1, [client, tms]() { - client->creator->on_thread_members_update.call(tms); + client->creator->queue_work(1, [c = client->creator, tms]() { + c->on_thread_members_update.call(tms); }); } } diff --git a/src/dpp/events/thread_update.cpp b/src/dpp/events/thread_update.cpp index e6fa3bec80..797b184a53 100644 --- a/src/dpp/events/thread_update.cpp +++ b/src/dpp/events/thread_update.cpp @@ -38,8 +38,8 @@ void thread_update::handle(discord_client* client, json& j, const std::string& r dpp::thread_update_t tu(client, raw); tu.updated = t; tu.updating_guild = g; - client->creator->queue_work(1, [client, tu]() { - client->creator->on_thread_update.call(tu); + client->creator->queue_work(1, [c = client->creator, tu]() { + c->on_thread_update.call(tu); }); } } diff --git a/src/dpp/events/typing_start.cpp b/src/dpp/events/typing_start.cpp index 4ffc231e8f..aa000607c7 100644 --- a/src/dpp/events/typing_start.cpp +++ b/src/dpp/events/typing_start.cpp @@ -43,8 +43,8 @@ void typing_start::handle(discord_client* client, json &j, const std::string &ra ts.user_id = snowflake_not_null(&d, "user_id"); ts.typing_user = dpp::find_user(ts.user_id); ts.timestamp = ts_not_null(&d, "timestamp"); - client->creator->queue_work(1, [client, ts]() { - client->creator->on_typing_start.call(ts); + client->creator->queue_work(1, [c = client->creator, ts]() { + c->on_typing_start.call(ts); }); } } diff --git a/src/dpp/events/user_update.cpp b/src/dpp/events/user_update.cpp index d018825c87..2766d34b30 100644 --- a/src/dpp/events/user_update.cpp +++ b/src/dpp/events/user_update.cpp @@ -49,8 +49,8 @@ void user_update::handle(discord_client* client, json &j, const std::string &raw if (!client->creator->on_user_update.empty()) { dpp::user_update_t uu(client, raw); uu.updated = *u; - client->creator->queue_work(1, [client, uu]() { - client->creator->on_user_update.call(uu); + client->creator->queue_work(1, [c = client->creator, uu]() { + c->on_user_update.call(uu); }); } } else { @@ -59,8 +59,8 @@ void user_update::handle(discord_client* client, json &j, const std::string &raw u.fill_from_json(&d); dpp::user_update_t uu(client, raw); uu.updated = u; - client->creator->queue_work(1, [client, uu]() { - client->creator->on_user_update.call(uu); + client->creator->queue_work(1, [c = client->creator, uu]() { + c->on_user_update.call(uu); }); } } diff --git a/src/dpp/events/voice_server_update.cpp b/src/dpp/events/voice_server_update.cpp index ea1c8e07d5..864584be97 100644 --- a/src/dpp/events/voice_server_update.cpp +++ b/src/dpp/events/voice_server_update.cpp @@ -60,8 +60,8 @@ void voice_server_update::handle(discord_client* client, json &j, const std::str } if (!client->creator->on_voice_server_update.empty()) { - client->creator->queue_work(1, [client, vsu]() { - client->creator->on_voice_server_update.call(vsu); + client->creator->queue_work(1, [c = client->creator, vsu]() { + c->on_voice_server_update.call(vsu); }); } } diff --git a/src/dpp/events/voice_state_update.cpp b/src/dpp/events/voice_state_update.cpp index 7831564b7f..9b2b9f0543 100644 --- a/src/dpp/events/voice_state_update.cpp +++ b/src/dpp/events/voice_state_update.cpp @@ -84,8 +84,8 @@ void voice_state_update::handle(discord_client* client, json &j, const std::stri } if (!client->creator->on_voice_state_update.empty()) { - client->creator->queue_work(1, [client, vsu]() { - client->creator->on_voice_state_update.call(vsu); + client->creator->queue_work(1, [c = client->creator, vsu]() { + c->on_voice_state_update.call(vsu); }); } } diff --git a/src/dpp/events/webhooks_update.cpp b/src/dpp/events/webhooks_update.cpp index bf4ea4ae96..7eb4f0eedf 100644 --- a/src/dpp/events/webhooks_update.cpp +++ b/src/dpp/events/webhooks_update.cpp @@ -41,8 +41,8 @@ void webhooks_update::handle(discord_client* client, json &j, const std::string dpp::webhooks_update_t wu(client, raw); wu.webhook_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); wu.webhook_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); - client->creator->queue_work(1, [client, wu]() { - client->creator->on_webhooks_update.call(wu); + client->creator->queue_work(1, [c = client->creator, wu]() { + c->on_webhooks_update.call(wu); }); } } From 2190cf64b0bb1e446abe68716db05ee64fef25c4 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 14 Dec 2024 00:30:58 +0000 Subject: [PATCH 109/112] make event.from safe in threads --- include/dpp/dispatcher.h | 34 ++++++++++------ src/dpp/cluster.cpp | 2 +- src/dpp/discordclient.cpp | 2 +- src/dpp/dispatcher.cpp | 40 ++++++++++--------- src/dpp/events/automod_rule_create.cpp | 2 +- src/dpp/events/automod_rule_delete.cpp | 2 +- src/dpp/events/automod_rule_execute.cpp | 2 +- src/dpp/events/automod_rule_update.cpp | 2 +- src/dpp/events/channel_create.cpp | 2 +- src/dpp/events/channel_delete.cpp | 2 +- src/dpp/events/channel_pins_update.cpp | 2 +- src/dpp/events/channel_update.cpp | 2 +- src/dpp/events/entitlement_create.cpp | 2 +- src/dpp/events/entitlement_delete.cpp | 2 +- src/dpp/events/entitlement_update.cpp | 2 +- .../events/guild_audit_log_entry_create.cpp | 2 +- src/dpp/events/guild_ban_add.cpp | 2 +- src/dpp/events/guild_ban_remove.cpp | 2 +- src/dpp/events/guild_create.cpp | 2 +- src/dpp/events/guild_delete.cpp | 2 +- src/dpp/events/guild_emojis_update.cpp | 2 +- src/dpp/events/guild_integrations_update.cpp | 2 +- src/dpp/events/guild_join_request_delete.cpp | 2 +- src/dpp/events/guild_member_add.cpp | 2 +- src/dpp/events/guild_member_remove.cpp | 2 +- src/dpp/events/guild_member_update.cpp | 4 +- src/dpp/events/guild_members_chunk.cpp | 2 +- src/dpp/events/guild_role_create.cpp | 4 +- src/dpp/events/guild_role_delete.cpp | 4 +- src/dpp/events/guild_role_update.cpp | 4 +- .../events/guild_scheduled_event_create.cpp | 2 +- .../events/guild_scheduled_event_delete.cpp | 2 +- .../events/guild_scheduled_event_update.cpp | 2 +- .../events/guild_scheduled_event_user_add.cpp | 2 +- .../guild_scheduled_event_user_remove.cpp | 2 +- src/dpp/events/guild_stickers_update.cpp | 2 +- src/dpp/events/guild_update.cpp | 2 +- src/dpp/events/integration_create.cpp | 2 +- src/dpp/events/integration_delete.cpp | 2 +- src/dpp/events/integration_update.cpp | 2 +- src/dpp/events/interaction_create.cpp | 16 ++++---- src/dpp/events/invite_create.cpp | 2 +- src/dpp/events/invite_delete.cpp | 2 +- src/dpp/events/logger.cpp | 4 +- src/dpp/events/message_create.cpp | 9 ++--- src/dpp/events/message_delete.cpp | 2 +- src/dpp/events/message_delete_bulk.cpp | 2 +- src/dpp/events/message_poll_vote_add.cpp | 2 +- src/dpp/events/message_poll_vote_remove.cpp | 2 +- src/dpp/events/message_reaction_add.cpp | 2 +- src/dpp/events/message_reaction_remove.cpp | 2 +- .../events/message_reaction_remove_all.cpp | 2 +- .../events/message_reaction_remove_emoji.cpp | 2 +- src/dpp/events/message_update.cpp | 2 +- src/dpp/events/presence_update.cpp | 2 +- src/dpp/events/ready.cpp | 2 +- src/dpp/events/resumed.cpp | 2 +- src/dpp/events/stage_instance_create.cpp | 2 +- src/dpp/events/stage_instance_delete.cpp | 2 +- src/dpp/events/stage_instance_update.cpp | 2 +- src/dpp/events/thread_create.cpp | 2 +- src/dpp/events/thread_delete.cpp | 2 +- src/dpp/events/thread_list_sync.cpp | 2 +- src/dpp/events/thread_member_update.cpp | 2 +- src/dpp/events/thread_members_update.cpp | 2 +- src/dpp/events/thread_update.cpp | 2 +- src/dpp/events/typing_start.cpp | 2 +- src/dpp/events/user_update.cpp | 4 +- src/dpp/events/voice_server_update.cpp | 2 +- src/dpp/events/voice_state_update.cpp | 2 +- src/dpp/events/webhooks_update.cpp | 2 +- src/dpp/voice/enabled/courier_loop.cpp | 4 +- src/dpp/voice/enabled/handle_frame.cpp | 10 ++--- src/dpp/voice/enabled/read_ready.cpp | 2 +- src/dpp/voice/enabled/write_ready.cpp | 4 +- src/unittest/coro.cpp | 2 +- src/unittest/test.cpp | 2 +- 77 files changed, 141 insertions(+), 128 deletions(-) diff --git a/include/dpp/dispatcher.h b/include/dpp/dispatcher.h index be08488b7b..dad53f509c 100644 --- a/include/dpp/dispatcher.h +++ b/include/dpp/dispatcher.h @@ -85,9 +85,13 @@ struct DPP_EXPORT event_dispatch_t { /** * @brief Shard the event came from. - * Note that for some events, notably voice events, this may be nullptr. */ - discord_client* from = nullptr; + uint32_t shard = 0; + + /** + * @brief Cluster owning the event dispatch + */ + dpp::cluster* owner = nullptr; /** * @brief Whether the event was cancelled using cancel_event(). @@ -116,18 +120,24 @@ struct DPP_EXPORT event_dispatch_t { /** * @brief Construct a new event_dispatch_t object * - * @param client The shard the event originated on. May be a nullptr, e.g. for voice events + * @param shard_id The shard the event originated on. * @param raw Raw event data as JSON or ETF */ - event_dispatch_t(discord_client* client, const std::string& raw); + event_dispatch_t(dpp::cluster* creator, uint32_t shard_id, const std::string& raw); + + /** + * @brief Returns the shard object for the events shard id + * @return discord client object + */ + discord_client* from() const; /** * @brief Construct a new event_dispatch_t object * - * @param client The shard the event originated on. May be a nullptr, e.g. for voice events + * @param shard_id The shard the event originated on. * @param raw Raw event data as JSON or ETF */ - event_dispatch_t(discord_client* client, std::string&& raw); + event_dispatch_t(dpp::cluster* creator, uint32_t shard_id, std::string&& raw); /** * @brief Copy another event_dispatch_t object @@ -2018,28 +2028,28 @@ struct DPP_EXPORT voice_receive_t : public event_dispatch_t { /** * @brief Construct a new voice receive t object * - * @param client The shard the event originated on. - * WILL ALWAYS be NULL. + * @param creator The creating cluster + * @param shard_id Shard the voice channel exists on * @param raw Raw event text as UDP packet. * @param vc owning voice client pointer * @param _user_id user id who is speaking, 0 for a mix of all user audio * @param pcm user audio to set * @param length length of user audio in bytes */ - voice_receive_t(discord_client* client, const std::string& raw, class discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length); + voice_receive_t(dpp::cluster* creator, uint32_t shard_id, const std::string& raw, class discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length); /** * @brief Construct a new voice receive t object * - * @param client The shard the event originated on. - * WILL ALWAYS be NULL. + * @param creator The creating cluster + * @param shard_id Shard the voice channel exists on * @param raw Raw event text as UDP packet. * @param vc owning voice client pointer * @param _user_id user id who is speaking, 0 for a mix of all user audio * @param pcm user audio to set * @param length length of user audio in bytes */ - voice_receive_t(discord_client* client, std::string&& raw, class discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length); + voice_receive_t(dpp::cluster* creator, uint32_t shard_id, std::string&& raw, class discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length); /** * @brief Voice client diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index f9b004da58..1169db32d2 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -185,7 +185,7 @@ void cluster::queue_work(int priority, work_unit task) { void cluster::log(dpp::loglevel severity, const std::string &msg) const { if (!on_log.empty()) { /* Pass to user if they've hooked the event */ - dpp::log_t logmsg(nullptr, msg); + dpp::log_t logmsg(nullptr, 0, msg); logmsg.severity = severity; logmsg.message = msg; size_t pos{0}; diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 070681985d..0865069257 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -366,7 +366,7 @@ void discord_client::log(dpp::loglevel severity, const std::string &msg) const { if (!creator->on_log.empty()) { /* Pass to user if they've hooked the event */ - dpp::log_t logmsg(nullptr, msg); + dpp::log_t logmsg(creator, shard_id, msg); logmsg.severity = severity; logmsg.message = msg; size_t pos{0}; diff --git a/src/dpp/dispatcher.cpp b/src/dpp/dispatcher.cpp index db8ea2e888..314a1f1efe 100644 --- a/src/dpp/dispatcher.cpp +++ b/src/dpp/dispatcher.cpp @@ -31,9 +31,13 @@ namespace dpp { -event_dispatch_t::event_dispatch_t(discord_client* client, const std::string& raw) : raw_event(raw), from(client) {} +event_dispatch_t::event_dispatch_t(dpp::cluster* creator, uint32_t shard_id, const std::string& raw) : raw_event(raw), shard(shard_id), owner(creator) {} -event_dispatch_t::event_dispatch_t(discord_client* client, std::string&& raw) : raw_event(std::move(raw)), from(client) {} +event_dispatch_t::event_dispatch_t(dpp::cluster* creator, uint32_t shard_id, std::string&& raw) : raw_event(std::move(raw)), shard(shard_id), owner(creator) {} + +discord_client* event_dispatch_t::from() const { + return owner->get_shard(shard); +} const event_dispatch_t& event_dispatch_t::cancel_event() const { cancelled = true; @@ -73,12 +77,12 @@ void message_create_t::send(const std::string& m, command_completion_event_t cal } void message_create_t::send(const message& msg, command_completion_event_t callback) const { - this->from->creator->message_create(std::move(message{msg}.set_channel_id(this->msg.channel_id)), std::move(callback)); + owner->message_create(std::move(message{msg}.set_channel_id(this->msg.channel_id)), std::move(callback)); } void message_create_t::send(message&& msg, command_completion_event_t callback) const { msg.channel_id = this->msg.channel_id; - this->from->creator->message_create(std::move(msg), std::move(callback)); + owner->message_create(std::move(msg), std::move(callback)); } void message_create_t::reply(const std::string& m, bool mention_replied_user, command_completion_event_t callback) const { @@ -94,7 +98,7 @@ void message_create_t::reply(const message& msg, bool mention_replied_user, comm msg_to_send.allowed_mentions.replied_user = mention_replied_user; msg_to_send.allowed_mentions.users.push_back(this->msg.author.id); } - this->from->creator->message_create(std::move(msg_to_send), std::move(callback)); + owner->message_create(std::move(msg_to_send), std::move(callback)); } void message_create_t::reply(message&& msg, bool mention_replied_user, command_completion_event_t callback) const { @@ -104,15 +108,15 @@ void message_create_t::reply(message&& msg, bool mention_replied_user, command_c msg.allowed_mentions.replied_user = mention_replied_user; msg.allowed_mentions.users.push_back(this->msg.author.id); } - this->from->creator->message_create(std::move(msg), std::move(callback)); + owner->message_create(std::move(msg), std::move(callback)); } void interaction_create_t::reply(interaction_response_type t, const message& m, command_completion_event_t callback) const { - from->creator->interaction_response_create(this->command.id, this->command.token, dpp::interaction_response(t, m), std::move(callback)); + owner->interaction_response_create(this->command.id, this->command.token, dpp::interaction_response(t, m), std::move(callback)); } void interaction_create_t::reply(const message& m, command_completion_event_t callback) const { - from->creator->interaction_response_create( + owner->interaction_response_create( this->command.id, this->command.token, dpp::interaction_response(ir_channel_message_with_source, m), @@ -134,7 +138,7 @@ void interaction_create_t::reply(command_completion_event_t callback) const { } void interaction_create_t::dialog(const interaction_modal_response& mr, command_completion_event_t callback) const { - from->creator->interaction_response_create(this->command.id, this->command.token, mr, std::move(callback)); + owner->interaction_response_create(this->command.id, this->command.token, mr, std::move(callback)); } void interaction_create_t::reply(interaction_response_type t, const std::string& mt, command_completion_event_t callback) const { @@ -146,7 +150,7 @@ void interaction_create_t::reply(const std::string& mt, command_completion_event } void interaction_create_t::edit_response(const message& m, command_completion_event_t callback) const { - from->creator->interaction_response_edit(this->command.token, m, std::move(callback)); + owner->interaction_response_edit(this->command.token, m, std::move(callback)); } void interaction_create_t::edit_response(const std::string& mt, command_completion_event_t callback) const { @@ -154,9 +158,9 @@ void interaction_create_t::edit_response(const std::string& mt, command_completi } void interaction_create_t::get_original_response(command_completion_event_t callback) const { - from->creator->post_rest(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_get, "", [creator = this->from->creator, cb = std::move(callback)](json& j, const http_request_completion_t& http) { + owner->post_rest(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_get, "", [owner = this->owner, cb = std::move(callback)](json& j, const http_request_completion_t& http) { if (cb) { - cb(confirmation_callback_t(creator, message().fill_from_json(&j), http)); + cb(confirmation_callback_t(owner, message().fill_from_json(&j), http)); } }); } @@ -172,17 +176,17 @@ void interaction_create_t::edit_original_response(const message& m, command_comp file_mimetypes.push_back(data.mimetype); } - from->creator->post_rest_multipart(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_patch, m.build_json(), [creator = this->from->creator, cb = std::move(callback)](json& j, const http_request_completion_t& http) { + owner->post_rest_multipart(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_patch, m.build_json(), [owner = this->owner, cb = std::move(callback)](json& j, const http_request_completion_t& http) { if (cb) { - cb(confirmation_callback_t(creator, message().fill_from_json(&j), http)); + cb(confirmation_callback_t(owner, message().fill_from_json(&j), http)); } }, m.file_data); } void interaction_create_t::delete_original_response(command_completion_event_t callback) const { - from->creator->post_rest(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_delete, "", [creator = this->from->creator, cb = std::move(callback)](const json &, const http_request_completion_t& http) { + owner->post_rest(API_PATH "/webhooks", std::to_string(command.application_id), command.token + "/messages/@original", m_delete, "", [owner = this->owner, cb = std::move(callback)](const json &, const http_request_completion_t& http) { if (cb) { - cb(confirmation_callback_t(creator, confirmation(), http)); + cb(confirmation_callback_t(owner, confirmation(), http)); } }); } @@ -267,11 +271,11 @@ command_value interaction_create_t::get_parameter(const std::string& name) const return {}; } -voice_receive_t::voice_receive_t(discord_client* client, const std::string& raw, discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length) : event_dispatch_t(client, raw), voice_client(vc), user_id(_user_id) { +voice_receive_t::voice_receive_t(dpp::cluster* creator, uint32_t shard_id, const std::string& raw, discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length) : event_dispatch_t(owner, shard_id, std::move(raw)), voice_client(vc), user_id(_user_id) { reassign(vc, _user_id, pcm, length); } -voice_receive_t::voice_receive_t(discord_client* client, std::string&& raw, discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length) : event_dispatch_t(client, std::move(raw)), voice_client(vc), user_id(_user_id) { +voice_receive_t::voice_receive_t(dpp::cluster* creator, uint32_t shard_id, std::string&& raw, discord_voice_client* vc, snowflake _user_id, const uint8_t* pcm, size_t length) : event_dispatch_t(owner, shard_id, std::move(raw)), voice_client(vc), user_id(_user_id) { reassign(vc, _user_id, pcm, length); } diff --git a/src/dpp/events/automod_rule_create.cpp b/src/dpp/events/automod_rule_create.cpp index 49849cc05f..0a1bdab273 100644 --- a/src/dpp/events/automod_rule_create.cpp +++ b/src/dpp/events/automod_rule_create.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void automod_rule_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_automod_rule_create.empty()) { json& d = j["d"]; - automod_rule_create_t arc(client, raw); + automod_rule_create_t arc(client->owner, client->shard_id, raw); arc.created = automod_rule().fill_from_json(&d); client->creator->queue_work(0, [c = client->creator, arc]() { c->on_automod_rule_create.call(arc); diff --git a/src/dpp/events/automod_rule_delete.cpp b/src/dpp/events/automod_rule_delete.cpp index bd44fb9cf0..59ffc57655 100644 --- a/src/dpp/events/automod_rule_delete.cpp +++ b/src/dpp/events/automod_rule_delete.cpp @@ -37,7 +37,7 @@ namespace dpp::events { void automod_rule_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_automod_rule_create.empty()) { json& d = j["d"]; - automod_rule_delete_t ard(client, raw); + automod_rule_delete_t ard(client->owner, client->shard_id, raw); ard.deleted = automod_rule().fill_from_json(&d); client->creator->queue_work(0, [c = client->creator, ard]() { c->on_automod_rule_delete.call(ard); diff --git a/src/dpp/events/automod_rule_execute.cpp b/src/dpp/events/automod_rule_execute.cpp index ef27d7d7f8..c2699d228b 100644 --- a/src/dpp/events/automod_rule_execute.cpp +++ b/src/dpp/events/automod_rule_execute.cpp @@ -37,7 +37,7 @@ namespace dpp::events { void automod_rule_execute::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_automod_rule_execute.empty()) { json& d = j["d"]; - automod_rule_execute_t are(client, raw); + automod_rule_execute_t are(client->owner, client->shard_id, raw); are.guild_id = snowflake_not_null(&d, "guild_id"); are.action = dpp::automod_action().fill_from_json(&(d["action"])); are.rule_id = snowflake_not_null(&d, "rule_id"); diff --git a/src/dpp/events/automod_rule_update.cpp b/src/dpp/events/automod_rule_update.cpp index 097d3ab58b..c14a968bf7 100644 --- a/src/dpp/events/automod_rule_update.cpp +++ b/src/dpp/events/automod_rule_update.cpp @@ -37,7 +37,7 @@ namespace dpp::events { void automod_rule_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_automod_rule_update.empty()) { json& d = j["d"]; - automod_rule_update_t aru(client, raw); + automod_rule_update_t aru(client->owner, client->shard_id, raw); aru.updated = automod_rule().fill_from_json(&d); client->creator->queue_work(0, [c = client->creator, aru]() { c->on_automod_rule_update.call(aru); diff --git a/src/dpp/events/channel_create.cpp b/src/dpp/events/channel_create.cpp index 2b931bec60..ae6bc3d1b0 100644 --- a/src/dpp/events/channel_create.cpp +++ b/src/dpp/events/channel_create.cpp @@ -67,7 +67,7 @@ void channel_create::handle(discord_client* client, json &j, const std::string & } } if (!client->creator->on_channel_create.empty()) { - dpp::channel_create_t cc(client, raw); + dpp::channel_create_t cc(client->owner, client->shard_id, raw); cc.created = c; cc.creating_guild = g; client->creator->queue_work(1, [c = client->creator, cc]() { diff --git a/src/dpp/events/channel_delete.cpp b/src/dpp/events/channel_delete.cpp index 9d6677140b..84898b2eb0 100644 --- a/src/dpp/events/channel_delete.cpp +++ b/src/dpp/events/channel_delete.cpp @@ -48,7 +48,7 @@ void channel_delete::handle(discord_client* client, json &j, const std::string & get_channel_cache()->remove(find_channel(c.id)); } if (!client->creator->on_channel_delete.empty()) { - channel_delete_t cd(client, raw); + channel_delete_t cd(client->owner, client->shard_id, raw); cd.deleted = c; cd.deleting_guild = g; client->creator->queue_work(1, [c = client->creator, cd]() { diff --git a/src/dpp/events/channel_pins_update.cpp b/src/dpp/events/channel_pins_update.cpp index f9012aea36..e3fe01cb57 100644 --- a/src/dpp/events/channel_pins_update.cpp +++ b/src/dpp/events/channel_pins_update.cpp @@ -39,7 +39,7 @@ void channel_pins_update::handle(discord_client* client, json &j, const std::str if (!client->creator->on_channel_pins_update.empty()) { json& d = j["d"]; - dpp::channel_pins_update_t cpu(client, raw); + dpp::channel_pins_update_t cpu(client->owner, client->shard_id, raw); cpu.pin_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); cpu.pin_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); cpu.timestamp = ts_not_null(&d, "last_pin_timestamp"); diff --git a/src/dpp/events/channel_update.cpp b/src/dpp/events/channel_update.cpp index 44cb24c5c9..5d7a9d8276 100644 --- a/src/dpp/events/channel_update.cpp +++ b/src/dpp/events/channel_update.cpp @@ -50,7 +50,7 @@ void channel_update::handle(discord_client* client, json &j, const std::string & } } if (!client->creator->on_channel_update.empty()) { - dpp::channel_update_t cu(client, raw); + dpp::channel_update_t cu(client->owner, client->shard_id, raw); cu.updated = c; cu.updating_guild = dpp::find_guild(c->guild_id); client->creator->queue_work(1, [c = client->creator, cu]() { diff --git a/src/dpp/events/entitlement_create.cpp b/src/dpp/events/entitlement_create.cpp index 3e42b675d6..59f0687877 100644 --- a/src/dpp/events/entitlement_create.cpp +++ b/src/dpp/events/entitlement_create.cpp @@ -38,7 +38,7 @@ void entitlement_create::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; ent.fill_from_json(&d); - dpp::entitlement_create_t entitlement_event(client, raw); + dpp::entitlement_create_t entitlement_event(client->owner, client->shard_id, raw); entitlement_event.created = ent; client->creator->queue_work(0, [c = client->creator, entitlement_event]() { diff --git a/src/dpp/events/entitlement_delete.cpp b/src/dpp/events/entitlement_delete.cpp index 196ed0774a..2315de963e 100644 --- a/src/dpp/events/entitlement_delete.cpp +++ b/src/dpp/events/entitlement_delete.cpp @@ -38,7 +38,7 @@ void entitlement_delete::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; ent.fill_from_json(&d); - dpp::entitlement_delete_t entitlement_event(client, raw); + dpp::entitlement_delete_t entitlement_event(client->owner, client->shard_id, raw); entitlement_event.deleted = ent; client->creator->queue_work(0, [c = client->creator, entitlement_event]() { diff --git a/src/dpp/events/entitlement_update.cpp b/src/dpp/events/entitlement_update.cpp index 150b2e0c0b..34979ade78 100644 --- a/src/dpp/events/entitlement_update.cpp +++ b/src/dpp/events/entitlement_update.cpp @@ -38,7 +38,7 @@ void entitlement_update::handle(discord_client* client, json &j, const std::stri json& d = j["d"]; ent.fill_from_json(&d); - dpp::entitlement_update_t entitlement_event(client, raw); + dpp::entitlement_update_t entitlement_event(client->owner, client->shard_id, raw); entitlement_event.updating_entitlement = ent; client->creator->queue_work(0, [c = client->creator, entitlement_event]() { diff --git a/src/dpp/events/guild_audit_log_entry_create.cpp b/src/dpp/events/guild_audit_log_entry_create.cpp index 15b5fc280f..de689d7a79 100644 --- a/src/dpp/events/guild_audit_log_entry_create.cpp +++ b/src/dpp/events/guild_audit_log_entry_create.cpp @@ -35,7 +35,7 @@ namespace dpp::events { void guild_audit_log_entry_create::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_audit_log_entry_create.empty()) { - dpp::guild_audit_log_entry_create_t ec(client, raw); + dpp::guild_audit_log_entry_create_t ec(client->owner, client->shard_id, raw); ec.entry.fill_from_json(&d); client->creator->queue_work(2, [c = client->creator, ec]() { c->on_guild_audit_log_entry_create.call(ec); diff --git a/src/dpp/events/guild_ban_add.cpp b/src/dpp/events/guild_ban_add.cpp index 82a2df68aa..cbec907aca 100644 --- a/src/dpp/events/guild_ban_add.cpp +++ b/src/dpp/events/guild_ban_add.cpp @@ -41,7 +41,7 @@ namespace dpp::events { void guild_ban_add::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_guild_ban_add.empty()) { json &d = j["d"]; - dpp::guild_ban_add_t gba(client, raw); + dpp::guild_ban_add_t gba(client->owner, client->shard_id, raw); gba.banning_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); gba.banned = dpp::user().fill_from_json(&(d["user"])); client->creator->queue_work(1, [c = client->creator, gba]() { diff --git a/src/dpp/events/guild_ban_remove.cpp b/src/dpp/events/guild_ban_remove.cpp index 7a45be894d..f8b5b451c2 100644 --- a/src/dpp/events/guild_ban_remove.cpp +++ b/src/dpp/events/guild_ban_remove.cpp @@ -41,7 +41,7 @@ namespace dpp::events { void guild_ban_remove::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_guild_ban_remove.empty()) { json &d = j["d"]; - dpp::guild_ban_remove_t gbr(client, raw); + dpp::guild_ban_remove_t gbr(client->owner, client->shard_id, raw); gbr.unbanning_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); gbr.unbanned = dpp::user().fill_from_json(&(d["user"])); client->creator->queue_work(1, [c = client->creator, gbr]() { diff --git a/src/dpp/events/guild_create.cpp b/src/dpp/events/guild_create.cpp index 5e6c3a7177..e80746a899 100644 --- a/src/dpp/events/guild_create.cpp +++ b/src/dpp/events/guild_create.cpp @@ -147,7 +147,7 @@ void guild_create::handle(discord_client* client, json &j, const std::string &ra } if (!client->creator->on_guild_create.empty()) { - dpp::guild_create_t gc(client, raw); + dpp::guild_create_t gc(client->owner, client->shard_id, raw); gc.created = g; /* Fill presences if there are any */ diff --git a/src/dpp/events/guild_delete.cpp b/src/dpp/events/guild_delete.cpp index c9b475b4a2..1711a0cb2c 100644 --- a/src/dpp/events/guild_delete.cpp +++ b/src/dpp/events/guild_delete.cpp @@ -87,7 +87,7 @@ void guild_delete::handle(discord_client* client, json &j, const std::string &ra } if (!client->creator->on_guild_delete.empty()) { - dpp::guild_delete_t gd(client, raw); + dpp::guild_delete_t gd(client->owner, client->shard_id, raw); gd.deleted = guild_del; gd.guild_id = guild_del.id; client->creator->queue_work(0, [c = client->creator, gd]() { diff --git a/src/dpp/events/guild_emojis_update.cpp b/src/dpp/events/guild_emojis_update.cpp index 98125a9603..5ba2895bdc 100644 --- a/src/dpp/events/guild_emojis_update.cpp +++ b/src/dpp/events/guild_emojis_update.cpp @@ -71,7 +71,7 @@ void guild_emojis_update::handle(discord_client* client, json &j, const std::str } } if (!client->creator->on_guild_emojis_update.empty()) { - dpp::guild_emojis_update_t geu(client, raw); + dpp::guild_emojis_update_t geu(client->owner, client->shard_id, raw); geu.emojis = emojis; geu.updating_guild = g; client->creator->queue_work(1, [c = client->creator, geu]() { diff --git a/src/dpp/events/guild_integrations_update.cpp b/src/dpp/events/guild_integrations_update.cpp index 2d93c448b3..45c187fbd2 100644 --- a/src/dpp/events/guild_integrations_update.cpp +++ b/src/dpp/events/guild_integrations_update.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void guild_integrations_update::handle(class discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_guild_integrations_update.empty()) { json& d = j["d"]; - dpp::guild_integrations_update_t giu(client, raw); + dpp::guild_integrations_update_t giu(client->owner, client->shard_id, raw); giu.updating_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); client->creator->queue_work(1, [c = client->creator, giu]() { c->on_guild_integrations_update.call(giu); diff --git a/src/dpp/events/guild_join_request_delete.cpp b/src/dpp/events/guild_join_request_delete.cpp index 7b13745282..6fd613210a 100644 --- a/src/dpp/events/guild_join_request_delete.cpp +++ b/src/dpp/events/guild_join_request_delete.cpp @@ -38,7 +38,7 @@ namespace dpp::events { void guild_join_request_delete::handle(class discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_guild_join_request_delete.empty()) { json& d = j["d"]; - dpp::guild_join_request_delete_t grd(client, raw); + dpp::guild_join_request_delete_t grd(client->owner, client->shard_id, raw); grd.user_id = snowflake_not_null(&d, "user_id"); grd.guild_id = snowflake_not_null(&d, "guild_id"); client->creator->queue_work(1, [c = client->creator, grd]() { diff --git a/src/dpp/events/guild_member_add.cpp b/src/dpp/events/guild_member_add.cpp index a245e7658a..bb2e0b9c2d 100644 --- a/src/dpp/events/guild_member_add.cpp +++ b/src/dpp/events/guild_member_add.cpp @@ -40,7 +40,7 @@ void guild_member_add::handle(discord_client* client, json &j, const std::string json d = j["d"]; dpp::snowflake guild_id = snowflake_not_null(&d, "guild_id"); dpp::guild* g = dpp::find_guild(guild_id); - dpp::guild_member_add_t gmr(client, raw); + dpp::guild_member_add_t gmr(client->owner, client->shard_id, raw); if (client->creator->cache_policy.user_policy == dpp::cp_none) { dpp::guild_member gm; gm.fill_from_json(&d, guild_id, snowflake_not_null(&(d["user"]), "id")); diff --git a/src/dpp/events/guild_member_remove.cpp b/src/dpp/events/guild_member_remove.cpp index acc5552c34..2d20d547d9 100644 --- a/src/dpp/events/guild_member_remove.cpp +++ b/src/dpp/events/guild_member_remove.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void guild_member_remove::handle(discord_client* client, json &j, const std::string &raw) { json d = j["d"]; - dpp::guild_member_remove_t gmr(client, raw); + dpp::guild_member_remove_t gmr(client->owner, client->shard_id, raw); gmr.removed.fill_from_json(&(d["user"])); gmr.guild_id = snowflake_not_null(&d, "guild_id"); gmr.removing_guild = dpp::find_guild(gmr.guild_id); diff --git a/src/dpp/events/guild_member_update.cpp b/src/dpp/events/guild_member_update.cpp index dd81d91e4c..92e007ccdc 100644 --- a/src/dpp/events/guild_member_update.cpp +++ b/src/dpp/events/guild_member_update.cpp @@ -40,7 +40,7 @@ void guild_member_update::handle(discord_client* client, json &j, const std::str if (client->creator->cache_policy.user_policy == dpp::cp_none) { dpp::user u; u.fill_from_json(&(d["user"])); - dpp::guild_member_update_t gmu(client, raw); + dpp::guild_member_update_t gmu(client->owner, client->shard_id, raw); gmu.updating_guild = g; if (!client->creator->on_guild_member_update.empty()) { guild_member m; @@ -62,7 +62,7 @@ void guild_member_update::handle(discord_client* client, json &j, const std::str } if (!client->creator->on_guild_member_update.empty()) { - dpp::guild_member_update_t gmu(client, raw); + dpp::guild_member_update_t gmu(client->owner, client->shard_id, raw); gmu.updating_guild = g; gmu.updated = m; client->creator->queue_work(0, [c = client->creator, gmu]() { diff --git a/src/dpp/events/guild_members_chunk.cpp b/src/dpp/events/guild_members_chunk.cpp index 26f5785d7c..6c589efc0b 100644 --- a/src/dpp/events/guild_members_chunk.cpp +++ b/src/dpp/events/guild_members_chunk.cpp @@ -64,7 +64,7 @@ void guild_members_chunk::handle(discord_client* client, json &j, const std::str } } if (!client->creator->on_guild_members_chunk.empty()) { - dpp::guild_members_chunk_t gmc(client, raw); + dpp::guild_members_chunk_t gmc(client->owner, client->shard_id, raw); gmc.adding = g; gmc.members = &um; client->creator->queue_work(1, [c = client->creator, gmc]() { diff --git a/src/dpp/events/guild_role_create.cpp b/src/dpp/events/guild_role_create.cpp index 2d5ad162ce..591f6bc1be 100644 --- a/src/dpp/events/guild_role_create.cpp +++ b/src/dpp/events/guild_role_create.cpp @@ -46,7 +46,7 @@ void guild_role_create::handle(discord_client* client, json &j, const std::strin dpp::role r; r.fill_from_json(guild_id, &role); if (!client->creator->on_guild_role_create.empty()) { - dpp::guild_role_create_t grc(client, raw); + dpp::guild_role_create_t grc(client->owner, client->shard_id, raw); grc.creating_guild = g; grc.created = &r; client->creator->queue_work(1, [c = client->creator, grc]() { @@ -65,7 +65,7 @@ void guild_role_create::handle(discord_client* client, json &j, const std::strin g->roles.push_back(r->id); } if (!client->creator->on_guild_role_create.empty()) { - dpp::guild_role_create_t grc(client, raw); + dpp::guild_role_create_t grc(client->owner, client->shard_id, raw); grc.creating_guild = g; grc.created = r; client->creator->queue_work(1, [c = client->creator, grc]() { diff --git a/src/dpp/events/guild_role_delete.cpp b/src/dpp/events/guild_role_delete.cpp index d23136bd4e..df01744096 100644 --- a/src/dpp/events/guild_role_delete.cpp +++ b/src/dpp/events/guild_role_delete.cpp @@ -44,7 +44,7 @@ void guild_role_delete::handle(discord_client* client, json &j, const std::strin dpp::guild* g = dpp::find_guild(guild_id); if (client->creator->cache_policy.role_policy == dpp::cp_none) { if (!client->creator->on_guild_role_delete.empty()) { - dpp::guild_role_delete_t grd(client, raw); + dpp::guild_role_delete_t grd(client->owner, client->shard_id, raw); grd.deleting_guild = g; grd.role_id = role_id; grd.deleted = nullptr; @@ -55,7 +55,7 @@ void guild_role_delete::handle(discord_client* client, json &j, const std::strin } else { dpp::role *r = dpp::find_role(role_id); if (!client->creator->on_guild_role_delete.empty()) { - dpp::guild_role_delete_t grd(client, raw); + dpp::guild_role_delete_t grd(client->owner, client->shard_id, raw); grd.deleting_guild = g; grd.deleted = r; grd.role_id = role_id; diff --git a/src/dpp/events/guild_role_update.cpp b/src/dpp/events/guild_role_update.cpp index 20f8573217..ba819511cf 100644 --- a/src/dpp/events/guild_role_update.cpp +++ b/src/dpp/events/guild_role_update.cpp @@ -44,7 +44,7 @@ void guild_role_update::handle(discord_client* client, json &j, const std::strin dpp::role r; r.fill_from_json(guild_id, &d); if (!client->creator->on_guild_role_update.empty()) { - dpp::guild_role_update_t gru(client, raw); + dpp::guild_role_update_t gru(client->owner, client->shard_id, raw); gru.updating_guild = g; gru.updated = &r; client->creator->queue_work(1, [c = client->creator, gru]() { @@ -57,7 +57,7 @@ void guild_role_update::handle(discord_client* client, json &j, const std::strin if (r) { r->fill_from_json(g->id, &role); if (!client->creator->on_guild_role_update.empty()) { - dpp::guild_role_update_t gru(client, raw); + dpp::guild_role_update_t gru(client->owner, client->shard_id, raw); gru.updating_guild = g; gru.updated = r; client->creator->queue_work(1, [c = client->creator, gru]() { diff --git a/src/dpp/events/guild_scheduled_event_create.cpp b/src/dpp/events/guild_scheduled_event_create.cpp index 7c72cd24ab..34b0e2cf07 100644 --- a/src/dpp/events/guild_scheduled_event_create.cpp +++ b/src/dpp/events/guild_scheduled_event_create.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void guild_scheduled_event_create::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_create.empty()) { - dpp::guild_scheduled_event_create_t ec(client, raw); + dpp::guild_scheduled_event_create_t ec(client->owner, client->shard_id, raw); ec.created.fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, ec]() { c->on_guild_scheduled_event_create.call(ec); diff --git a/src/dpp/events/guild_scheduled_event_delete.cpp b/src/dpp/events/guild_scheduled_event_delete.cpp index ae914d38f6..991e1f1efe 100644 --- a/src/dpp/events/guild_scheduled_event_delete.cpp +++ b/src/dpp/events/guild_scheduled_event_delete.cpp @@ -40,7 +40,7 @@ namespace dpp::events { void guild_scheduled_event_delete::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_delete.empty()) { - dpp::guild_scheduled_event_delete_t ed(client, raw); + dpp::guild_scheduled_event_delete_t ed(client->owner, client->shard_id, raw); ed.deleted.fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, ed]() { c->on_guild_scheduled_event_delete.call(ed); diff --git a/src/dpp/events/guild_scheduled_event_update.cpp b/src/dpp/events/guild_scheduled_event_update.cpp index 0f606d25a4..7f1085853b 100644 --- a/src/dpp/events/guild_scheduled_event_update.cpp +++ b/src/dpp/events/guild_scheduled_event_update.cpp @@ -40,7 +40,7 @@ namespace dpp::events { void guild_scheduled_event_update::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_update.empty()) { - dpp::guild_scheduled_event_update_t eu(client, raw); + dpp::guild_scheduled_event_update_t eu(client->owner, client->shard_id, raw); eu.updated.fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, eu]() { c->on_guild_scheduled_event_update.call(eu); diff --git a/src/dpp/events/guild_scheduled_event_user_add.cpp b/src/dpp/events/guild_scheduled_event_user_add.cpp index bae5c7419f..c984b4f261 100644 --- a/src/dpp/events/guild_scheduled_event_user_add.cpp +++ b/src/dpp/events/guild_scheduled_event_user_add.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void guild_scheduled_event_user_add::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_user_add.empty()) { - dpp::guild_scheduled_event_user_add_t eua(client, raw); + dpp::guild_scheduled_event_user_add_t eua(client->owner, client->shard_id, raw); eua.guild_id = snowflake_not_null(&d, "guild_id"); eua.user_id = snowflake_not_null(&d, "user_id"); eua.event_id = snowflake_not_null(&d, "guild_scheduled_event_id"); diff --git a/src/dpp/events/guild_scheduled_event_user_remove.cpp b/src/dpp/events/guild_scheduled_event_user_remove.cpp index 5ebfc34333..2cb1e48c54 100644 --- a/src/dpp/events/guild_scheduled_event_user_remove.cpp +++ b/src/dpp/events/guild_scheduled_event_user_remove.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void guild_scheduled_event_user_remove::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; if (!client->creator->on_guild_scheduled_event_user_remove.empty()) { - dpp::guild_scheduled_event_user_remove_t eur(client, raw); + dpp::guild_scheduled_event_user_remove_t eur(client->owner, client->shard_id, raw); eur.guild_id = snowflake_not_null(&d, "guild_id"); eur.user_id = snowflake_not_null(&d, "user_id"); eur.event_id = snowflake_not_null(&d, "guild_scheduled_event_id"); diff --git a/src/dpp/events/guild_stickers_update.cpp b/src/dpp/events/guild_stickers_update.cpp index dfc43d30d2..c0c8773755 100644 --- a/src/dpp/events/guild_stickers_update.cpp +++ b/src/dpp/events/guild_stickers_update.cpp @@ -42,7 +42,7 @@ void guild_stickers_update::handle(discord_client* client, json &j, const std::s if (!client->creator->on_guild_stickers_update.empty()) { dpp::snowflake guild_id = snowflake_not_null(&d, "guild_id"); dpp::guild* g = dpp::find_guild(guild_id); - dpp::guild_stickers_update_t gsu(client, raw); + dpp::guild_stickers_update_t gsu(client->owner, client->shard_id, raw); for (auto & sticker : d["stickers"]) { dpp::sticker s; s.fill_from_json(&sticker); diff --git a/src/dpp/events/guild_update.cpp b/src/dpp/events/guild_update.cpp index 4398279587..75dbabec1b 100644 --- a/src/dpp/events/guild_update.cpp +++ b/src/dpp/events/guild_update.cpp @@ -65,7 +65,7 @@ void guild_update::handle(discord_client* client, json &j, const std::string &ra } } if (!client->creator->on_guild_update.empty()) { - dpp::guild_update_t gu(client, raw); + dpp::guild_update_t gu(client->owner, client->shard_id, raw); gu.updated = g; client->creator->queue_work(1, [c = client->creator, gu]() { c->on_guild_update.call(gu); diff --git a/src/dpp/events/integration_create.cpp b/src/dpp/events/integration_create.cpp index 48fd9be7e8..81669856aa 100644 --- a/src/dpp/events/integration_create.cpp +++ b/src/dpp/events/integration_create.cpp @@ -40,7 +40,7 @@ namespace dpp::events { void integration_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_integration_create.empty()) { json& d = j["d"]; - dpp::integration_create_t ic(client, raw); + dpp::integration_create_t ic(client->owner, client->shard_id, raw); ic.created_integration = dpp::integration().fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, ic]() { c->on_integration_create.call(ic); diff --git a/src/dpp/events/integration_delete.cpp b/src/dpp/events/integration_delete.cpp index d93063e5dc..a3fcd06857 100644 --- a/src/dpp/events/integration_delete.cpp +++ b/src/dpp/events/integration_delete.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void integration_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_integration_delete.empty()) { json& d = j["d"]; - dpp::integration_delete_t id(client, raw); + dpp::integration_delete_t id(client->owner, client->shard_id, raw); id.deleted_integration = dpp::integration().fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, id]() { c->on_integration_delete.call(id); diff --git a/src/dpp/events/integration_update.cpp b/src/dpp/events/integration_update.cpp index 814ac81634..7201a2554e 100644 --- a/src/dpp/events/integration_update.cpp +++ b/src/dpp/events/integration_update.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void integration_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_integration_update.empty()) { json& d = j["d"]; - dpp::integration_update_t iu(client, raw); + dpp::integration_update_t iu(client->owner, client->shard_id, raw); iu.updated_integration = dpp::integration().fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, iu]() { c->on_integration_update.call(iu); diff --git a/src/dpp/events/interaction_create.cpp b/src/dpp/events/interaction_create.cpp index 51d00fe958..da39a4e018 100644 --- a/src/dpp/events/interaction_create.cpp +++ b/src/dpp/events/interaction_create.cpp @@ -99,7 +99,7 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri if (cmd_data.type == ctxm_message && !client->creator->on_message_context_menu.empty()) { if (i.resolved.messages.size()) { /* Message right-click context menu */ - message_context_menu_t mcm(client, raw); + message_context_menu_t mcm(client->owner, client->shard_id, raw); mcm.command = i; mcm.set_message(i.resolved.messages.begin()->second); client->creator->queue_work(1, [c = client->creator, mcm]() { @@ -109,7 +109,7 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri } else if (cmd_data.type == ctxm_user && !client->creator->on_user_context_menu.empty()) { if (i.resolved.users.size()) { /* User right-click context menu */ - user_context_menu_t ucm(client, raw); + user_context_menu_t ucm(client->owner, client->shard_id, raw); ucm.command = i; ucm.set_user(i.resolved.users.begin()->second); client->creator->queue_work(1, [c = client->creator, ucm]() { @@ -117,7 +117,7 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri }); } } else if (cmd_data.type == ctxm_chat_input && !client->creator->on_slashcommand.empty()) { - dpp::slashcommand_t sc(client, raw); + dpp::slashcommand_t sc(client->owner, client->shard_id, raw); sc.command = i; client->creator->queue_work(1, [c = client->creator, sc]() { c->on_slashcommand.call(sc); @@ -128,7 +128,7 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri * events still find their way here. At some point in the future, receiving * ctxm_user and ctxm_message inputs to this event will be depreciated. */ - dpp::interaction_create_t ic(client, raw); + dpp::interaction_create_t ic(client->owner, client->shard_id, raw); ic.command = i; client->creator->queue_work(1, [c = client->creator, ic]() { c->on_interaction_create.call(ic); @@ -136,7 +136,7 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri } } else if (i.type == it_modal_submit) { if (!client->creator->on_form_submit.empty()) { - dpp::form_submit_t fs(client, raw); + dpp::form_submit_t fs(client->owner, client->shard_id, raw); fs.custom_id = string_not_null(&(d["data"]), "custom_id"); fs.command = i; for (auto & c : d["data"]["components"]) { @@ -149,7 +149,7 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri } else if (i.type == it_autocomplete) { // "data":{"id":"903319628816728104","name":"blep","options":[{"focused":true,"name":"animal","type":3,"value":"a"}],"type":1} if (!client->creator->on_autocomplete.empty()) { - dpp::autocomplete_t ac(client, raw); + dpp::autocomplete_t ac(client->owner, client->shard_id, raw); ac.id = snowflake_not_null(&(d["data"]), "id"); ac.name = string_not_null(&(d["data"]), "name"); fill_options(d["data"]["options"], ac.options); @@ -162,7 +162,7 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri dpp::component_interaction bi = std::get(i.data); if (bi.component_type == cot_button) { if (!client->creator->on_button_click.empty()) { - dpp::button_click_t ic(client, raw); + dpp::button_click_t ic(client->owner, client->shard_id, raw); ic.command = i; ic.custom_id = bi.custom_id; ic.component_type = bi.component_type; @@ -174,7 +174,7 @@ void interaction_create::handle(discord_client* client, json &j, const std::stri bi.component_type == cot_role_selectmenu || bi.component_type == cot_mentionable_selectmenu || bi.component_type == cot_channel_selectmenu) { if (!client->creator->on_select_click.empty()) { - dpp::select_click_t ic(client, raw); + dpp::select_click_t ic(client->owner, client->shard_id, raw); ic.command = i; ic.custom_id = bi.custom_id; ic.component_type = bi.component_type; diff --git a/src/dpp/events/invite_create.cpp b/src/dpp/events/invite_create.cpp index d06b38eaa2..b8af9a5eab 100644 --- a/src/dpp/events/invite_create.cpp +++ b/src/dpp/events/invite_create.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void invite_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_invite_create.empty()) { json& d = j["d"]; - dpp::invite_create_t ci(client, raw); + dpp::invite_create_t ci(client->owner, client->shard_id, raw); ci.created_invite = dpp::invite().fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, ci]() { c->on_invite_create.call(ci); diff --git a/src/dpp/events/invite_delete.cpp b/src/dpp/events/invite_delete.cpp index 5a77618e14..746bb4c4c7 100644 --- a/src/dpp/events/invite_delete.cpp +++ b/src/dpp/events/invite_delete.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void invite_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_invite_delete.empty()) { json& d = j["d"]; - dpp::invite_delete_t cd(client, raw); + dpp::invite_delete_t cd(client->owner, client->shard_id, raw); cd.deleted_invite = dpp::invite().fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, cd]() { c->on_invite_delete.call(cd); diff --git a/src/dpp/events/logger.cpp b/src/dpp/events/logger.cpp index 33c6222433..d505acae53 100644 --- a/src/dpp/events/logger.cpp +++ b/src/dpp/events/logger.cpp @@ -37,10 +37,10 @@ namespace dpp::events { */ void logger::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_log.empty()) { - dpp::log_t logmsg(client, raw); + dpp::log_t logmsg(client->owner, client->shard_id, raw); logmsg.severity = (dpp::loglevel)from_string(raw.substr(0, raw.find(';'))); logmsg.message = raw.substr(raw.find(';') + 1, raw.length()); - c->on_log.call(logmsg); + client->creator->on_log.call(logmsg); } } diff --git a/src/dpp/events/message_create.cpp b/src/dpp/events/message_create.cpp index ddd97292e0..39ad89adf1 100644 --- a/src/dpp/events/message_create.cpp +++ b/src/dpp/events/message_create.cpp @@ -38,12 +38,11 @@ namespace dpp::events { void message_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_create.empty()) { - json js = j; - client->creator->queue_work(1, [c = client->creator, js, raw]() { + client->creator->queue_work(1, [shard_id = client->shard_id, c = client->creator, js = j, raw]() { json d = js["d"]; - dpp::message_create_t msg(client, raw); - msg.msg = message(client->owner).fill_from_json(&d, client->creator->cache_policy); - msg.msg.owner = client->creator; + dpp::message_create_t msg(c, shard_id, raw); + msg.msg = message(c).fill_from_json(&d, c->cache_policy); + msg.msg.owner = c; c->on_message_create.call(msg); }); } diff --git a/src/dpp/events/message_delete.cpp b/src/dpp/events/message_delete.cpp index e3bde8f88d..bffd907f20 100644 --- a/src/dpp/events/message_delete.cpp +++ b/src/dpp/events/message_delete.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void message_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_delete.empty()) { json d = j["d"]; - dpp::message_delete_t msg(client, raw); + dpp::message_delete_t msg(client->owner, client->shard_id, raw); msg.id = snowflake_not_null(&d, "id"); msg.guild_id = snowflake_not_null(&d, "guild_id"); msg.channel_id = snowflake_not_null(&d, "channel_id"); diff --git a/src/dpp/events/message_delete_bulk.cpp b/src/dpp/events/message_delete_bulk.cpp index ae53d1501b..4ac923c900 100644 --- a/src/dpp/events/message_delete_bulk.cpp +++ b/src/dpp/events/message_delete_bulk.cpp @@ -37,7 +37,7 @@ namespace dpp::events { void message_delete_bulk::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_delete_bulk.empty()) { json& d = j["d"]; - dpp::message_delete_bulk_t msg(client, raw); + dpp::message_delete_bulk_t msg(client->owner, client->shard_id, raw); msg.deleting_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); msg.deleting_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); msg.deleting_user = dpp::find_user(snowflake_not_null(&d, "user_id")); diff --git a/src/dpp/events/message_poll_vote_add.cpp b/src/dpp/events/message_poll_vote_add.cpp index 0e9e730b14..3b50d287d5 100644 --- a/src/dpp/events/message_poll_vote_add.cpp +++ b/src/dpp/events/message_poll_vote_add.cpp @@ -40,7 +40,7 @@ void message_poll_vote_add::handle(discord_client* client, json &j, const std::s if (!client->creator->on_message_poll_vote_add.empty()) { json d = j["d"]; - dpp::message_poll_vote_add_t vote(client, raw); + dpp::message_poll_vote_add_t vote(client->owner, client->shard_id, raw); vote.user_id = snowflake_not_null(&j, "user_id"); vote.message_id = snowflake_not_null(&j, "message_id"); vote.channel_id = snowflake_not_null(&j, "channel_id"); diff --git a/src/dpp/events/message_poll_vote_remove.cpp b/src/dpp/events/message_poll_vote_remove.cpp index 9bf0d0d6eb..feccb4cc00 100644 --- a/src/dpp/events/message_poll_vote_remove.cpp +++ b/src/dpp/events/message_poll_vote_remove.cpp @@ -40,7 +40,7 @@ void message_poll_vote_remove::handle(discord_client* client, json &j, const std if (!client->creator->on_message_poll_vote_add.empty()) { json d = j["d"]; - dpp::message_poll_vote_remove_t vote(client, raw); + dpp::message_poll_vote_remove_t vote(client->owner, client->shard_id, raw); vote.user_id = snowflake_not_null(&j, "user_id"); vote.message_id = snowflake_not_null(&j, "message_id"); vote.channel_id = snowflake_not_null(&j, "channel_id"); diff --git a/src/dpp/events/message_reaction_add.cpp b/src/dpp/events/message_reaction_add.cpp index 52acbcffb8..392834bf39 100644 --- a/src/dpp/events/message_reaction_add.cpp +++ b/src/dpp/events/message_reaction_add.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void message_reaction_add::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_reaction_add.empty()) { json &d = j["d"]; - dpp::message_reaction_add_t mra(client, raw); + dpp::message_reaction_add_t mra(client->owner, client->shard_id, raw); dpp::snowflake guild_id = snowflake_not_null(&d, "guild_id"); mra.reacting_guild = dpp::find_guild(guild_id); mra.reacting_user = dpp::user().fill_from_json(&(d["member"]["user"])); diff --git a/src/dpp/events/message_reaction_remove.cpp b/src/dpp/events/message_reaction_remove.cpp index 380f776828..25ffa9c852 100644 --- a/src/dpp/events/message_reaction_remove.cpp +++ b/src/dpp/events/message_reaction_remove.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void message_reaction_remove::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_reaction_remove.empty()) { json &d = j["d"]; - dpp::message_reaction_remove_t mrr(client, raw); + dpp::message_reaction_remove_t mrr(client->owner, client->shard_id, raw); dpp::snowflake guild_id = snowflake_not_null(&d, "guild_id"); mrr.reacting_guild = dpp::find_guild(guild_id); mrr.reacting_user_id = snowflake_not_null(&d, "user_id"); diff --git a/src/dpp/events/message_reaction_remove_all.cpp b/src/dpp/events/message_reaction_remove_all.cpp index 5c71f85485..dca6cce8d5 100644 --- a/src/dpp/events/message_reaction_remove_all.cpp +++ b/src/dpp/events/message_reaction_remove_all.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void message_reaction_remove_all::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_reaction_remove_all.empty()) { json &d = j["d"]; - dpp::message_reaction_remove_all_t mrra(client, raw); + dpp::message_reaction_remove_all_t mrra(client->owner, client->shard_id, raw); mrra.reacting_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); mrra.channel_id = snowflake_not_null(&d, "channel_id"); mrra.reacting_channel = dpp::find_channel(mrra.channel_id); diff --git a/src/dpp/events/message_reaction_remove_emoji.cpp b/src/dpp/events/message_reaction_remove_emoji.cpp index 1c38b5fadd..ceebccfaac 100644 --- a/src/dpp/events/message_reaction_remove_emoji.cpp +++ b/src/dpp/events/message_reaction_remove_emoji.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void message_reaction_remove_emoji::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_reaction_remove_emoji.empty()) { json &d = j["d"]; - dpp::message_reaction_remove_emoji_t mrre(client, raw); + dpp::message_reaction_remove_emoji_t mrre(client->owner, client->shard_id, raw); mrre.reacting_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); mrre.channel_id = snowflake_not_null(&d, "channel_id"); mrre.reacting_channel = dpp::find_channel(mrre.channel_id); diff --git a/src/dpp/events/message_update.cpp b/src/dpp/events/message_update.cpp index 6f6f5bf028..545ebd78df 100644 --- a/src/dpp/events/message_update.cpp +++ b/src/dpp/events/message_update.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void message_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_message_update.empty()) { json d = j["d"]; - dpp::message_update_t msg(client, raw); + dpp::message_update_t msg(client->owner, client->shard_id, raw); dpp::message m(client->creator); m.fill_from_json(&d); msg.msg = m; diff --git a/src/dpp/events/presence_update.cpp b/src/dpp/events/presence_update.cpp index cacbe2aa1a..aac6f37e63 100644 --- a/src/dpp/events/presence_update.cpp +++ b/src/dpp/events/presence_update.cpp @@ -38,7 +38,7 @@ namespace dpp::events { void presence_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_presence_update.empty()) { json& d = j["d"]; - dpp::presence_update_t pu(client, raw); + dpp::presence_update_t pu(client->owner, client->shard_id, raw); pu.rich_presence = dpp::presence().fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, pu]() { c->on_presence_update.call(pu); diff --git a/src/dpp/events/ready.cpp b/src/dpp/events/ready.cpp index 251f455a57..765f9a9daa 100644 --- a/src/dpp/events/ready.cpp +++ b/src/dpp/events/ready.cpp @@ -68,7 +68,7 @@ void ready::handle(discord_client* client, json &j, const std::string &raw) { } if (!client->creator->on_ready.empty()) { - dpp::ready_t r(client, raw); + dpp::ready_t r(client->owner, client->shard_id, raw); r.session_id = client->sessionid; r.shard_id = client->shard_id; for (const auto& guild : j["d"]["guilds"]) { diff --git a/src/dpp/events/resumed.cpp b/src/dpp/events/resumed.cpp index ad66831d92..706c45e2dd 100644 --- a/src/dpp/events/resumed.cpp +++ b/src/dpp/events/resumed.cpp @@ -41,7 +41,7 @@ void resumed::handle(discord_client* client, json &j, const std::string &raw) { client->ready = true; if (!client->creator->on_resumed.empty()) { - dpp::resumed_t r(client, raw); + dpp::resumed_t r(client->owner, client->shard_id, raw); r.session_id = client->sessionid; r.shard_id = client->shard_id; client->creator->queue_work(1, [c = client->creator, r]() { diff --git a/src/dpp/events/stage_instance_create.cpp b/src/dpp/events/stage_instance_create.cpp index f6049b36c3..db42c0d1a7 100644 --- a/src/dpp/events/stage_instance_create.cpp +++ b/src/dpp/events/stage_instance_create.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void stage_instance_create::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_stage_instance_create.empty()) { json& d = j["d"]; - dpp::stage_instance_create_t sic(client, raw); + dpp::stage_instance_create_t sic(client->owner, client->shard_id, raw); sic.created.fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, sic]() { c->on_stage_instance_create.call(sic); diff --git a/src/dpp/events/stage_instance_delete.cpp b/src/dpp/events/stage_instance_delete.cpp index 15cd1a0522..d639b866e5 100644 --- a/src/dpp/events/stage_instance_delete.cpp +++ b/src/dpp/events/stage_instance_delete.cpp @@ -37,7 +37,7 @@ namespace dpp::events { void stage_instance_delete::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_stage_instance_delete.empty()) { json& d = j["d"]; - dpp::stage_instance_delete_t sid(client, raw); + dpp::stage_instance_delete_t sid(client->owner, client->shard_id, raw); sid.deleted.fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, sid]() { c->on_stage_instance_delete.call(sid); diff --git a/src/dpp/events/stage_instance_update.cpp b/src/dpp/events/stage_instance_update.cpp index 6f1febb7b9..d445356008 100644 --- a/src/dpp/events/stage_instance_update.cpp +++ b/src/dpp/events/stage_instance_update.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void stage_instance_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_stage_instance_update.empty()) { json& d = j["d"]; - dpp::stage_instance_update_t siu(client, raw); + dpp::stage_instance_update_t siu(client->owner, client->shard_id, raw); siu.updated.fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, siu]() { c->on_stage_instance_update.call(siu); diff --git a/src/dpp/events/thread_create.cpp b/src/dpp/events/thread_create.cpp index 01d3ad3022..7049ad6e52 100644 --- a/src/dpp/events/thread_create.cpp +++ b/src/dpp/events/thread_create.cpp @@ -39,7 +39,7 @@ void thread_create::handle(discord_client* client, json& j, const std::string& r g->threads.push_back(t.id); } if (!client->creator->on_thread_create.empty()) { - dpp::thread_create_t tc(client, raw); + dpp::thread_create_t tc(client->owner, client->shard_id, raw); tc.created = t; tc.creating_guild = g; client->creator->queue_work(1, [c = client->creator, tc]() { diff --git a/src/dpp/events/thread_delete.cpp b/src/dpp/events/thread_delete.cpp index 4bcab8084d..ad4512e2e3 100644 --- a/src/dpp/events/thread_delete.cpp +++ b/src/dpp/events/thread_delete.cpp @@ -39,7 +39,7 @@ void thread_delete::handle(discord_client* client, json& j, const std::string& r g->threads.erase(std::remove(g->threads.begin(), g->threads.end(), t.id), g->threads.end()); } if (!client->creator->on_thread_delete.empty()) { - dpp::thread_delete_t td(client, raw); + dpp::thread_delete_t td(client->owner, client->shard_id, raw); td.deleted = t; td.deleting_guild = g; client->creator->queue_work(1, [c = client->creator, td]() { diff --git a/src/dpp/events/thread_list_sync.cpp b/src/dpp/events/thread_list_sync.cpp index 718f834783..d0019deb2c 100644 --- a/src/dpp/events/thread_list_sync.cpp +++ b/src/dpp/events/thread_list_sync.cpp @@ -41,7 +41,7 @@ void thread_list_sync::handle(discord_client* client, json& j, const std::string } } if (!client->creator->on_thread_list_sync.empty()) { - dpp::thread_list_sync_t tls(client, raw); + dpp::thread_list_sync_t tls(client->owner, client->shard_id, raw); if (d.find("threads") != d.end()) { for (auto& t : d["threads"]) { tls.threads.push_back(thread().fill_from_json(&t)); diff --git a/src/dpp/events/thread_member_update.cpp b/src/dpp/events/thread_member_update.cpp index 39c3bafbff..61c421afb4 100644 --- a/src/dpp/events/thread_member_update.cpp +++ b/src/dpp/events/thread_member_update.cpp @@ -32,7 +32,7 @@ namespace dpp::events { void thread_member_update::handle(discord_client* client, json& j, const std::string& raw) { if (!client->creator->on_thread_member_update.empty()) { json& d = j["d"]; - dpp::thread_member_update_t tm(client, raw); + dpp::thread_member_update_t tm(client->owner, client->shard_id, raw); tm.updated = thread_member().fill_from_json(&d); client->creator->queue_work(1, [c = client->creator, tm]() { c->on_thread_member_update.call(tm); diff --git a/src/dpp/events/thread_members_update.cpp b/src/dpp/events/thread_members_update.cpp index 8968772011..d4e14b7676 100644 --- a/src/dpp/events/thread_members_update.cpp +++ b/src/dpp/events/thread_members_update.cpp @@ -34,7 +34,7 @@ void thread_members_update::handle(discord_client* client, json& j, const std::s dpp::guild* g = dpp::find_guild(snowflake_not_null(&d, "guild_id")); if (!client->creator->on_thread_members_update.empty()) { - dpp::thread_members_update_t tms(client, raw); + dpp::thread_members_update_t tms(client->owner, client->shard_id, raw); tms.updating_guild = g; set_snowflake_not_null(&d, "id", tms.thread_id); set_int8_not_null(&d, "member_count", tms.member_count); diff --git a/src/dpp/events/thread_update.cpp b/src/dpp/events/thread_update.cpp index 797b184a53..9600ef36f5 100644 --- a/src/dpp/events/thread_update.cpp +++ b/src/dpp/events/thread_update.cpp @@ -35,7 +35,7 @@ void thread_update::handle(discord_client* client, json& j, const std::string& r t.fill_from_json(&d); dpp::guild* g = dpp::find_guild(t.guild_id); if (!client->creator->on_thread_update.empty()) { - dpp::thread_update_t tu(client, raw); + dpp::thread_update_t tu(client->owner, client->shard_id, raw); tu.updated = t; tu.updating_guild = g; client->creator->queue_work(1, [c = client->creator, tu]() { diff --git a/src/dpp/events/typing_start.cpp b/src/dpp/events/typing_start.cpp index aa000607c7..bab90053ce 100644 --- a/src/dpp/events/typing_start.cpp +++ b/src/dpp/events/typing_start.cpp @@ -37,7 +37,7 @@ namespace dpp::events { void typing_start::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_typing_start.empty()) { json& d = j["d"]; - dpp::typing_start_t ts(client, raw); + dpp::typing_start_t ts(client->owner, client->shard_id, raw); ts.typing_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); ts.typing_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); ts.user_id = snowflake_not_null(&d, "user_id"); diff --git a/src/dpp/events/user_update.cpp b/src/dpp/events/user_update.cpp index 2766d34b30..05aee1b63d 100644 --- a/src/dpp/events/user_update.cpp +++ b/src/dpp/events/user_update.cpp @@ -47,7 +47,7 @@ void user_update::handle(discord_client* client, json &j, const std::string &raw u->fill_from_json(&d); } if (!client->creator->on_user_update.empty()) { - dpp::user_update_t uu(client, raw); + dpp::user_update_t uu(client->owner, client->shard_id, raw); uu.updated = *u; client->creator->queue_work(1, [c = client->creator, uu]() { c->on_user_update.call(uu); @@ -57,7 +57,7 @@ void user_update::handle(discord_client* client, json &j, const std::string &raw if (!client->creator->on_user_update.empty()) { dpp::user u; u.fill_from_json(&d); - dpp::user_update_t uu(client, raw); + dpp::user_update_t uu(client->owner, client->shard_id, raw); uu.updated = u; client->creator->queue_work(1, [c = client->creator, uu]() { c->on_user_update.call(uu); diff --git a/src/dpp/events/voice_server_update.cpp b/src/dpp/events/voice_server_update.cpp index 864584be97..489a013fdb 100644 --- a/src/dpp/events/voice_server_update.cpp +++ b/src/dpp/events/voice_server_update.cpp @@ -39,7 +39,7 @@ namespace dpp::events { void voice_server_update::handle(discord_client* client, json &j, const std::string &raw) { json &d = j["d"]; - dpp::voice_server_update_t vsu(client, raw); + dpp::voice_server_update_t vsu(client->owner, client->shard_id, raw); vsu.guild_id = snowflake_not_null(&d, "guild_id"); vsu.token = string_not_null(&d, "token"); vsu.endpoint = string_not_null(&d, "endpoint"); diff --git a/src/dpp/events/voice_state_update.cpp b/src/dpp/events/voice_state_update.cpp index 9b2b9f0543..30c81eed59 100644 --- a/src/dpp/events/voice_state_update.cpp +++ b/src/dpp/events/voice_state_update.cpp @@ -40,7 +40,7 @@ namespace dpp::events { void voice_state_update::handle(discord_client* client, json &j, const std::string &raw) { json& d = j["d"]; - dpp::voice_state_update_t vsu(client, raw); + dpp::voice_state_update_t vsu(client->owner, client->shard_id, raw); vsu.state = dpp::voicestate().fill_from_json(&d); vsu.state.shard = client; diff --git a/src/dpp/events/webhooks_update.cpp b/src/dpp/events/webhooks_update.cpp index 7eb4f0eedf..1750cdb2fd 100644 --- a/src/dpp/events/webhooks_update.cpp +++ b/src/dpp/events/webhooks_update.cpp @@ -38,7 +38,7 @@ namespace dpp::events { void webhooks_update::handle(discord_client* client, json &j, const std::string &raw) { if (!client->creator->on_webhooks_update.empty()) { json& d = j["d"]; - dpp::webhooks_update_t wu(client, raw); + dpp::webhooks_update_t wu(client->owner, client->shard_id, raw); wu.webhook_guild = dpp::find_guild(snowflake_not_null(&d, "guild_id")); wu.webhook_channel = dpp::find_channel(snowflake_not_null(&d, "channel_id")); client->creator->queue_work(1, [c = client->creator, wu]() { diff --git a/src/dpp/voice/enabled/courier_loop.cpp b/src/dpp/voice/enabled/courier_loop.cpp index 72ccccdbfc..a8d8f61c0a 100644 --- a/src/dpp/voice/enabled/courier_loop.cpp +++ b/src/dpp/voice/enabled/courier_loop.cpp @@ -146,7 +146,7 @@ void discord_voice_client::voice_courier_loop(discord_voice_client& client, cour * Since this sample comes from a lost packet, * we can only pretend there is an event, without any raw payload byte. */ - voice_receive_t vr(nullptr, "", &client, d.user_id, + voice_receive_t vr(client.creator, 0, "", &client, d.user_id, reinterpret_cast(flush_data_pcm), lost_packet_samples * opus_channel_count * sizeof(opus_int16)); @@ -282,7 +282,7 @@ void discord_voice_client::voice_courier_loop(discord_voice_client& client, cour pcm_downsample_ptr += client.mixer->byte_blocks_per_register; } - voice_receive_t vr(nullptr, "", &client, 0, reinterpret_cast(pcm_downsample), + voice_receive_t vr(client.owner, 0, "", &client, 0, reinterpret_cast(pcm_downsample), max_samples * opus_channel_count * sizeof(opus_int16)); client.creator->on_voice_receive_combined.call(vr); diff --git a/src/dpp/voice/enabled/handle_frame.cpp b/src/dpp/voice/enabled/handle_frame.cpp index 6a8bfe5510..69f879bd92 100644 --- a/src/dpp/voice/enabled/handle_frame.cpp +++ b/src/dpp/voice/enabled/handle_frame.cpp @@ -204,7 +204,7 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod } break; case voice_client_platform: { - voice_client_platform_t vcp(nullptr, data); + voice_client_platform_t vcp(owner, 0, data); vcp.voice_client = this; vcp.user_id = snowflake_not_null(&j["d"], "user_id"); vcp.platform = static_cast(int8_not_null(&j["d"], "platform")); @@ -302,7 +302,7 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod dave_mls_pending_remove_list.insert(u_id); if (!creator->on_voice_client_disconnect.empty()) { - voice_client_disconnect_t vcd(nullptr, data); + voice_client_disconnect_t vcd(owner, 0, data); vcd.voice_client = this; vcd.user_id = u_id; creator->queue_work(0, [this, vcd]() { @@ -322,7 +322,7 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod ssrc_map[u_ssrc] = u_id; if (!creator->on_voice_client_speaking.empty()) { - voice_client_speaking_t vcs(nullptr, data); + voice_client_speaking_t vcs(owner, 0, data); vcs.voice_client = this; vcs.user_id = u_id; vcs.ssrc = u_ssrc; @@ -422,7 +422,7 @@ bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcod send_silence(20); /* Fire on_voice_ready */ if (!creator->on_voice_ready.empty()) { - voice_ready_t rdy(nullptr, data); + voice_ready_t rdy(owner, 0, data); rdy.voice_client = this; rdy.voice_channel_id = this->channel_id; creator->queue_work(0, [this, rdy]() { @@ -533,7 +533,7 @@ void discord_voice_client::ready_for_transition(const std::string &data) { mls_state->done_ready = true; if (!creator->on_voice_ready.empty()) { - voice_ready_t rdy(nullptr, data); + voice_ready_t rdy(owner, 0, data); rdy.voice_client = this; rdy.voice_channel_id = this->channel_id; creator->queue_work(0, [this, rdy]() { diff --git a/src/dpp/voice/enabled/read_ready.cpp b/src/dpp/voice/enabled/read_ready.cpp index 03c79b0486..e499de3f5d 100644 --- a/src/dpp/voice/enabled/read_ready.cpp +++ b/src/dpp/voice/enabled/read_ready.cpp @@ -68,7 +68,7 @@ void discord_voice_client::read_ready() voice_payload vp{0, // seq, populate later 0, // timestamp, populate later - std::make_unique(nullptr, std::string(reinterpret_cast(buffer), packet_size))}; + std::make_unique(owner, 0, std::string(reinterpret_cast(buffer), packet_size))}; vp.vr->voice_client = this; diff --git a/src/dpp/voice/enabled/write_ready.cpp b/src/dpp/voice/enabled/write_ready.cpp index 6877a97c26..e2b78ef8c8 100644 --- a/src/dpp/voice/enabled/write_ready.cpp +++ b/src/dpp/voice/enabled/write_ready.cpp @@ -103,7 +103,7 @@ void discord_voice_client::write_ready() { last_timestamp = std::chrono::high_resolution_clock::now(); if (!creator->on_voice_buffer_send.empty()) { - voice_buffer_send_t snd(nullptr, ""); + voice_buffer_send_t snd(owner, 0, ""); snd.buffer_size = bufsize; snd.packets_left = outbuf.size(); snd.voice_client = this; @@ -114,7 +114,7 @@ void discord_voice_client::write_ready() { } if (track_marker_found) { if (!creator->on_voice_track_marker.empty()) { - voice_track_marker_t vtm(nullptr, ""); + voice_track_marker_t vtm(owner, 0, ""); vtm.voice_client = this; { std::lock_guard lock(this->stream_mutex); diff --git a/src/unittest/coro.cpp b/src/unittest/coro.cpp index eead3c85b6..8ec42d89d2 100644 --- a/src/unittest/coro.cpp +++ b/src/unittest/coro.cpp @@ -523,7 +523,7 @@ void coro_offline_tests() void event_handler_test(dpp::cluster *bot) { bot->on_message_create([](dpp::message_create_t event) -> dpp::task { if (event.msg.content == "coro test") { - dpp::cluster *bot = event.from->creator; + dpp::cluster *bot = event.owner; set_status(CORO_EVENT_HANDLER, ts_success); start_test(CORO_API_CALLS); diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 9b2e0778ab..4d5d308ee0 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -411,7 +411,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b // create a fake interaction dpp::cluster cluster(""); dpp::discord_client client(&cluster, 1, 1, ""); - dpp::interaction_create_t interaction(&client, ""); + dpp::interaction_create_t interaction(nullptr, 0, ""); /* Check the method with subcommands */ set_test(GET_PARAMETER_WITH_SUBCOMMANDS, false); From cbdc1f9bdbaf171942b4c8add7df36752dc1fe2d Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 14 Dec 2024 01:23:45 +0000 Subject: [PATCH 110/112] fix(docs): update documentation examples to use event.from() and event.owner --- docpages/example_code/coro_awaiting_events.cpp | 2 +- docpages/example_code/coro_expiring_buttons.cpp | 4 ++-- docpages/example_code/coro_intro.cpp | 2 +- docpages/example_code/coro_simple_commands1.cpp | 2 +- docpages/example_code/coro_simple_commands2.cpp | 4 ++-- docpages/example_code/join_voice.cpp | 4 ++-- docpages/example_code/mp3.cpp | 2 +- docpages/example_code/oggopus.cpp | 2 +- docpages/example_code/oggopus2.cpp | 2 +- docpages/example_code/record_user.cpp | 2 +- docpages/example_code/soundboard.cpp | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docpages/example_code/coro_awaiting_events.cpp b/docpages/example_code/coro_awaiting_events.cpp index f5be22b681..469d1bcf94 100644 --- a/docpages/example_code/coro_awaiting_events.cpp +++ b/docpages/example_code/coro_awaiting_events.cpp @@ -20,7 +20,7 @@ int main() { ); co_await event.co_reply(m); - dpp::button_click_t click_event = co_await event.from->creator->on_button_click.when( + dpp::button_click_t click_event = co_await event.owner->on_button_click.when( // Note!! Due to a bug in g++11 and g++12, id must be captured as a reference here or the compiler will destroy it twice. This is fixed in g++13 [&id] (dpp::button_click_t const &b) { return b.custom_id == id; diff --git a/docpages/example_code/coro_expiring_buttons.cpp b/docpages/example_code/coro_expiring_buttons.cpp index ed58bdadad..4b5f585d59 100644 --- a/docpages/example_code/coro_expiring_buttons.cpp +++ b/docpages/example_code/coro_expiring_buttons.cpp @@ -21,10 +21,10 @@ int main() { co_await event.co_reply(m); auto result = co_await dpp::when_any{ // Whichever completes first... - event.from->creator->on_button_click.when([&id](const dpp::button_click_t &b) { // Button clicked + event.owner->on_button_click.when([&id](const dpp::button_click_t &b) { // Button clicked return b.custom_id == id; }), - event.from->creator->co_sleep(5) // Or sleep 5 seconds + event.owner->co_sleep(5) // Or sleep 5 seconds }; // Note!! Due to a bug in g++11 and g++12, id must be captured as a reference above or the compiler will destroy it twice. This is fixed in g++13 if (result.index() == 0) { // Awaitable #0 completed first, that is the button click event diff --git a/docpages/example_code/coro_intro.cpp b/docpages/example_code/coro_intro.cpp index c48128547e..0018943b67 100644 --- a/docpages/example_code/coro_intro.cpp +++ b/docpages/example_code/coro_intro.cpp @@ -9,7 +9,7 @@ int main() { bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task { if (event.command.get_command_name() == "file") { /* Request the image from the URL specified and co_await the response */ - dpp::http_request_completion_t result = co_await event.from->creator->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get); + dpp::http_request_completion_t result = co_await event.owner->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get); /* Create a message and attach the image on success */ dpp::message msg(event.command.channel_id, "This is my new attachment:"); diff --git a/docpages/example_code/coro_simple_commands1.cpp b/docpages/example_code/coro_simple_commands1.cpp index 09891ea125..0891684131 100644 --- a/docpages/example_code/coro_simple_commands1.cpp +++ b/docpages/example_code/coro_simple_commands1.cpp @@ -7,7 +7,7 @@ int main() { bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task { if (event.command.get_command_name() == "addemoji") { - dpp::cluster *cluster = event.from->creator; + dpp::cluster *cluster = event.owner; // Retrieve parameter values dpp::snowflake file_id = std::get(event.get_parameter("file")); std::string emoji_name = std::get(event.get_parameter("name")); diff --git a/docpages/example_code/coro_simple_commands2.cpp b/docpages/example_code/coro_simple_commands2.cpp index ac4683eb45..40a46e4e5f 100644 --- a/docpages/example_code/coro_simple_commands2.cpp +++ b/docpages/example_code/coro_simple_commands2.cpp @@ -32,7 +32,7 @@ int main() { } } // Finally if everything else failed, request API - dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_guild_get_member(event.command.guild_id, user_id); + dpp::confirmation_callback_t confirmation = co_await event.owner->co_guild_get_member(event.command.guild_id, user_id); if (confirmation.is_error()) { co_return std::nullopt; // Member not found, return empty } else { @@ -53,7 +53,7 @@ int main() { std::string avatar_url = member->get_avatar_url(512); if (avatar_url.empty()) { // Member does not have a custom avatar for this server, get their user avatar - dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_user_get_cached(member->user_id); + dpp::confirmation_callback_t confirmation = co_await event.owner->co_user_get_cached(member->user_id); if (confirmation.is_error()) { // Wait for the thinking response to arrive to make sure we can edit co_await thinking; diff --git a/docpages/example_code/join_voice.cpp b/docpages/example_code/join_voice.cpp index 6dc771f9ae..18dd5926c1 100644 --- a/docpages/example_code/join_voice.cpp +++ b/docpages/example_code/join_voice.cpp @@ -18,7 +18,7 @@ int main() { dpp::guild* g = dpp::find_guild(event.command.guild_id); /* Get the voice channel that the bot is currently in from this server (will return nullptr if we're not in a voice channel!) */ - auto current_vc = event.from->get_voice(event.command.guild_id); + auto current_vc = event.from()->get_voice(event.command.guild_id); bool join_vc = true; @@ -38,7 +38,7 @@ int main() { /* We are on a different voice channel. We should leave it, then join the new one * by falling through to the join_vc branch below. */ - event.from->disconnect_voice(event.command.guild_id); + event.from()->disconnect_voice(event.command.guild_id); join_vc = true; } diff --git a/docpages/example_code/mp3.cpp b/docpages/example_code/mp3.cpp index 83cc555116..1daf9e5d8c 100644 --- a/docpages/example_code/mp3.cpp +++ b/docpages/example_code/mp3.cpp @@ -75,7 +75,7 @@ int main() { event.reply("Joined your channel!"); } else if (event.command.get_command_name() == "mp3") { /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + dpp::voiceconn* v = event.from()->get_voice(event.command.guild_id); /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { diff --git a/docpages/example_code/oggopus.cpp b/docpages/example_code/oggopus.cpp index 5c5d3e8952..7cfa65ae41 100644 --- a/docpages/example_code/oggopus.cpp +++ b/docpages/example_code/oggopus.cpp @@ -39,7 +39,7 @@ int main(int argc, char const *argv[]) { } else if (event.command.get_command_name() == "play") { /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + dpp::voiceconn* v = event.from()->get_voice(event.command.guild_id); /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { diff --git a/docpages/example_code/oggopus2.cpp b/docpages/example_code/oggopus2.cpp index 46956a553c..ea969c9bf9 100644 --- a/docpages/example_code/oggopus2.cpp +++ b/docpages/example_code/oggopus2.cpp @@ -37,7 +37,7 @@ int main() { event.reply("Joined your channel!"); } else if (event.command.get_command_name() == "play") { /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + dpp::voiceconn* v = event.from()->get_voice(event.command.guild_id); /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { diff --git a/docpages/example_code/record_user.cpp b/docpages/example_code/record_user.cpp index 9b98af56c7..7de36710f3 100644 --- a/docpages/example_code/record_user.cpp +++ b/docpages/example_code/record_user.cpp @@ -36,7 +36,7 @@ int main() { /* Tell the user we joined their channel. */ event.reply("Joined your channel, now recording!"); } else if (event.command.get_command_name() == "stop") { - event.from->disconnect_voice(event.command.guild_id); + event.from()->disconnect_voice(event.command.guild_id); fclose(fd); event.reply("Stopped recording."); diff --git a/docpages/example_code/soundboard.cpp b/docpages/example_code/soundboard.cpp index 77602afd53..457a44a9f7 100644 --- a/docpages/example_code/soundboard.cpp +++ b/docpages/example_code/soundboard.cpp @@ -47,7 +47,7 @@ int main() { event.reply("Joined your channel!"); } else if (event.command.get_command_name() == "robot") { /* Get the voice channel the bot is in, in this current guild. */ - dpp::voiceconn* v = event.from->get_voice(event.command.guild_id); + dpp::voiceconn* v = event.from()->get_voice(event.command.guild_id); /* If the voice channel was invalid, or there is an issue with it, then tell the user. */ if (!v || !v->voiceclient || !v->voiceclient->is_ready()) { From 786b1dde768aa3034a58933ce2bd7e30646ebb50 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 14 Dec 2024 02:24:20 +0000 Subject: [PATCH 111/112] docs: missing comment blocks --- include/dpp/cluster.h | 6 +++++ include/dpp/sslclient.h | 1 + include/dpp/thread_pool.h | 49 ++++++++++++++++++++++++++++++++++++++- include/dpp/timer.h | 9 +++++++ include/dpp/wsclient.h | 1 + 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index d3cfc3b693..988b9e54b9 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -52,6 +52,12 @@ namespace dpp { +/** + * @brief Pass this value into the constructor of dpp::cluster for the shard count to create a cluster with no shards. + * A cluster with no shards does not connect to a websocket, but can still use the event loop to dispatch HTTPS API + * requests to Discord. This is useful for bots that do not need to receive websocket events as it will save a lot of + * resources. + */ constexpr uint32_t NO_SHARDS = ~0U; /** diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 3fe1c4c0db..896a60baf5 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -256,6 +256,7 @@ class DPP_EXPORT ssl_client /** * @brief Connect to a specified host and port. Throws std::runtime_error on fatal error. + * @param creator Creating cluster * @param _hostname The hostname to connect to * @param _port the Port number to connect to * @param plaintext_downgrade Set to true to connect using plaintext only, without initialising SSL. diff --git a/include/dpp/thread_pool.h b/include/dpp/thread_pool.h index 54a498c868..724ce24a54 100644 --- a/include/dpp/thread_pool.h +++ b/include/dpp/thread_pool.h @@ -30,17 +30,35 @@ namespace dpp { +/** + * @brief A work unit is a lambda executed in the thread pool + */ using work_unit = std::function; /** - * A task within a thread pool. A simple lambda that accepts no parameters and returns void. + * @brief A task within a thread pool. A simple lambda that accepts no parameters and returns void. */ struct DPP_EXPORT thread_pool_task { + /** + * @brief Task priority, lower value is higher priority + */ int priority; + /** + * @brief Work unit to execute as the task + */ work_unit function; }; +/** + * @brief Compares two thread pool tasks by priority + */ struct DPP_EXPORT thread_pool_task_comparator { + /** + * @brief Compare two tasks + * @param a first task + * @param b second task + * @return true if a > b + */ bool operator()(const thread_pool_task &a, const thread_pool_task &b) const { return a.priority > b.priority; }; @@ -51,10 +69,30 @@ struct DPP_EXPORT thread_pool_task_comparator { * into a queue, which is processed in-order by whichever thread is free. */ struct DPP_EXPORT thread_pool { + + /** + * @brief Threads that comprise the thread pool + */ std::vector threads; + + /** + * @brief Priority queue of tasks to be executed + */ std::priority_queue, thread_pool_task_comparator> tasks; + + /** + * @brief Mutex for accessing the priority queue + */ std::mutex queue_mutex; + + /** + * @brief Condition variable to notify for new tasks to run + */ std::condition_variable cv; + + /** + * @brief True if the thread pool is due to stop + */ bool stop{false}; /** @@ -63,7 +101,16 @@ struct DPP_EXPORT thread_pool { * @param num_threads number of threads in the pool */ explicit thread_pool(class cluster* creator, size_t num_threads = std::thread::hardware_concurrency()); + + /** + * @brief Destroy the thread pool + */ ~thread_pool(); + + /** + * @brief Enqueue a new task to the thread pool + * @param task task to enqueue + */ void enqueue(thread_pool_task task); }; diff --git a/include/dpp/timer.h b/include/dpp/timer.h index 8896288ffe..b5c9827ddf 100644 --- a/include/dpp/timer.h +++ b/include/dpp/timer.h @@ -75,7 +75,16 @@ struct DPP_EXPORT timer_t { timer_callback_t on_stop{}; }; +/** + * @brief Used to compare two timers next tick times in a priority queue + */ struct DPP_EXPORT timer_comparator { + /** + * @brief Compare two timers + * @param a first timer + * @param b second timer + * @return returns true if a > b + */ bool operator()(const timer_t &a, const timer_t &b) const { return a.next_tick > b.next_tick; }; diff --git a/include/dpp/wsclient.h b/include/dpp/wsclient.h index e7fc875d0a..9380d2d072 100644 --- a/include/dpp/wsclient.h +++ b/include/dpp/wsclient.h @@ -179,6 +179,7 @@ class DPP_EXPORT websocket_client : public ssl_client { /** * @brief Connect to a specific websocket server. + * @param creator Creating cluster * @param hostname Hostname to connect to * @param port Port to connect to * @param urlpath The URL path components of the HTTP request to send From 56694b2c1ca8f072f0902a418eb866dc5479e998 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Sat, 14 Dec 2024 12:35:47 +0000 Subject: [PATCH 112/112] fix: make new constructor for cluster explicit --- include/dpp/cluster.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 988b9e54b9..144b64b0c5 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -282,7 +282,7 @@ class DPP_EXPORT cluster { * All callbacks and events are placed into the thread pool. The bigger you make this pool (but generally no bigger than your number of cores), the more your bot will scale. * @throw dpp::exception Thrown on windows, if WinSock fails to initialise, or on any other system if a dpp::request_queue fails to construct */ - cluster(uint32_t pool_threads = std::thread::hardware_concurrency() / 2); + explicit cluster(uint32_t pool_threads = std::thread::hardware_concurrency() / 2); /** * @brief Constructor for creating a cluster. All but the token are optional.