mirror of https://github.com/RainMark/cops.git
10 changed files with 420 additions and 5 deletions
@ -1,3 +1,13 @@ |
|||
#!/bin/bash |
|||
|
|||
g++ -g -std=c++17 -I. *.cpp *.S -o cops |
|||
CXX="/usr/local/gcc-10/bin/g++-10" |
|||
FLAGS="-g -std=c++17 -fno-sized-deallocation -D_GLIBCXX_USE_CXX11_ABI=0 -I." |
|||
|
|||
mkdir -p bin |
|||
|
|||
$CXX $FLAGS switch_test.cpp coroutine.cpp *.S -o bin/switch_test |
|||
|
|||
$CXX $FLAGS future_test.cpp -o bin/future_test |
|||
|
|||
$CXX $FLAGS -O0 loop_test.cpp coroutine.cpp *.S -o bin/loop_test |
|||
|
|||
|
|||
@ -0,0 +1,227 @@ |
|||
#pragma once |
|||
|
|||
#include <deque> |
|||
#include <memory> |
|||
#include <string> |
|||
|
|||
#include <assert.h> |
|||
#include <unistd.h> |
|||
#include <sys/eventfd.h> |
|||
#include <sys/epoll.h> |
|||
#include <sys/socket.h> |
|||
#include <netinet/in.h> |
|||
#include <arpa/inet.h> |
|||
|
|||
#include "coroutine.h" |
|||
|
|||
namespace cops { |
|||
|
|||
class task_t { |
|||
public: |
|||
virtual ~task_t() = default; |
|||
virtual void run() = 0; |
|||
}; |
|||
|
|||
template <class T> |
|||
class lambda_task_t : public task_t { |
|||
public: |
|||
explicit lambda_task_t(T&& task) : task_(std::forward<T>(task)) {} |
|||
void run() override { |
|||
task_(); |
|||
} |
|||
private: |
|||
T task_; |
|||
}; |
|||
|
|||
template <class T> |
|||
inline std::unique_ptr<task_t> make_lambda_task(T&& lambda) { |
|||
return std::unique_ptr<task_t>(new lambda_task_t(std::forward<T>(lambda))); |
|||
} |
|||
|
|||
inline void oops(const std::string& s) { |
|||
perror(s.data()); |
|||
exit(1); |
|||
} |
|||
|
|||
class event_loop_t { |
|||
public: |
|||
struct epoll_data_t { |
|||
int fd; |
|||
std::unique_ptr<task_t> task; |
|||
}; |
|||
static constexpr int kMaxEpollEvents = 128; |
|||
|
|||
public: |
|||
event_loop_t() = default; |
|||
~event_loop_t() { |
|||
if (!closed_) { |
|||
stop(); |
|||
} |
|||
} |
|||
|
|||
template <class Fn> |
|||
void call_soon(Fn&& fn) { |
|||
task_queue_.push_back(make_lambda_task(std::forward<Fn>(fn)).release()); |
|||
} |
|||
|
|||
template <class Fn> |
|||
void create_task(Fn&& fn) { |
|||
auto coro = make_coro(std::forward<Fn>(fn)); |
|||
call_soon([coro = coro.get()]() { coro->switch_in(); }); |
|||
coro->detach(coro, this); |
|||
assert(coro.get() == nullptr); |
|||
} |
|||
|
|||
int create_server(const std::string& host, int16_t port) { |
|||
int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); |
|||
if (sockfd == 0) { |
|||
oops("socket() " + std::to_string(errno)); |
|||
} |
|||
int opt = 1; |
|||
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { |
|||
oops("setsockopt() " + std::to_string(errno)); |
|||
} |
|||
struct sockaddr_in sa; |
|||
sa.sin_family = AF_INET; |
|||
inet_pton(AF_INET, host.data(), &(sa.sin_addr)); |
|||
sa.sin_port = htons(port); |
|||
if (bind(sockfd, (struct sockaddr*)&sa, sizeof(sa)) < 0) { |
|||
oops("bind() " + std::to_string(errno)); |
|||
} |
|||
int backlog = 4096; |
|||
if (listen(sockfd, backlog) < 0) { |
|||
oops("listen() " + std::to_string(errno)); |
|||
} |
|||
return sockfd; |
|||
} |
|||
|
|||
int sock_accept(int sockfd) { |
|||
struct sockaddr_in sa; |
|||
socklen_t len = sizeof(sa); |
|||
int fd = accept4(sockfd, (struct sockaddr*)&sa, (socklen_t*)&len, SOCK_NONBLOCK); |
|||
// TODO: return std::tuple<fd, sa>
|
|||
if (fd >= 0) { |
|||
return fd; |
|||
} |
|||
if (errno == EAGAIN || errno == EWOULDBLOCK) { |
|||
auto curr = current; |
|||
file_desc_add(sockfd, EPOLLIN, [curr]() { curr->switch_in(); }); |
|||
curr->switch_out(); |
|||
} |
|||
return accept4(sockfd, (struct sockaddr*)&sa, (socklen_t*)&len, SOCK_NONBLOCK); |
|||
} |
|||
|
|||
ssize_t sock_recv(int sockfd, void* buf, size_t len, int flags) { |
|||
ssize_t n = recv(sockfd, buf, len, flags); |
|||
if (n >= 0) { |
|||
return n; |
|||
} |
|||
if (errno == EAGAIN || errno == EWOULDBLOCK) { |
|||
auto curr = current; |
|||
file_desc_add(sockfd, EPOLLIN, [curr]() { curr->switch_in(); }); |
|||
curr->switch_out(); |
|||
} |
|||
return recv(sockfd, buf, len, flags); |
|||
} |
|||
|
|||
ssize_t sock_send(int sockfd, void* buf, size_t len, int flags) { |
|||
ssize_t n = send(sockfd, buf, len, flags); |
|||
if (n >= 0) { |
|||
return n; |
|||
} |
|||
if (errno == EAGAIN || errno == EWOULDBLOCK) { |
|||
auto curr = current; |
|||
file_desc_add(sockfd, EPOLLOUT, [curr]() { curr->switch_in(); }); |
|||
curr->switch_out(); |
|||
} |
|||
return send(sockfd, buf, len, flags); |
|||
} |
|||
|
|||
public: |
|||
void run_forever() { |
|||
epollfd_ = epoll_create(kMaxEpollEvents); |
|||
if (epollfd_ < 0) { |
|||
oops("epoll_create() " + std::to_string(errno)); |
|||
} |
|||
eventfd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); |
|||
if (eventfd_ < 0) { |
|||
oops("eventfd() " + std::to_string(errno)); |
|||
} |
|||
struct epoll_event ev; |
|||
ev.events = EPOLLIN; |
|||
ev.data.ptr = nullptr; |
|||
if (epoll_ctl(epollfd_, EPOLL_CTL_ADD, eventfd_, &ev) < 0) { |
|||
oops("epoll_ctl() " + std::to_string(errno)); |
|||
} |
|||
|
|||
closed_ = false; |
|||
while (!closed_) { |
|||
int timeout = task_queue_.empty() ? -1 : 0; |
|||
struct epoll_event events[kMaxEpollEvents]; |
|||
int n = epoll_wait(epollfd_, events, kMaxEpollEvents, timeout); |
|||
if (n < 0) { |
|||
oops("epoll_wait() " + std::to_string(errno)); |
|||
} |
|||
for (int i = 0; i < n; ++i) { |
|||
auto data = static_cast<epoll_data_t*>(events[i].data.ptr); |
|||
// response wakeup
|
|||
if (!data) { |
|||
static char _unused[8]; |
|||
read(eventfd_, _unused, 8); |
|||
continue; |
|||
} |
|||
// invoke io callback
|
|||
file_desc_del(data->fd); |
|||
data->task->run(); |
|||
delete data; |
|||
} |
|||
// schedule normal task
|
|||
if (!task_queue_.empty()) { |
|||
std::unique_ptr<task_t> task(task_queue_.front()); |
|||
task_queue_.pop_front(); |
|||
task->run(); |
|||
} |
|||
} |
|||
file_desc_del(eventfd_); |
|||
close(eventfd_); |
|||
close(epollfd_); |
|||
eventfd_ = -1; |
|||
epollfd_ = -1; |
|||
} |
|||
void stop() { |
|||
closed_ = true; |
|||
} |
|||
|
|||
private: |
|||
void wakeup() { |
|||
if (eventfd_ > 0) { |
|||
static uint64_t one = 1; |
|||
write(eventfd_, &one, sizeof(one)); |
|||
} |
|||
} |
|||
|
|||
template <class Callback> |
|||
void file_desc_add(int fd, int flags, Callback callback) { |
|||
struct epoll_event ev; |
|||
ev.events = flags; |
|||
auto task = make_lambda_task(std::forward<Callback>(callback)); |
|||
ev.data.ptr = new epoll_data_t{fd, std::move(task)}; |
|||
if (epoll_ctl(epollfd_, EPOLL_CTL_ADD, fd, &ev) < 0) { |
|||
oops("epoll_ctl() " + std::to_string(errno)); |
|||
} |
|||
} |
|||
|
|||
void file_desc_del(int fd) { |
|||
if (epoll_ctl(epollfd_, EPOLL_CTL_DEL, fd, nullptr) < 0) { |
|||
oops("epoll_ctl() " + std::to_string(errno)); |
|||
} |
|||
} |
|||
|
|||
private: |
|||
bool closed_ = true; |
|||
int epollfd_ = -1; |
|||
int eventfd_ = -1; |
|||
std::deque<task_t*> task_queue_; |
|||
}; |
|||
|
|||
} // cops
|
|||
@ -0,0 +1,82 @@ |
|||
#pragma once |
|||
|
|||
#include <atomic> |
|||
#include <memory> |
|||
#include <functional> |
|||
|
|||
namespace cops { |
|||
|
|||
template <class T> |
|||
class future_t { |
|||
public: |
|||
enum class status_t { |
|||
kInit = 0, |
|||
kHasValue, |
|||
kHasCallback, |
|||
kDone, |
|||
}; |
|||
|
|||
using Fn = std::function<void(void)>; |
|||
|
|||
public: |
|||
future_t() = default; |
|||
~future_t() = default; |
|||
|
|||
void set_value(T&& value) { |
|||
auto s = status_.load(std::memory_order_acquire); |
|||
if (s == status_t::kHasValue || s == status_t::kDone) { |
|||
return; |
|||
} |
|||
value_ = std::move(value); |
|||
if (s == status_t::kHasCallback) { |
|||
invoke(); |
|||
return; |
|||
} |
|||
s = status_.exchange(status_t::kHasValue, std::memory_order_acq_rel); |
|||
// double check, maybe exchange after set_callback
|
|||
if (s == status_t::kHasCallback) { |
|||
invoke(); |
|||
} |
|||
} |
|||
|
|||
T&& value() { |
|||
return std::move(value_); |
|||
} |
|||
|
|||
bool has_value() { |
|||
return status_t::kHasValue == status_.load(std::memory_order_acquire); |
|||
} |
|||
|
|||
void set_callback(Fn&& fn) { |
|||
auto s = status_.load(std::memory_order_acquire); |
|||
if (s == status_t::kHasCallback || s == status_t::kDone) { |
|||
return; |
|||
} |
|||
callback_ = std::forward<Fn>(fn); |
|||
if (s == status_t::kHasValue) { |
|||
invoke(); |
|||
return; |
|||
} |
|||
s = status_.exchange(status_t::kHasCallback, std::memory_order_acq_rel); |
|||
// double check, maybe exchange after set_value
|
|||
if (s == status_t::kHasValue) { |
|||
invoke(); |
|||
} |
|||
} |
|||
|
|||
private: |
|||
void set_done() { |
|||
status_.store(status_t::kDone, std::memory_order_release); |
|||
} |
|||
void invoke() { |
|||
set_done(); |
|||
callback_(); |
|||
} |
|||
|
|||
private: |
|||
T value_; |
|||
std::atomic<status_t> status_ = status_t::kInit; |
|||
Fn callback_; |
|||
}; |
|||
|
|||
} // cops
|
|||
@ -0,0 +1,25 @@ |
|||
#include <iostream> |
|||
#include "future.h" |
|||
|
|||
int main() { |
|||
auto future = std::make_shared<cops::future_t<int>>(); |
|||
std::cout << future->has_value() << std::endl; |
|||
future->set_value(100); |
|||
std::cout << future->has_value() << std::endl; |
|||
future->set_callback([]() { |
|||
std::cout << "callback" << std::endl; |
|||
}); |
|||
future->set_value(101); |
|||
future->set_callback([]() { |
|||
std::cout << "callback" << std::endl; |
|||
}); |
|||
std::cout << future->value() << std::endl; |
|||
|
|||
auto future2 = std::make_shared<cops::future_t<float>>(); |
|||
future2->set_callback([]() { |
|||
std::cout << "callback2" << std::endl; |
|||
}); |
|||
future2->set_value(10.1); |
|||
std::cout << future2->value() << std::endl; |
|||
return 0; |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
#include <iostream> |
|||
#include "coroutine.h" |
|||
#include "event_loop.h" |
|||
|
|||
int main() { |
|||
auto loop = std::make_unique<cops::event_loop_t>(); |
|||
loop->call_soon([&loop]() { |
|||
std::cout << "task1" << std::endl; |
|||
loop->call_soon([&loop]() { |
|||
std::cout << "task2" << std::endl; |
|||
}); |
|||
}); |
|||
|
|||
loop->create_task([&loop]() { |
|||
int server = loop->create_server("127.0.0.1", 9999); |
|||
std::cout << "create server " << server << std::endl; |
|||
while (1) { |
|||
int conn = loop->sock_accept(server); |
|||
std::cout << "server " << server << " new conn " << conn << std::endl; |
|||
loop->create_task([&loop, conn]() { |
|||
char buf[128]; |
|||
ssize_t n = loop->sock_recv(conn, buf, 128, 0); |
|||
std::cout << buf << std::endl; |
|||
n = loop->sock_send(conn, buf, n, 0); |
|||
close(conn); |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
loop->create_task([&loop]() { |
|||
int server = loop->create_server("127.0.0.1", 9998); |
|||
std::cout << "create server " << server << std::endl; |
|||
while (1) { |
|||
int conn = loop->sock_accept(server); |
|||
std::cout << "server " << server << " new conn " << conn << std::endl; |
|||
loop->create_task([&loop, conn]() { |
|||
char buf[128]; |
|||
ssize_t n = loop->sock_recv(conn, buf, 128, 0); |
|||
std::cout << buf << std::endl; |
|||
n = loop->sock_send(conn, buf, n, 0); |
|||
close(conn); |
|||
}); |
|||
} |
|||
}); |
|||
loop->run_forever(); |
|||
return 0; |
|||
} |
|||
@ -1,5 +1,5 @@ |
|||
#include <iostream> |
|||
#include <context.h> |
|||
#include "coroutine.h" |
|||
|
|||
int main() { |
|||
std::unique_ptr<cops::coro_t> coro; |
|||
@ -0,0 +1,11 @@ |
|||
#!/usr/bin/env python3 |
|||
|
|||
import sys |
|||
import socket |
|||
|
|||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: |
|||
s.connect((sys.argv[1], int(sys.argv[2]))) |
|||
s.sendall(b'hello') |
|||
data = s.recv(1024) |
|||
print(data) |
|||
|
|||
Loading…
Reference in new issue