├── .gitignore ├── src └── coros │ ├── commons │ ├── error.h │ └── error.cpp │ ├── network │ ├── util.h │ ├── socket_op.h │ ├── util.cpp │ ├── stream.h │ ├── server_socket.h │ ├── server.h │ ├── socket.h │ ├── server.cpp │ ├── stream.cpp │ ├── server_socket.cpp │ └── socket.cpp │ ├── io │ ├── read_awaiter.cpp │ ├── write_awaiter.cpp │ ├── read_awaiter.h │ ├── write_awaiter.h │ ├── monitor.h │ ├── listener.h │ ├── monitor.cpp │ └── listener.cpp │ ├── app.h │ ├── app.cpp │ ├── async │ ├── thread_pool.h │ ├── thread_pool.cpp │ └── future.h │ └── memory │ ├── buffer.h │ └── buffer.cpp ├── CMakeLists.txt ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build -------------------------------------------------------------------------------- /src/coros/commons/error.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMONS_ERROR_H 2 | #define COMMONS_ERROR_H 3 | 4 | #include 5 | 6 | namespace coros::base { 7 | void throw_errno(int status, std::string prefix); 8 | } 9 | 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/coros/network/util.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_NETWORK_UTIL_H 2 | #define COROS_NETWORK_UTIL_H 3 | 4 | #include 5 | 6 | struct addrinfo; 7 | 8 | namespace coros::base { 9 | void set_non_blocking_socket(int socket_fd); 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/coros/network/socket_op.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_NETWORK_SOCKET_OP_H 2 | #define COROS_NETWORK_SOCKET_OP_H 3 | 4 | namespace coros::base { 5 | enum SocketOperation { 6 | SOCKET_OP_BLOCK, 7 | SOCKET_OP_ERROR, 8 | SOCKET_OP_CLOSE, 9 | SOCKET_OP_SUCCESS 10 | }; 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/coros/commons/error.cpp: -------------------------------------------------------------------------------- 1 | #include "error.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void coros::base::throw_errno(int status, std::string prefix) { 9 | if (status == -1) { 10 | throw std::runtime_error(prefix.append(strerror(errno))); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/coros/io/read_awaiter.cpp: -------------------------------------------------------------------------------- 1 | #include "read_awaiter.h" 2 | #include "listener.h" 3 | 4 | #include 5 | 6 | bool coros::base::IoReadAwaiter::await_ready() noexcept { 7 | return false; 8 | } 9 | 10 | void coros::base::IoReadAwaiter::await_suspend(std::coroutine_handle<> handle) { 11 | listener.set_read_handle(handle); 12 | } 13 | 14 | void coros::base::IoReadAwaiter::await_resume() { 15 | } 16 | -------------------------------------------------------------------------------- /src/coros/io/write_awaiter.cpp: -------------------------------------------------------------------------------- 1 | #include "write_awaiter.h" 2 | #include "listener.h" 3 | 4 | #include 5 | 6 | bool coros::base::IoWriteAwaiter::await_ready() noexcept { 7 | return false; 8 | } 9 | 10 | void coros::base::IoWriteAwaiter::await_suspend(std::coroutine_handle<> handle) { 11 | listener.set_write_handle(handle); 12 | } 13 | 14 | void coros::base::IoWriteAwaiter::await_resume() { 15 | } 16 | -------------------------------------------------------------------------------- /src/coros/io/read_awaiter.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_IO_READ_AWAITER_H 2 | #define COROS_IO_READ_AWAITER_H 3 | 4 | #include 5 | 6 | namespace coros::base { 7 | class IoEventListener; 8 | 9 | struct IoReadAwaiter { 10 | IoEventListener& listener; 11 | bool await_ready() noexcept; 12 | void await_suspend(std::coroutine_handle<> handle); 13 | void await_resume(); 14 | }; 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/coros/io/write_awaiter.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_IO_WRITE_AWAITER_H 2 | #define COROS_IO_WRITE_AWAITER_H 3 | 4 | #include 5 | 6 | namespace coros::base { 7 | class IoEventListener; 8 | 9 | struct IoWriteAwaiter { 10 | IoEventListener& listener; 11 | bool await_ready() noexcept; 12 | void await_suspend(std::coroutine_handle<> handle); 13 | void await_resume(); 14 | }; 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/coros/network/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include "coros/commons/error.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | void coros::base::set_non_blocking_socket(int socket_fd) { 13 | int flags = fcntl(socket_fd, F_GETFL); 14 | int status = fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); 15 | throw_errno(status, "set_non_blocking_socket error: "); 16 | } 17 | -------------------------------------------------------------------------------- /src/coros/network/stream.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_NETWORK_STREAM_H 2 | #define COROS_NETWORK_STREAM_H 3 | 4 | #include "socket_op.h" 5 | 6 | #include 7 | 8 | namespace coros::base { 9 | class ByteBuffer; 10 | 11 | class SocketStream { 12 | private: 13 | int socket_fd; 14 | std::atomic_bool is_closed; 15 | public: 16 | SocketStream(int socket_fd); 17 | SocketOperation recv_from_socket(ByteBuffer& buffer); 18 | SocketOperation send_to_socket(ByteBuffer& buffer); 19 | void close(); 20 | }; 21 | } 22 | 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(coros STATIC 2 | src/coros/async/thread_pool.cpp 3 | src/coros/commons/error.cpp 4 | src/coros/io/listener.cpp 5 | src/coros/io/monitor.cpp 6 | src/coros/io/read_awaiter.cpp 7 | src/coros/io/write_awaiter.cpp 8 | src/coros/memory/buffer.cpp 9 | src/coros/network/server.cpp 10 | src/coros/network/socket.cpp 11 | src/coros/network/server_socket.cpp 12 | src/coros/network/stream.cpp 13 | src/coros/network/util.cpp 14 | src/coros/app.cpp) 15 | 16 | find_package(Threads REQUIRED) 17 | target_include_directories(coros PUBLIC src) 18 | target_link_libraries(coros PRIVATE Threads::Threads) 19 | -------------------------------------------------------------------------------- /src/coros/app.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_APP_H 2 | #define COROS_APP_H 3 | 4 | #include "async/future.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace coros::base { 11 | class Socket; 12 | 13 | class Server; 14 | 15 | class ServerApplication { 16 | private: 17 | std::mutex socket_mutex; 18 | std::unordered_map> socket_map; 19 | public: 20 | void handle_socket(Server& server, std::shared_ptr socket_ptr); 21 | virtual Future on_request(Server& server, std::shared_ptr socket) = 0; 22 | void shutdown(); 23 | }; 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/coros/network/server_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_NETWORK_SERVER_SOCKET_H 2 | #define COROS_NETWORK_SERVER_SOCKET_H 3 | 4 | #include "coros/async/future.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace coros::base { 10 | class IoEventMonitor; 11 | 12 | class IoEventListener; 13 | 14 | class Socket; 15 | 16 | class ServerSocket { 17 | private: 18 | int socket_fd; 19 | IoEventMonitor& io_monitor; 20 | IoEventListener* io_listener; 21 | std::atomic_bool is_closed; 22 | public: 23 | ServerSocket(short port, IoEventMonitor& io_monitor); 24 | AwaitableValue> accept_conn(); 25 | void close_socket(); 26 | }; 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/coros/app.cpp: -------------------------------------------------------------------------------- 1 | #include "app.h" 2 | #include "network/socket.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | void coros::base::ServerApplication::handle_socket(Server& server, std::shared_ptr socket_ptr) { 9 | { 10 | std::lock_guard socket_lock(socket_mutex); 11 | socket_map[socket_ptr->get_fd()] = socket_ptr; 12 | } 13 | on_request(server, std::move(socket_ptr)); 14 | } 15 | 16 | void coros::base::ServerApplication::shutdown() { 17 | std::lock_guard socket_lock(socket_mutex); 18 | for (auto it = socket_map.begin(); it != socket_map.end(); ++it) { 19 | if (std::shared_ptr socket = it->second.lock()) { 20 | socket->close_socket(); 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/coros/network/server.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_SERVER_H 2 | #define COROS_SERVER_H 3 | 4 | #include "coros/async/future.h" 5 | #include "coros/network/server_socket.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace coros::base { 11 | class ServerApplication; 12 | 13 | class ThreadPool; 14 | 15 | class IoEventMonitor; 16 | 17 | class Server { 18 | private: 19 | ServerApplication& server_app; 20 | IoEventMonitor& io_monitor; 21 | ThreadPool& thread_pool; 22 | ServerSocket server_socket; 23 | std::atomic_bool marked_for_close; 24 | Future run_server_loop(); 25 | public: 26 | Server(short port, ServerApplication& server_app, IoEventMonitor& io_monitor, 27 | ThreadPool& thread_pool); 28 | void start(); 29 | void shutdown(); 30 | }; 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/coros/async/thread_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_ASYNC_THREAD_POOL_H 2 | #define COROS_ASYNC_THREAD_POOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace coros::base { 13 | class ThreadPool { 14 | private: 15 | const int max_threads; 16 | bool is_shutdown; 17 | std::vector threads; 18 | std::queue> jobs; 19 | std::mutex jobs_mutex; 20 | std::condition_variable jobs_condition; 21 | void run_jobs(); 22 | public: 23 | ThreadPool(); 24 | ThreadPool(int max_threads); 25 | void shutdown(); 26 | void run(std::function job); 27 | void run(std::coroutine_handle<> handle); 28 | }; 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/coros/io/monitor.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_IO_MONITOR_H 2 | #define COROS_IO_MONITOR_H 3 | 4 | #include "listener.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define IO_POLL_MAX_EVENTS 1000 13 | #define IO_POLL_TIMEOUT 2000 14 | 15 | namespace coros::base { 16 | class IoEventListener; 17 | 18 | class ThreadPool; 19 | 20 | class IoEventMonitor { 21 | private: 22 | ThreadPool& thread_pool; 23 | std::atomic_bool is_shutdown; 24 | std::mutex fd_mutex; 25 | std::unordered_map> fd_listener_map; 26 | int epoll_fd; 27 | void run(); 28 | void trigger_events(epoll_event* events, int count); 29 | public: 30 | IoEventMonitor(ThreadPool& thread_pool); 31 | void shutdown(); 32 | IoEventListener* register_fd(int fd); 33 | void remove_fd(int fd); 34 | }; 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/coros/memory/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_MEMORY_BUFFER_H 2 | #define COROS_MEMORY_BUFFER_H 3 | 4 | #include 5 | 6 | namespace coros::base { 7 | struct IOChunk { 8 | std::byte* data; 9 | long long size; 10 | }; 11 | 12 | class ByteBuffer { 13 | private: 14 | std::byte* data; 15 | long long read_p; 16 | long long write_p; 17 | long long max_capacity; 18 | long long get_index(long long p); 19 | bool has_wrap_around(); 20 | public: 21 | ByteBuffer(long long max_capacity); 22 | ~ByteBuffer(); 23 | IOChunk get_read_chunk(); 24 | IOChunk get_write_chunk(); 25 | void increment_read_pointer(long long size); 26 | void increment_write_pointer(long long size); 27 | void read(std::byte* dest, long long size); 28 | void write(const std::byte* src, long long size); 29 | long long get_total_capacity(); 30 | long long get_total_remaining(); 31 | }; 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Manoharan Ajay Anand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/coros/io/listener.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_IO_LISTENER_H 2 | #define COROS_IO_LISTENER_H 3 | 4 | #include "read_awaiter.h" 5 | #include "write_awaiter.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace coros::base { 13 | class IoEventMonitor; 14 | 15 | class ThreadPool; 16 | 17 | class IoEventListener { 18 | private: 19 | ThreadPool& thread_pool; 20 | int epoll_fd; 21 | int io_fd; 22 | std::atomic_bool marked_for_close; 23 | std::mutex listener_mutex; 24 | bool listening_read; 25 | bool listening_write; 26 | std::optional> read_handle_opt; 27 | std::optional> write_handle_opt; 28 | void listen_for_event(bool need_read, bool need_write); 29 | bool run_handle(std::optional>& handle_opt, bool has_event); 30 | public: 31 | IoEventListener(ThreadPool& thread_pool, int epoll_fd, int io_fd); 32 | ~IoEventListener(); 33 | void on_event(bool can_read, bool can_write); 34 | void set_read_handle(std::coroutine_handle<> read_handle); 35 | void set_write_handle(std::coroutine_handle<> write_handle); 36 | IoReadAwaiter await_read(); 37 | IoWriteAwaiter await_write(); 38 | void close(); 39 | }; 40 | } 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coros 2 | 3 | ## About 4 | 5 | Coros is a simple TCP server framework built with C++ 20 coroutines. The use of coroutines enables Coros to be highly concurrent even with a small number of threads. 6 | 7 | ## How it Works 8 | 9 | In order to understand how Coros works, you need to understand how coroutines work. Coroutines are 10 | simply functions that can be suspended and resumed while keeping it's state intact. This is 11 | especially useful for concurrency as a coroutine can be suspended when it's performing a blocking 12 | operation and be resumed from the suspension point after the operation is complete. During 13 | the suspension, the underlying thread that is running the coroutine can perform other operations 14 | without idling. 15 | 16 | For every new socket connection, Coros spawns a new coroutine which is picked up by a thread in 17 | it's thread pool. The coroutine is able to read/write from the socket. When a socket operation ends 18 | up blocking, such as when waiting for a client to send data, the coroutine is suspended and 19 | Coros's event monitor is signalled. The underlying thread picks up other jobs such as 20 | serving another socket connection. Coros's event monitor will then resume the coroutine, on any one 21 | of the free threads in the thread pool, once the socket is ready for read/write operations. 22 | 23 | ## Guide 24 | 25 | Please refer to the wiki to learn how to use Coros for your applications. 26 | 27 | ## Compatibility 28 | 29 | This project can be compiled with g++ 10 on Linux. 30 | -------------------------------------------------------------------------------- /src/coros/network/socket.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_SOCKET_H 2 | #define COROS_SOCKET_H 3 | 4 | #include "coros/async/future.h" 5 | #include "coros/memory/buffer.h" 6 | #include "stream.h" 7 | #include "socket_op.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace coros::base { 14 | class IoEventMonitor; 15 | 16 | class IoEventListener; 17 | 18 | struct SocketDetails { 19 | int socket_fd; 20 | sockaddr_storage client_addr; 21 | socklen_t addr_size; 22 | }; 23 | 24 | class Socket { 25 | private: 26 | SocketDetails details; 27 | IoEventMonitor& io_monitor; 28 | IoEventListener* io_listener; 29 | SocketStream stream; 30 | ByteBuffer input_buffer; 31 | ByteBuffer output_buffer; 32 | std::atomic_bool is_closed; 33 | void read_available(std::byte*& dest, long long& size, long long& total_read); 34 | void skip_available(long long& size, long long& total_skipped); 35 | void write_available(const std::byte*& src, long long& size); 36 | public: 37 | Socket(SocketDetails details, IoEventMonitor& io_monitor); 38 | AwaitableValue read(std::byte* dest, long long size, bool read_fully); 39 | AwaitableValue skip(long long size, bool skip_fully); 40 | AwaitableFuture write(const std::byte* src, long long size); 41 | AwaitableFuture flush(); 42 | int get_fd(); 43 | void close_socket(); 44 | }; 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/coros/network/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | #include "socket.h" 3 | #include "coros/app.h" 4 | #include "coros/async/thread_pool.h" 5 | #include "coros/io/monitor.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | coros::base::Server::Server(short port, ServerApplication& server_app, 13 | IoEventMonitor& io_monitor, ThreadPool& thread_pool) 14 | : server_socket(port, io_monitor), server_app(server_app), 15 | io_monitor(io_monitor), thread_pool(thread_pool) { 16 | this->marked_for_close = false; 17 | } 18 | 19 | coros::base::Future coros::base::Server::run_server_loop() { 20 | try { 21 | while (!marked_for_close) { 22 | std::shared_ptr socket = co_await server_socket.accept_conn(); 23 | thread_pool.run([&, socket_ptr = std::move(socket)] { 24 | server_app.handle_socket( 25 | *this, 26 | std::move(socket_ptr) 27 | ); 28 | }); 29 | } 30 | } catch (std::runtime_error error) { 31 | std::cout << "Error: " << error.what() << std::endl; 32 | } 33 | server_socket.close_socket(); 34 | } 35 | 36 | void coros::base::Server::start() { 37 | thread_pool.run([&] { 38 | run_server_loop(); 39 | }); 40 | } 41 | 42 | void coros::base::Server::shutdown() { 43 | if (marked_for_close.exchange(true)) { 44 | throw std::runtime_error(std::string("Server shutdown(): Server already shutdown")); 45 | } 46 | server_socket.close_socket(); 47 | server_app.shutdown(); 48 | io_monitor.shutdown(); 49 | thread_pool.shutdown(); 50 | } 51 | -------------------------------------------------------------------------------- /src/coros/network/stream.cpp: -------------------------------------------------------------------------------- 1 | #include "stream.h" 2 | #include "socket_op.h" 3 | 4 | #include "coros/memory/buffer.h" 5 | #include "coros/commons/error.h" 6 | 7 | #include 8 | #include 9 | 10 | coros::base::SocketStream::SocketStream(int socket_fd) { 11 | this->socket_fd = socket_fd; 12 | this->is_closed = false; 13 | } 14 | 15 | coros::base::SocketOperation coros::base::SocketStream::recv_from_socket(ByteBuffer& buffer) { 16 | if (is_closed) { 17 | return SOCKET_OP_CLOSE; 18 | } 19 | while (buffer.get_total_capacity() > 0) { 20 | coros::base::IOChunk chunk = buffer.get_write_chunk(); 21 | int size_read = recv(socket_fd, chunk.data, chunk.size, 0); 22 | if (size_read == 0) { 23 | this->close(); 24 | return SOCKET_OP_CLOSE; 25 | } 26 | if (size_read < 0) { 27 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 28 | return SOCKET_OP_BLOCK; 29 | } 30 | throw_errno(size_read, "SocketStream recv(): "); 31 | } 32 | buffer.increment_write_pointer(size_read); 33 | } 34 | return SOCKET_OP_SUCCESS; 35 | } 36 | 37 | coros::base::SocketOperation coros::base::SocketStream::send_to_socket(ByteBuffer& buffer) { 38 | if (is_closed) { 39 | return SOCKET_OP_CLOSE; 40 | } 41 | while (buffer.get_total_remaining() > 0) { 42 | coros::base::IOChunk chunk = buffer.get_read_chunk(); 43 | int size_written = send(socket_fd, chunk.data, chunk.size, 0); 44 | if (size_written < 0) { 45 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 46 | return SOCKET_OP_BLOCK; 47 | } 48 | throw_errno(size_written, "SocketStream send(): "); 49 | } 50 | buffer.increment_read_pointer(size_written); 51 | } 52 | return SOCKET_OP_SUCCESS; 53 | } 54 | 55 | void coros::base::SocketStream::close() { 56 | is_closed = true; 57 | } 58 | -------------------------------------------------------------------------------- /src/coros/async/thread_pool.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_pool.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | coros::base::ThreadPool::ThreadPool() : max_threads(std::thread::hardware_concurrency()) { 11 | is_shutdown = false; 12 | } 13 | 14 | coros::base::ThreadPool::ThreadPool(int max_threads) : max_threads(max_threads) { 15 | is_shutdown = false; 16 | } 17 | 18 | void coros::base::ThreadPool::run_jobs() { 19 | while (true) { 20 | std::function job; 21 | { 22 | std::unique_lock jobs_lock(jobs_mutex); 23 | jobs_condition.wait(jobs_lock, [&] { return !jobs.empty() || is_shutdown;}); 24 | if (jobs.empty() && is_shutdown) { 25 | return; 26 | } 27 | job = jobs.front(); 28 | jobs.pop(); 29 | } 30 | try { 31 | job(); 32 | } catch (std::runtime_error err) { 33 | std::cerr << err.what() << std::endl; 34 | } 35 | } 36 | } 37 | 38 | void coros::base::ThreadPool::run(std::function job) { 39 | { 40 | std::lock_guard guard(jobs_mutex); 41 | if (is_shutdown) { 42 | throw std::runtime_error("ThreadPool run() error: ThreadPool already shutdown"); 43 | } 44 | jobs.push(job); 45 | if (threads.size() < max_threads) { 46 | threads.push_back(std::thread(&ThreadPool::run_jobs, this)); 47 | } 48 | } 49 | jobs_condition.notify_one(); 50 | } 51 | 52 | void coros::base::ThreadPool::run(std::coroutine_handle<> handle) { 53 | run([&, handle]() { 54 | handle.resume(); 55 | }); 56 | } 57 | 58 | void coros::base::ThreadPool::shutdown() { 59 | { 60 | std::lock_guard guard(jobs_mutex); 61 | if (is_shutdown) { 62 | throw std::runtime_error("ThreadPool shutdown() error: ThreadPool already shutdown"); 63 | } 64 | is_shutdown = true; 65 | } 66 | jobs_condition.notify_all(); 67 | for (auto it = threads.begin(); it != threads.end(); ++it) { 68 | it->join(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/coros/io/monitor.cpp: -------------------------------------------------------------------------------- 1 | #include "monitor.h" 2 | #include "listener.h" 3 | 4 | #include "coros/async/thread_pool.h" 5 | #include "coros/commons/error.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | coros::base::IoEventMonitor::IoEventMonitor(ThreadPool& thread_pool): thread_pool(thread_pool) { 14 | is_shutdown = false; 15 | epoll_fd = epoll_create(1); 16 | thread_pool.run([&]() { 17 | this->run(); 18 | }); 19 | } 20 | 21 | void coros::base::IoEventMonitor::run() { 22 | std::array events; 23 | while (!is_shutdown) { 24 | int count = epoll_wait(epoll_fd, events.data(), IO_POLL_MAX_EVENTS, IO_POLL_TIMEOUT); 25 | if (count < 0) { 26 | if (errno == EINTR) { 27 | continue; 28 | } 29 | throw_errno(count, "IoEventMonitor run() error: "); 30 | } 31 | trigger_events(events.data(), count); 32 | } 33 | } 34 | 35 | void coros::base::IoEventMonitor::shutdown() { 36 | if (is_shutdown.exchange(true)) { 37 | throw std::runtime_error("IoEventMonitor shutdown() error: already shutdown"); 38 | } 39 | fd_listener_map.clear(); 40 | close(epoll_fd); 41 | } 42 | 43 | coros::base::IoEventListener* coros::base::IoEventMonitor::register_fd(int fd) { 44 | auto listener = std::make_unique(thread_pool, epoll_fd, fd); 45 | auto listener_p = listener.get(); 46 | { 47 | std::lock_guard guard(fd_mutex); 48 | fd_listener_map[fd] = std::move(listener); 49 | } 50 | return listener_p; 51 | } 52 | 53 | void coros::base::IoEventMonitor::remove_fd(int fd) { 54 | std::lock_guard guard(fd_mutex); 55 | fd_listener_map.erase(fd); 56 | } 57 | 58 | void coros::base::IoEventMonitor::trigger_events(epoll_event* events, int count) { 59 | std::lock_guard guard(fd_mutex); 60 | for (int i = 0; i < count; ++i) { 61 | epoll_event& event = *(events + i); 62 | auto it = fd_listener_map.find(event.data.fd); 63 | if (it == fd_listener_map.end()) { 64 | continue; 65 | } 66 | bool can_read = (event.events & EPOLLIN) == EPOLLIN; 67 | bool can_write = (event.events & EPOLLOUT) == EPOLLOUT; 68 | it->second->on_event(can_read, can_write); 69 | } 70 | } -------------------------------------------------------------------------------- /src/coros/network/server_socket.cpp: -------------------------------------------------------------------------------- 1 | #include "server_socket.h" 2 | #include "util.h" 3 | #include "socket.h" 4 | 5 | #include "coros/io/monitor.h" 6 | #include "coros/io/listener.h" 7 | #include "coros/commons/error.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | addrinfo* get_local_addr_info(short port) { 17 | std::string service = std::to_string(port); 18 | int status; 19 | addrinfo* res; 20 | addrinfo hints = {}; 21 | hints.ai_family = AF_INET; 22 | hints.ai_socktype = SOCK_STREAM; 23 | hints.ai_flags = AI_PASSIVE; 24 | status = getaddrinfo(NULL, service.c_str(), &hints, &res); 25 | coros::base::throw_errno(status, "get_addr_info: "); 26 | return res; 27 | } 28 | 29 | coros::base::ServerSocket::ServerSocket(short port, 30 | IoEventMonitor& io_monitor): io_monitor(io_monitor), 31 | is_closed(false) { 32 | addrinfo* info = get_local_addr_info(port); 33 | socket_fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol); 34 | throw_errno(socket_fd, "ServerSocket socket(): "); 35 | int status = bind(socket_fd, info->ai_addr, info->ai_addrlen); 36 | freeaddrinfo(info); 37 | throw_errno(status, "ServerSocket bind(): "); 38 | int yes = 1; 39 | status = setsockopt(socket_fd , SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); 40 | throw_errno(status, "ServerSocket setsockopt(): "); 41 | set_non_blocking_socket(socket_fd); 42 | status = listen(socket_fd, 10); 43 | throw_errno(status, "ServerSocket listen(): "); 44 | io_listener = io_monitor.register_fd(socket_fd); 45 | } 46 | 47 | coros::base::AwaitableValue> 48 | coros::base::ServerSocket::accept_conn() { 49 | SocketDetails details; 50 | while (true) { 51 | details.socket_fd = accept(socket_fd, 52 | reinterpret_cast(&details.client_addr), 53 | &details.addr_size); 54 | if (details.socket_fd != -1) { 55 | break; 56 | } 57 | if (errno != EAGAIN && errno != EWOULDBLOCK) { 58 | throw_errno(details.socket_fd, "ServerSocket accept() error: "); 59 | } 60 | co_await io_listener->await_read(); 61 | } 62 | set_non_blocking_socket(details.socket_fd); 63 | co_return std::make_shared(details, io_monitor); 64 | } 65 | 66 | void coros::base::ServerSocket::close_socket() { 67 | if (is_closed.exchange(true)) { 68 | return; 69 | } 70 | io_monitor.remove_fd(socket_fd); 71 | close(socket_fd); 72 | } 73 | -------------------------------------------------------------------------------- /src/coros/memory/buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | coros::base::ByteBuffer::ByteBuffer(long long max_capacity) { 9 | this->data = new std::byte[max_capacity]; 10 | this->read_p = 0; 11 | this->write_p = 0; 12 | this->max_capacity = max_capacity; 13 | } 14 | 15 | coros::base::ByteBuffer::~ByteBuffer() { 16 | delete[] this->data; 17 | } 18 | 19 | long long coros::base::ByteBuffer::get_index(long long p) { 20 | return p % max_capacity; 21 | } 22 | 23 | bool coros::base::ByteBuffer::has_wrap_around() { 24 | long long quotient_w = write_p / max_capacity; 25 | long long quotient_r = read_p / max_capacity; 26 | return quotient_w > quotient_r && read_p > (quotient_r * max_capacity); 27 | } 28 | 29 | coros::base::IOChunk coros::base::ByteBuffer::get_read_chunk() { 30 | long long read_index = get_index(read_p); 31 | if (!has_wrap_around()) { 32 | return { data + read_index, write_p - read_p }; 33 | } 34 | return { data + read_index, max_capacity - read_index }; 35 | } 36 | 37 | coros::base::IOChunk coros::base::ByteBuffer::get_write_chunk() { 38 | long long write_index = get_index(write_p); 39 | if (has_wrap_around()) { 40 | return { data + write_index, get_index(read_p) - write_index }; 41 | } 42 | return { data + write_index, max_capacity - write_index }; 43 | } 44 | 45 | void coros::base::ByteBuffer::increment_read_pointer(long long size) { 46 | if (size > get_total_remaining()) { 47 | throw std::runtime_error("ByteBuffer increment_read_pointer error: More than remaining"); 48 | } 49 | read_p += size; 50 | if (read_p > max_capacity && write_p > max_capacity) { 51 | read_p -= max_capacity; 52 | write_p -= max_capacity; 53 | } 54 | } 55 | 56 | void coros::base::ByteBuffer::increment_write_pointer(long long size) { 57 | if (size > get_total_capacity()) { 58 | throw std::runtime_error("ByteBuffer increment_write_pointer error: More than capacity"); 59 | } 60 | write_p += size; 61 | } 62 | 63 | void coros::base::ByteBuffer::read(std::byte* dest, long long size) { 64 | if (size > get_total_remaining()) { 65 | throw std::runtime_error("ByteBuffer read error: Read size more than remaining"); 66 | } 67 | while (size > 0) { 68 | IOChunk chunk = get_read_chunk(); 69 | long long read_size = std::min(size, chunk.size); 70 | std::memcpy(dest, chunk.data, read_size); 71 | size -= read_size; 72 | dest += read_size; 73 | increment_read_pointer(read_size); 74 | } 75 | } 76 | 77 | void coros::base::ByteBuffer::write(const std::byte* src, long long size) { 78 | if (size > get_total_capacity()) { 79 | throw std::runtime_error("ByteBuffer write error: Write size more than capacity"); 80 | } 81 | while (size > 0) { 82 | IOChunk chunk = get_write_chunk(); 83 | long long write_size = std::min(size, chunk.size); 84 | std::memcpy(chunk.data, src, write_size); 85 | size -= write_size; 86 | src += write_size; 87 | increment_write_pointer(write_size); 88 | } 89 | } 90 | 91 | long long coros::base::ByteBuffer::get_total_capacity() { 92 | return max_capacity - get_total_remaining(); 93 | } 94 | 95 | long long coros::base::ByteBuffer::get_total_remaining() { 96 | return write_p - read_p; 97 | } 98 | -------------------------------------------------------------------------------- /src/coros/io/listener.cpp: -------------------------------------------------------------------------------- 1 | #include "listener.h" 2 | 3 | #include "coros/async/thread_pool.h" 4 | #include "coros/commons/error.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | coros::base::IoEventListener::IoEventListener(ThreadPool& thread_pool, 11 | int epoll_fd, 12 | int io_fd): thread_pool(thread_pool), 13 | epoll_fd(epoll_fd), 14 | marked_for_close(false), 15 | listening_read(false), 16 | listening_write(false), 17 | io_fd(io_fd) { 18 | epoll_event e_event; 19 | e_event.data.fd = io_fd; 20 | int status = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, io_fd, &e_event); 21 | throw_errno(status, "IoEventListener constructor error: "); 22 | } 23 | 24 | coros::base::IoEventListener::~IoEventListener() { 25 | epoll_event e_event; 26 | e_event.data.fd = io_fd; 27 | int status = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, io_fd, &e_event); 28 | throw_errno(status, "IoEventListener destructor error: "); 29 | if (read_handle_opt) { 30 | read_handle_opt.value().destroy(); 31 | } 32 | if (write_handle_opt) { 33 | write_handle_opt.value().destroy(); 34 | } 35 | } 36 | 37 | void coros::base::IoEventListener::listen_for_event(bool need_read, bool need_write) { 38 | if (!need_read && !need_write) { 39 | return; 40 | } 41 | listening_read = listening_read | need_read; 42 | listening_write = listening_write | need_write; 43 | uint32_t event_flags = EPOLLONESHOT; 44 | if (listening_read) { 45 | event_flags = event_flags | EPOLLIN; 46 | } 47 | if (listening_write) { 48 | event_flags = event_flags | EPOLLOUT; 49 | } 50 | epoll_event e_event; 51 | e_event.data.fd = io_fd; 52 | e_event.events = event_flags; 53 | int status = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, io_fd, &e_event); 54 | throw_errno(status, "IoEventListener listen_for_event(): "); 55 | } 56 | 57 | bool coros::base::IoEventListener::run_handle(std::optional>& handle_opt, 58 | bool has_event) { 59 | if (handle_opt) { 60 | if (!has_event) { 61 | return true; 62 | } 63 | thread_pool.run(handle_opt.value()); 64 | handle_opt.reset(); 65 | } 66 | return false; 67 | } 68 | 69 | void coros::base::IoEventListener::on_event(bool can_read, bool can_write) { 70 | if (marked_for_close) { 71 | return; 72 | } 73 | std::lock_guard guard(listener_mutex); 74 | listening_read = false; 75 | listening_write = false; 76 | bool need_read = run_handle(read_handle_opt, can_read); 77 | bool need_write = run_handle(write_handle_opt, can_write); 78 | listen_for_event(need_read, need_write); 79 | } 80 | 81 | void coros::base::IoEventListener::set_read_handle(std::coroutine_handle<> read_handle) { 82 | std::lock_guard guard(listener_mutex); 83 | read_handle_opt = read_handle; 84 | listen_for_event(true, false); 85 | } 86 | 87 | void coros::base::IoEventListener::set_write_handle(std::coroutine_handle<> write_handle) { 88 | std::lock_guard guard(listener_mutex); 89 | write_handle_opt = write_handle; 90 | listen_for_event(false, true); 91 | } 92 | 93 | coros::base::IoReadAwaiter coros::base::IoEventListener::await_read() { 94 | return { *this }; 95 | } 96 | 97 | coros::base::IoWriteAwaiter coros::base::IoEventListener::await_write() { 98 | return { *this }; 99 | } 100 | 101 | void coros::base::IoEventListener::close() { 102 | if (marked_for_close.exchange(true)) { 103 | return; 104 | } 105 | std::lock_guard guard(listener_mutex); 106 | if (read_handle_opt) { 107 | thread_pool.run(read_handle_opt.value()); 108 | read_handle_opt.reset(); 109 | } 110 | if (write_handle_opt) { 111 | thread_pool.run(write_handle_opt.value()); 112 | write_handle_opt.reset(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/coros/async/future.h: -------------------------------------------------------------------------------- 1 | #ifndef COROS_ASYNC_FUTURE_H 2 | #define COROS_ASYNC_FUTURE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace coros::base { 9 | struct Future { 10 | struct promise_type { 11 | Future get_return_object() { return {}; } 12 | std::suspend_never initial_suspend() { return {}; } 13 | std::suspend_never final_suspend() noexcept { return {}; } 14 | void unhandled_exception() {} 15 | void return_void() {} 16 | }; 17 | }; 18 | 19 | struct AwaitableFuture { 20 | struct promise_type { 21 | std::coroutine_handle<> waiting; 22 | std::exception_ptr exception; 23 | 24 | AwaitableFuture get_return_object() { 25 | return { std::coroutine_handle::from_promise(*this) }; 26 | } 27 | 28 | std::suspend_never initial_suspend() { return {}; } 29 | 30 | struct final_awaiter { 31 | bool await_ready() noexcept { return false; } 32 | 33 | std::coroutine_handle<> await_suspend( 34 | std::coroutine_handle handle) noexcept { 35 | auto waiting = handle.promise().waiting; 36 | if (waiting) { 37 | return waiting; 38 | } 39 | return std::noop_coroutine(); 40 | } 41 | 42 | void await_resume() noexcept {} 43 | }; 44 | 45 | final_awaiter final_suspend() noexcept { return {}; } 46 | 47 | void unhandled_exception() { 48 | exception = std::current_exception(); 49 | } 50 | 51 | void return_void() {} 52 | }; 53 | 54 | std::coroutine_handle coro_handle; 55 | 56 | ~AwaitableFuture() { 57 | coro_handle.destroy(); 58 | } 59 | 60 | bool await_ready() noexcept { 61 | return coro_handle.done(); 62 | } 63 | 64 | void await_suspend(std::coroutine_handle<> handle) { 65 | coro_handle.promise().waiting = handle; 66 | } 67 | 68 | void await_resume() { 69 | promise_type& promise = coro_handle.promise(); 70 | if (promise.exception) { 71 | std::rethrow_exception(promise.exception); 72 | } 73 | } 74 | }; 75 | 76 | template 77 | struct AwaitableValue { 78 | struct promise_type { 79 | std::optional val; 80 | std::coroutine_handle<> waiting; 81 | std::exception_ptr exception; 82 | 83 | AwaitableValue get_return_object() { 84 | return { std::coroutine_handle::from_promise(*this) }; 85 | } 86 | 87 | std::suspend_never initial_suspend() { return {}; } 88 | 89 | struct final_awaiter { 90 | bool await_ready() noexcept { return false; } 91 | 92 | std::coroutine_handle<> await_suspend( 93 | std::coroutine_handle handle) noexcept { 94 | auto waiting = handle.promise().waiting; 95 | if (waiting) { 96 | return waiting; 97 | } 98 | return std::noop_coroutine(); 99 | } 100 | 101 | void await_resume() noexcept {} 102 | }; 103 | 104 | final_awaiter final_suspend() noexcept { return {}; } 105 | 106 | void unhandled_exception() { 107 | exception = std::current_exception(); 108 | } 109 | 110 | void return_value(const T& t) { 111 | val = t; 112 | } 113 | 114 | void return_value(T&& t) { 115 | val = std::move(t); 116 | } 117 | }; 118 | 119 | std::coroutine_handle coro_handle; 120 | 121 | ~AwaitableValue() { 122 | coro_handle.destroy(); 123 | } 124 | 125 | bool await_ready() noexcept { 126 | return coro_handle.done(); 127 | } 128 | 129 | void await_suspend(std::coroutine_handle<> handle) { 130 | coro_handle.promise().waiting = handle; 131 | } 132 | 133 | T&& await_resume() { 134 | promise_type& promise = coro_handle.promise(); 135 | if (promise.exception) { 136 | std::rethrow_exception(promise.exception); 137 | } 138 | return std::move(promise.val.value()); 139 | } 140 | }; 141 | } 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /src/coros/network/socket.cpp: -------------------------------------------------------------------------------- 1 | #include "socket.h" 2 | 3 | #include "coros/io/monitor.h" 4 | #include "coros/io/listener.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define SOCKET_BUFFER_SIZE 8192 13 | 14 | coros::base::Socket::Socket(SocketDetails details, IoEventMonitor& io_monitor) 15 | : details(details), io_monitor(io_monitor), 16 | stream(details.socket_fd), input_buffer(SOCKET_BUFFER_SIZE), 17 | output_buffer(SOCKET_BUFFER_SIZE), is_closed(false) { 18 | io_listener = io_monitor.register_fd(details.socket_fd); 19 | } 20 | 21 | void coros::base::Socket::read_available(std::byte*& dest, long long& size, long long& total_read) { 22 | long long buffer_remaining = input_buffer.get_total_remaining(); 23 | if (buffer_remaining == 0) { 24 | return; 25 | } 26 | long long size_to_read = std::min(size, buffer_remaining); 27 | input_buffer.read(dest, size_to_read); 28 | dest += size_to_read; 29 | size -= size_to_read; 30 | total_read += size_to_read; 31 | } 32 | 33 | coros::base::AwaitableValue coros::base::Socket::read(std::byte* dest, 34 | long long size, 35 | bool read_fully) { 36 | long long total_read = 0; 37 | read_available(dest, size, total_read); 38 | while (size > 0) { 39 | SocketOperation status = stream.recv_from_socket(input_buffer); 40 | read_available(dest, size, total_read); 41 | if (size == 0 || status == SOCKET_OP_SUCCESS) { 42 | continue; 43 | } 44 | if (status == SOCKET_OP_BLOCK) { 45 | co_await io_listener->await_read(); 46 | } else if (status == SOCKET_OP_CLOSE) { 47 | if (read_fully) { 48 | throw std::runtime_error("Socket read(): Socket has been closed"); 49 | } 50 | break; 51 | } 52 | } 53 | co_return total_read; 54 | } 55 | 56 | void coros::base::Socket::skip_available(long long& size, long long& total_skipped) { 57 | long long buffer_remaining = input_buffer.get_total_remaining(); 58 | if (buffer_remaining == 0) { 59 | return; 60 | } 61 | long long size_to_skip = std::min(size, buffer_remaining); 62 | input_buffer.increment_read_pointer(size_to_skip); 63 | size -= size_to_skip; 64 | total_skipped += size_to_skip; 65 | } 66 | 67 | coros::base::AwaitableValue coros::base::Socket::skip(long long size, bool skip_fully) { 68 | long long total_skipped = 0; 69 | skip_available(size, total_skipped); 70 | while (size > 0) { 71 | SocketOperation status = stream.recv_from_socket(input_buffer); 72 | skip_available(size, total_skipped); 73 | if (size == 0 || status == SOCKET_OP_SUCCESS) { 74 | continue; 75 | } 76 | if (status == SOCKET_OP_BLOCK) { 77 | co_await io_listener->await_read(); 78 | } else if (status == SOCKET_OP_CLOSE) { 79 | if (skip_fully) { 80 | throw std::runtime_error("Socket skip(): Socket has been closed"); 81 | } 82 | break; 83 | } 84 | } 85 | co_return total_skipped; 86 | } 87 | 88 | void coros::base::Socket::write_available(const std::byte*& src, long long& size) { 89 | long long buffer_capacity = output_buffer.get_total_capacity(); 90 | if (buffer_capacity == 0) { 91 | return; 92 | } 93 | long long size_to_write = std::min(buffer_capacity, size); 94 | output_buffer.write(src, size_to_write); 95 | src += size_to_write; 96 | size -= size_to_write; 97 | } 98 | 99 | coros::base::AwaitableFuture coros::base::Socket::write(const std::byte* src, long long size) { 100 | write_available(src, size); 101 | while (size > 0) { 102 | SocketOperation status = stream.send_to_socket(output_buffer); 103 | write_available(src, size); 104 | if (size == 0 || status == SOCKET_OP_SUCCESS) { 105 | continue; 106 | } 107 | if (status == SOCKET_OP_BLOCK) { 108 | co_await io_listener->await_write(); 109 | } else if (status == SOCKET_OP_CLOSE) { 110 | throw std::runtime_error("Socket write(): Socket has been closed"); 111 | } 112 | } 113 | } 114 | 115 | coros::base::AwaitableFuture coros::base::Socket::flush() { 116 | while (output_buffer.get_total_remaining() > 0) { 117 | SocketOperation status = stream.send_to_socket(output_buffer); 118 | if (status == SOCKET_OP_BLOCK) { 119 | co_await io_listener->await_write(); 120 | } else if (status == SOCKET_OP_CLOSE) { 121 | throw std::runtime_error("Socket flush(): Socket has been closed"); 122 | } 123 | } 124 | } 125 | 126 | int coros::base::Socket::get_fd() { 127 | return details.socket_fd; 128 | } 129 | 130 | void coros::base::Socket::close_socket() { 131 | if (is_closed.exchange(true)) { 132 | return; 133 | } 134 | stream.close(); 135 | io_listener->close(); 136 | io_monitor.remove_fd(details.socket_fd); 137 | close(details.socket_fd); 138 | } 139 | --------------------------------------------------------------------------------