Commit 497ad343 authored by Dominik Charousset's avatar Dominik Charousset

Use stateful brokers in CURL example

parent a77b9138
...@@ -97,180 +97,171 @@ constexpr int min_req_interval = 10; ...@@ -97,180 +97,171 @@ constexpr int min_req_interval = 10;
// maximum delay between HTTP requests // maximum delay between HTTP requests
constexpr int max_req_interval = 300; constexpr int max_req_interval = 300;
// provides print utility and each base_actor has a parent // put everything into anonymous namespace (except main)
class base_actor : public event_based_actor { namespace {
protected:
base_actor(actor parent, std::string name, std::string color_code) // provides print utility, a name, and a parent
: parent_(std::move(parent)), struct base_state {
name_(std::move(name)), base_state(local_actor* thisptr) : self(thisptr) {
color_(std::move(color_code)),
out_(this) {
// nop // nop
} }
~base_actor(); actor_ostream print() {
return aout(self) << color << name << " (id = " << self->id() << "): ";
inline actor_ostream& print() {
return out_ << color_ << name_ << " (id = " << id() << "): ";
} }
void on_exit() { virtual void init(actor m_parent, std::string m_name, std::string m_color) {
print() << "on_exit" << color::reset_endl; parent = std::move(m_parent);
name = std::move(m_name);
color = std::move(m_color);
print() << "started" << color::reset_endl;
} }
actor parent_; virtual ~base_state() {
print() << "done" << color::reset_endl;
}
private: local_actor* self;
std::string name_; actor parent;
std::string color_; std::string name;
actor_ostream out_; std::string color;
}; };
base_actor::~base_actor() {
// avoid weak-vtables warning
}
// encapsulates an HTTP request // encapsulates an HTTP request
class client_job : public base_actor { behavior client_job(stateful_actor<base_state>* self, actor parent) {
public: self->state.init(std::move(parent), "client-job", color::blue);
explicit client_job(actor parent) self->send(self->state.parent, read_atom::value,
: base_actor(std::move(parent), "client_job", color::blue) { "http://www.example.com/index.html",
// nop
}
~client_job();
protected:
behavior make_behavior() override {
print() << "init" << color::reset_endl;
send(parent_, read_atom::value, "http://www.example.com/index.html",
uint64_t{0}, uint64_t{4095}); uint64_t{0}, uint64_t{4095});
return { return {
[=](reply_atom, const buffer_type& buf) { [=](reply_atom, const buffer_type& buf) {
print() << "successfully received " << buf.size() << " bytes" self->state.print() << "successfully received " << buf.size() << " bytes"
<< color::reset_endl; << color::reset_endl;
quit(); self->quit();
}, },
[=](fail_atom) { [=](fail_atom) {
print() << "failure" << color::reset_endl; self->state.print() << "failure" << color::reset_endl;
quit(); self->quit();
} }
}; };
}
};
client_job::~client_job() {
// avoid weak-vtables warning
} }
// spawns HTTP requests struct client_state : base_state {
class client : public base_actor { client_state(local_actor* self)
public: : base_state(self),
count(0),
explicit client(const actor& parent) re(rd()),
: base_actor(parent, "client", color::green), dist(min_req_interval, max_req_interval) {
count_(0),
re_(rd_()),
dist_(min_req_interval, max_req_interval) {
// nop // nop
} }
~client(); size_t count;
std::random_device rd;
std::default_random_engine re;
std::uniform_int_distribution<int> dist;
};
protected: // spawns HTTP requests
behavior make_behavior() override { behavior client(stateful_actor<client_state>* self, actor parent) {
using std::chrono::milliseconds; using std::chrono::milliseconds;
link_to(parent_); self->link_to(parent);
print() << "init" << color::reset_endl; self->state.init(std::move(parent), "client", color::green);
// start 'loop' self->send(self, next_atom::value);
send(this, next_atom::value); return {
return (
[=](next_atom) { [=](next_atom) {
print() << "spawn new client_job (nr. " auto& st = self->state;
<< ++count_ st.print() << "spawn new client_job (nr. " << ++st.count << ")"
<< ")"
<< color::reset_endl; << color::reset_endl;
// client_job will use IO // client_job will use IO
// and should thus be spawned in a separate thread // and should thus be spawned in a separate thread
spawn<client_job, detached+linked>(parent_); self->spawn<detached+linked>(client_job, st.parent);
// compute random delay until next job is launched // compute random delay until next job is launched
auto delay = dist_(re_); auto delay = st.dist(st.re);
delayed_send(this, milliseconds(delay), next_atom::value); self->delayed_send(self, milliseconds(delay), next_atom::value);
} }
); };
}
struct curl_state : base_state {
curl_state(local_actor* self) : base_state(self) {
// nop
} }
private: ~curl_state() {
size_t count_; if (curl)
std::random_device rd_; curl_easy_cleanup(curl);
std::default_random_engine re_; }
std::uniform_int_distribution<int> dist_;
};
client::~client() { static size_t callback(void* data, size_t bsize, size_t nmemb, void* userp) {
// avoid weak-vtables warning size_t size = bsize * nmemb;
} auto& buf = reinterpret_cast<curl_state*>(userp)->buf;
auto first = reinterpret_cast<char*>(data);
auto last = first + bsize;
buf.insert(buf.end(), first, last);
return size;
}
// manages a CURL session void init(actor m_parent, std::string m_name, std::string m_color) override {
class curl_worker : public base_actor { curl = curl_easy_init();
public: if (! curl)
explicit curl_worker(const actor& parent) throw std::runtime_error("Unable initialize CURL.");
: base_actor(parent, "curl_worker", color::yellow), curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_state::callback);
curl_(nullptr) { curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
// nop base_state::init(std::move(m_parent), std::move(m_name),
std::move(m_color));
} }
~curl_worker(); CURL* curl = nullptr;
buffer_type buf;
};
protected: // manages a CURL session
behavior make_behavior() override { behavior curl_worker(stateful_actor<curl_state>* self, actor parent) {
print() << "init" << color::reset_endl; self->state.init(std::move(parent), "curl-worker", color::yellow);
curl_ = curl_easy_init(); return {
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, &curl_worker::cb);
curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1);
return (
[=](read_atom, const std::string& fname, uint64_t offset, uint64_t range) [=](read_atom, const std::string& fname, uint64_t offset, uint64_t range)
-> message { -> message {
print() << "read" << color::reset_endl; auto& st = self->state;
st.print() << "read" << color::reset_endl;
for (;;) { for (;;) {
buf_.clear(); st.buf.clear();
// set URL // set URL
curl_easy_setopt(curl_, CURLOPT_URL, fname.c_str()); curl_easy_setopt(st.curl, CURLOPT_URL, fname.c_str());
// set range // set range
std::ostringstream oss; std::ostringstream oss;
oss << offset << "-" << range; oss << offset << "-" << range;
curl_easy_setopt(curl_, CURLOPT_RANGE, oss.str().c_str()); curl_easy_setopt(st.curl, CURLOPT_RANGE, oss.str().c_str());
// set curl callback // set curl callback
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, curl_easy_setopt(st.curl, CURLOPT_WRITEDATA,
reinterpret_cast<void*>(this)); reinterpret_cast<void*>(&st));
// launch file transfer // launch file transfer
auto res = curl_easy_perform(curl_); auto res = curl_easy_perform(st.curl);
if (res != CURLE_OK) { if (res != CURLE_OK) {
print() << "curl_easy_perform() failed: " st.print() << "curl_easy_perform() failed: "
<< curl_easy_strerror(res) << curl_easy_strerror(res)
<< color::reset_endl; << color::reset_endl;
} else { } else {
long hc = 0; // http return code long hc = 0; // http return code
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &hc); curl_easy_getinfo(st.curl, CURLINFO_RESPONSE_CODE, &hc);
switch (hc) { switch (hc) {
default: default:
print() << "http error: download failed with " st.print() << "http error: download failed with "
<< "'HTTP RETURN CODE': " << "'HTTP RETURN CODE': "
<< hc << hc
<< color::reset_endl; << color::reset_endl;
break; break;
case 200: // ok case 200: // ok
case 206: // partial content case 206: // partial content
print() << "received " st.print() << "received "
<< buf_.size() << st.buf.size()
<< " bytes with 'HTTP RETURN CODE': " << " bytes with 'HTTP RETURN CODE': "
<< hc << hc
<< color::reset_endl; << color::reset_endl;
// tell parent that this worker is done // tell parent that this worker is done
send(parent_, finished_atom::value); self->send(st.parent, finished_atom::value);
return make_message(reply_atom::value, buf_); return make_message(reply_atom::value, std::move(st.buf));
case 404: // file does not exist case 404: // file does not exist
print() << "http error: download failed with " st.print() << "http error: download failed with "
<< "'HTTP RETURN CODE': 404 (file does " << "'HTTP RETURN CODE': 404 (file does "
<< "not exist!)" << "not exist!)"
<< color::reset_endl; << color::reset_endl;
...@@ -280,77 +271,51 @@ protected: ...@@ -280,77 +271,51 @@ protected:
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} }
} }
); };
}
void on_exit() {
curl_easy_cleanup(curl_);
print() << "on_exit" << color::reset_endl;
}
private:
static size_t cb(void* data, size_t bsize, size_t nmemb, void* userp) {
size_t size = bsize * nmemb;
auto& buf = reinterpret_cast<curl_worker*>(userp)->buf_;
auto first = reinterpret_cast<char*>(data);
auto last = first + bsize;
buf.insert(buf.end(), first, last);
return size;
}
CURL* curl_;
buffer_type buf_;
};
curl_worker::~curl_worker() {
// avoid weak-vtables warning
} }
// manages {num_curl_workers} workers with a round-robin protocol struct master_state : base_state {
class curl_master : public base_actor { master_state(local_actor* self) : base_state(self) {
public:
curl_master() : base_actor(invalid_actor, "curl_master", color::magenta) {
// nop // nop
} }
std::vector<actor> idle;
std::vector<actor> busy;
};
~curl_master(); behavior curl_master(stateful_actor<master_state>* self) {
self->state.init(invalid_actor, "curl-master", color::magenta);
protected:
behavior make_behavior() override {
print() << "init" << color::reset_endl;
// spawn workers // spawn workers
for(size_t i = 0; i < num_curl_workers; ++i) { for(size_t i = 0; i < num_curl_workers; ++i)
idle_worker_.push_back(spawn<curl_worker, detached+linked>(this)); self->state.idle.push_back(self->spawn<detached+linked>(curl_worker, self));
}
auto worker_finished = [=] { auto worker_finished = [=] {
auto sender = current_sender(); auto sender = self->current_sender();
auto last = busy_worker_.end(); auto last = self->state.busy.end();
auto i = std::find(busy_worker_.begin(), last, sender); auto i = std::find(self->state.busy.begin(), last, sender);
if (i == last) { if (i == last)
return; return;
} self->state.idle.push_back(*i);
idle_worker_.push_back(*i); self->state.busy.erase(i);
busy_worker_.erase(i); self->state.print() << "worker is done" << color::reset_endl;
print() << "worker is done" << color::reset_endl;
}; };
print() << "spawned " << idle_worker_.size() self->state.print() << "spawned " << self->state.idle.size()
<< " worker(s)" << color::reset_endl; << " worker(s)" << color::reset_endl;
return { return {
[=](read_atom, const std::string&, uint64_t, uint64_t) { [=](read_atom, const std::string&, uint64_t, uint64_t) {
print() << "received {'read'}" << color::reset_endl; auto& st = self->state;
st.print() << "received {'read'}" << color::reset_endl;
// forward job to an idle worker // forward job to an idle worker
actor worker = idle_worker_.back(); actor worker = st.idle.back();
idle_worker_.pop_back(); st.idle.pop_back();
busy_worker_.push_back(worker); st.busy.push_back(worker);
forward_to(worker); self->forward_to(worker);
print() << busy_worker_.size() << " active jobs" << color::reset_endl; st.print() << st.busy.size() << " active jobs" << color::reset_endl;
if (idle_worker_.empty()) { if (st.idle.empty()) {
// wait until at least one worker finished its job // wait until at least one worker finished its job
become ( self->become (
keep_behavior, keep_behavior,
[=](finished_atom) { [=](finished_atom) {
worker_finished(); worker_finished();
unbecome(); self->unbecome();
} }
); );
} }
...@@ -359,20 +324,11 @@ protected: ...@@ -359,20 +324,11 @@ protected:
worker_finished(); worker_finished();
} }
}; };
}
private:
std::vector<actor> idle_worker_;
std::vector<actor> busy_worker_;
};
curl_master::~curl_master() {
// avoid weak-vtables warning
} }
// signal handling for ctrl+c // signal handling for ctrl+c
namespace {
std::atomic<bool> shutdown_flag{false}; std::atomic<bool> shutdown_flag{false};
} // namespace <anonymous> } // namespace <anonymous>
int main() { int main() {
...@@ -392,12 +348,11 @@ int main() { ...@@ -392,12 +348,11 @@ int main() {
{ // lifetime scope of self { // lifetime scope of self
scoped_actor self; scoped_actor self;
// spawn client and curl_master // spawn client and curl_master
auto master = self->spawn<curl_master, detached>(); auto master = self->spawn<detached>(curl_master);
self->spawn<client, detached>(master); self->spawn<detached>(client, master);
// poll CTRL+C flag every second // poll CTRL+C flag every second
while (! shutdown_flag) { while (! shutdown_flag)
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
}
aout(self) << color::cyan << "received CTRL+C" << color::reset_endl; aout(self) << color::cyan << "received CTRL+C" << color::reset_endl;
// shutdown actors // shutdown actors
anon_send_exit(master, exit_reason::user_shutdown); anon_send_exit(master, exit_reason::user_shutdown);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment