Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85833312
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
34 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/job/asio-promise-handler.hpp b/src/job/asio-promise-handler.hpp
index 83626b4..6f64c5d 100644
--- a/src/job/asio-promise-handler.hpp
+++ b/src/job/asio-promise-handler.hpp
@@ -1,210 +1,210 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <boost/asio.hpp>
#include <promise-interface.hpp>
namespace Kazv
{
template<class Exec, class T>
class AsioPromise;
namespace detail
{
template<class Exec>
struct AsioPromiseHelper
{
template<class T>
using PromiseType = AsioPromise<Exec, T>;
};
struct IdentityFunc
{
template<class T>
constexpr T &&operator()(T &&t) const {
return std::forward<T>(t);
}
};
}
template<class Exec, class T>
class AsioPromise : public AbstractPromise<detail::AsioPromiseHelper<Exec>::template PromiseType, T>
{
using BaseT = AbstractPromise<detail::AsioPromiseHelper<Exec>::template PromiseType, T>;
template<class FuncT, class PromiseT, class ResolveT>
struct WaitHelper
{
void wait() const {
if (p.ready()) {
auto res = func(p.get());
using ResT = decltype(res);
if constexpr (isPromise<ResT>) {
boost::asio::post(
executor,
[w=WaitHelper<detail::IdentityFunc, ResT, ResolveT>{
executor,
res,
detail::IdentityFunc{},
resolve,
}]() mutable {
w.wait();
});
} else {
resolve(res);
}
} else {
boost::asio::post(
executor,
[*this]() mutable {
wait();
}
);
}
}
Exec executor;
PromiseT p;
FuncT func;
ResolveT resolve;
};
struct ResolveHelper
{
template<class ValT>
void operator()(ValT val) const {
using ResT = std::decay_t<decltype(val)>;
if constexpr (isPromise<ResT>) {
auto w = WaitHelper<detail::IdentityFunc, ResT, ResolveHelper>{
executor,
val,
detail::IdentityFunc{},
*this
};
w.wait();
} else {
p->set_value(std::move(val));
}
}
Exec executor;
std::shared_ptr<std::promise<T>> p;
};
public:
AsioPromise(Exec executor, T value)
: BaseT(this)
, m_executor(std::move(executor)) {
std::promise<T> p;
m_val = p.get_future().share();
p.set_value(std::move(value));
}
template<class Func>
AsioPromise(Exec executor, Func &&callback)
: BaseT(this)
, m_executor(std::move(executor)) {
auto p = std::make_shared<std::promise<T>>();
m_val = p->get_future().share();
auto resolve = ResolveHelper{m_executor, p};
boost::asio::post(
m_executor,
[=, callback=std::forward<Func>(callback),
resolve=std::move(resolve),
- guard=boost::asio::executor_work_guard(m_executor)]() {
+ guard=boost::asio::make_work_guard(m_executor)]() {
callback(resolve);
});
}
// FuncT: (DataT) -> AnotherDataT
// where AnotherDataT = PromiseThenResult<FuncT, typename BaseT::DataT>
template<class FuncT>
auto then(FuncT &&func)
-> AsioPromise<Exec,
PromiseThenResult<FuncT, typename BaseT::DataT>> {
return AsioPromise<Exec,
PromiseThenResult<FuncT, typename BaseT::DataT>>(
m_executor,
[=, func=std::forward<FuncT>(func), *this](auto resolve) {
auto waitHelper = WaitHelper<std::decay_t<FuncT>,
AsioPromise,
std::decay_t<decltype(resolve)>>{
m_executor,
*this,
func,
resolve
};
waitHelper.wait();
});
}
bool ready() const {
return m_val.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
}
T get() const {
return m_val.get();
}
private:
Exec m_executor;
std::shared_future<T> m_val;
};
template<class Exec>
class AsioPromiseHandler : public PromiseInterface<AsioPromiseHandler<Exec>,
detail::AsioPromiseHelper<Exec>::template PromiseType>
{
using BaseT = PromiseInterface<AsioPromiseHandler<Exec>,
detail::AsioPromiseHelper<Exec>::template PromiseType>;
public:
template<class T>
using PromiseT = AsioPromise<Exec, T>;
AsioPromiseHandler(Exec executor)
: BaseT(this)
, m_executor(executor)
{}
AsioPromiseHandler(const AsioPromiseHandler &that)
: BaseT(this)
, m_executor(that.m_executor)
{}
AsioPromiseHandler(AsioPromiseHandler &&that)
: BaseT(this)
, m_executor(std::move(that.m_executor))
{}
AsioPromiseHandler &operator=(const AsioPromiseHandler &that) {
m_executor = that.m_executor;
return *this;
}
AsioPromiseHandler &operator=(AsioPromiseHandler &&that) {
m_executor = std::move(that.m_executor);
return *this;
}
template<class T, class FuncT>
PromiseT<T> create(FuncT &&func) {
return PromiseT<T>(m_executor, std::forward<FuncT>(func));
}
template<class T>
PromiseT<T> createResolved(T val) {
return PromiseT<T>(m_executor, std::move(val));
}
private:
Exec m_executor;
};
template<class Exec>
AsioPromiseHandler(Exec) -> AsioPromiseHandler<Exec>;
}
diff --git a/src/job/cprjobhandler.cpp b/src/job/cprjobhandler.cpp
index ed43a34..efacb88 100644
--- a/src/job/cprjobhandler.cpp
+++ b/src/job/cprjobhandler.cpp
@@ -1,487 +1,487 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020 Tusooa Zhu
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <algorithm>
#include <deque>
#include <zug/into_vector.hpp>
#include <zug/transducer/map.hpp>
#include <cpr.h>
#include <callback.h>
#include <asio-std-file-handler.hpp>
#include "cprjobhandler.hpp"
#include "debug.hpp"
namespace {
using namespace Kazv;
struct SyncFileStream
{
using DataT = FileContent;
inline SyncFileStream(std::string filename, FileOpenMode mode)
: m_stream(filename,
(mode == FileOpenMode::Read
? std::ios_base::in
: std::ios_base::out)
| std::ios_base::binary)
{}
template<class Callback>
void read(int maxSize, Callback readCallback) {
auto buf = std::vector<char>(maxSize, '\0');
auto data = FileContent{};
auto actualSize = int{};
try {
m_stream.read(buf.data(), maxSize);
actualSize = m_stream.gcount();
data = FileContent(buf.begin(), buf.begin() + actualSize);
} catch (const std::exception &) {
readCallback(FileOpRetCode::Error, FileContent{});
return;
}
if (actualSize > 0) {
readCallback(FileOpRetCode::Success, data);
} else if (m_stream.eof()) {
readCallback(FileOpRetCode::Eof, FileContent{});
} else {
readCallback(FileOpRetCode::Error, FileContent{});
}
}
template<class Callback>
void write(DataT data, Callback writeCallback) {
for (auto c : data) {
m_stream.put(c);
if (m_stream.bad()) {
writeCallback(FileOpRetCode::Error, 0);
return;
}
}
writeCallback(FileOpRetCode::Success, data.size());
}
std::fstream m_stream;
};
struct SyncFileProvider
{
using FileStreamT = SyncFileStream;
std::string filename;
FileStreamT getStream(FileOpenMode mode) const {
return FileStreamT(filename, mode);
}
};
struct SyncFileHandler
{
using FileProviderT = SyncFileProvider;
FileProviderT getProviderFor(FileDesc desc) const {
// assert(desc.name())
return FileProviderT{desc.name().value()};
}
};
}
namespace Kazv
{
struct CprJobHandler::Private
{
CprJobHandler *q;
boost::asio::io_context::executor_type executor;
using TimerSP = std::shared_ptr<boost::asio::steady_timer>;
using TimerSPList = std::vector<TimerSP>;
using TimerMap = std::unordered_map<std::optional<std::string>, TimerSPList>;
TimerMap timerMap;
enum Status
{
Waiting,
Running,
};
using Callback = std::function<void(Response)>;
struct JobDesc
{
BaseJob job;
Callback callback;
Status status;
};
using JobQueue = std::deque<JobDesc>;
using JobMap = std::unordered_map<std::string, JobQueue>;
JobMap jobQueues;
void submitImpl(BaseJob job, std::function<void(Response)> userCallback);
void addToQueue(BaseJob job, Callback callback) {
boost::asio::post(
executor,
[=] {
// precondition: job has a queueId
auto queueId = job.queueId().value();
jobQueues[queueId].push_back(JobDesc{job, callback, Waiting});
});
}
void clearQueue(std::string queueId) {
boost::asio::post(
executor,
[=] { clearQueueImpl(queueId); });
}
void clearQueueImpl(std::string queueId) {
Response fakeResponse;
fakeResponse.statusCode = headFailureCancelledStatusCode;
fakeResponse.body = JsonBody(
json{ {"errcode", headFailureCancelledErrorCode},
{"error", headFailureCancelledErrorMsg} }
);
kzo.job.dbg() << "clearQueueImpl called with " << queueId << std::endl;
for (auto [job, callback, status] : jobQueues[queueId]) {
kzo.job.dbg() << "this job is " << (status == Waiting ? "Waiting" : "Running") << std::endl;
if (status == Waiting) {
// Run callback in a different thread, just as in submitImpl().
q->async([=] { callback(job.genResponse(fakeResponse)); } );
}
// if status is Running, the callback is already called
}
jobQueues[queueId].clear();
}
void popJob(std::string queueId) {
boost::asio::post(
executor,
[=] { popJobImpl(queueId); });
}
void popJobImpl(std::string queueId) {
if (! jobQueues[queueId].empty()) {
jobQueues[queueId].pop_front();
}
}
void monitorQueues() {
boost::asio::post(
executor,
[=] {
// precondition: job has a queueId
for (auto &[queueId, queue] : jobQueues) { // need to change queue
if (! queue.empty()) {
auto [job, callback, status] = queue.front();
if (status == Waiting) {
queue.front().status = Running;
submitImpl(
job,
[=](Response r) { // in new thread
callback(r);
if (! r.success() // should be enough for now
&& job.queuePolicy() == CancelFutureIfFailed) {
clearQueue(queueId); // in executor thread
} else {
popJob(queueId); // in executor thread
}
});
}
}
}
});
}
void addTimerToMap(TimerSP timer, std::optional<std::string> timerId) {
boost::asio::post(
executor,
[=] {
timerMap[timerId].push_back(timer);
});
}
void clearTimer(TimerSP timer, std::optional<std::string> timerId) {
boost::asio::post(
executor,
[=] {
std::remove(timerMap[timerId].begin(), timerMap[timerId].end(), timer);
});
}
void cancelAllTimers(std::optional<std::string> timerId) {
boost::asio::post(
executor,
[=] {
cancelAllTimersImpl(timerId);
});
}
void cancelAllTimersImpl(std::optional<std::string> timerId) {
auto timers = timerMap[timerId];
timerMap.erase(timerId);
for (auto timer : timers) {
timer->cancel();
}
}
void intervalTimerCallback(TimerSP timer,
std::function<void()> func,
int ms,
const boost::system::error_code &error) {
if (! error) {
func();
auto dur = boost::asio::chrono::milliseconds(ms);
timer->expires_at(timer->expiry() + dur);
timer->async_wait(
[=](const boost::system::error_code &error) {
intervalTimerCallback(timer, func, ms, error);
});
}
}
};
CprJobHandler::CprJobHandler(boost::asio::io_context::executor_type executor)
: m_d(new Private{this, std::move(executor), Private::TimerMap{}, Private::JobMap{}})
{
setInterval(
[=] {
m_d->monitorQueues();
},
50, // ms
"-queue-monitor");
}
CprJobHandler::~CprJobHandler() = default;
void CprJobHandler::async(std::function<void()> func)
{
- std::thread([func=std::move(func), guard=boost::asio::executor_work_guard(m_d->executor)]() {
+ std::thread([func=std::move(func), guard=boost::asio::make_work_guard(m_d->executor)]() {
func();
}).detach();
}
void CprJobHandler::setTimeout(std::function<void()> func, int ms, std::optional<std::string> timerId)
{
auto timer=std::make_shared<boost::asio::steady_timer>(
m_d->executor, boost::asio::chrono::milliseconds(ms));
m_d->addTimerToMap(timer, timerId);
timer->async_wait(
[=, timer=timer](const boost::system::error_code &error){
if (! error) {
func();
this->m_d->clearTimer(timer, timerId);
}
});
}
void CprJobHandler::setInterval(std::function<void()> func, int ms, std::optional<std::string> timerId)
{
auto dur = boost::asio::chrono::milliseconds(ms);
auto timer = std::make_shared<boost::asio::steady_timer>(m_d->executor, dur);
m_d->addTimerToMap(timer, timerId);
timer->async_wait(
[=](const boost::system::error_code &error) {
m_d->intervalTimerCallback(timer, func, ms, error);
});
}
void CprJobHandler::cancel(std::string timerId)
{
m_d->cancelAllTimers(timerId);
}
void CprJobHandler::submit(BaseJob job, std::function<void(Response)> userCallback)
{
if (job.queueId()) {
m_d->addToQueue(job, userCallback);
} else {
m_d->submitImpl(job, userCallback);
}
}
void CprJobHandler::Private::submitImpl(BaseJob job, std::function<void(Response)> userCallback)
{
cpr::Url url{job.url()};
auto streamUpload = std::holds_alternative<FileDesc>(job.requestBody());
cpr::Body body;
BaseJob::Header origHeader = job.requestHeader();
cpr::Header header(origHeader.get().begin(), origHeader.get().end());
auto readCallback = std::function<bool(char *, size_t &)>{};
auto writeCallback = std::function<bool(std::string)>{};
if (! std::holds_alternative<FileDesc>(job.requestBody())) {
body = std::get<BytesBody>(job.requestBody());
} else {
auto fileDesc = std::get<FileDesc>(job.requestBody());
auto typeOpt = fileDesc.contentType();
if (typeOpt) {
header.insert_or_assign("Content-Type", typeOpt.value());
kzo.job.dbg() << "Content-Type is " << header["Content-Type"] << std::endl;
}
auto fh = SyncFileHandler{};
auto provider = fileDesc.provider(fh);
auto stream = std::make_shared<FileStream>(provider.getStream());
readCallback =
[stream](char *buffer, size_t &length) -> bool {
bool retval = true;
kzo.job.dbg() << "A buffer of length " << length << " requested." << std::endl;
stream->read(length,
[&](FileOpRetCode code, FileContent data) {
if (code == FileOpRetCode::Success) {
std::copy_n(data.begin(), data.size(), buffer);
kzo.job.dbg() << "Read file successful, read " << data.size() << " bytes." << std::endl;
length = data.size();
} else if (code == FileOpRetCode::Eof) {
kzo.job.dbg() << "Got eof." << std::endl;
length = 0;
} else {
kzo.job.dbg() << "Got error reading file." << std::endl;
length = 0;
retval = false;
}
});
return retval;
};
}
if (job.responseFile()) {
auto fileDesc = job.responseFile().value();
auto fh = SyncFileHandler{};
auto provider = fileDesc.provider(fh);
auto stream = std::make_shared<FileStream>(provider.getStream(FileOpenMode::Write));
writeCallback =
[stream](std::string buffer) -> bool {
bool retval = true;
kzo.job.dbg() << "A buffer of length " << buffer.size() << " responded." << std::endl;
FileContent data(buffer.begin(), buffer.end());
stream->write(data,
[&](FileOpRetCode code, int num) {
if (code == FileOpRetCode::Success) {
kzo.job.dbg() << "Write file successful, wrote " << num << " bytes." << std::endl;
} else {
kzo.job.dbg() << "Got error writing file." << std::endl;
retval = false;
}
});
return retval;
};
}
cpr::Parameters params;
BaseJob::Query query = job.requestQuery();
BaseJob::ReturnType returnType = job.returnType();
BaseJob::Method method = job.requestMethod();
if (! query.empty()) {
// from cpr/parameters.cpp
cpr::CurlHolder holder;
for (const auto kv : query) {
std::string key = kv.first;
std::string value = kv.second;
params.Add(cpr::Parameter(std::move(key), std::move(value)));
}
}
auto callback = [returnType, responseFile=job.responseFile()](cpr::Response r) -> Response {
Body body;
if (responseFile) {
body = responseFile.value();
} else if (returnType == BaseJob::ReturnType::Json) {
try {
body = BaseJob::JsonBody(std::move(json::parse(r.text)));
} catch (const json::exception &e) {
// the response is not valid json
body = r.text;
kzo.job.dbg() << "body is not correct json: " << e.what() << std::endl;
}
} else {
body = r.text;
}
return { r.status_code,
body,
BaseJob::Header(r.header.begin(), r.header.end()),
{} // extraData, will be added in genResponse
};
};
std::shared_future<Response> res = std::visit(lager::visitor{
[=](BaseJob::Get) {
if (readCallback) {
return cpr::GetCallback(callback, url, cpr::ReadCallback(readCallback), header, params);
} else if (writeCallback) {
return cpr::GetCallback(callback, url, cpr::WriteCallback(writeCallback), header, body, params);
} else {
return cpr::GetCallback(callback, url, header, body, params);
}
},
[=](BaseJob::Post) {
if (readCallback) {
return cpr::PostCallback(callback, url, cpr::ReadCallback(readCallback), header, params);
} else if (writeCallback) {
return cpr::PostCallback(callback, url, cpr::WriteCallback(writeCallback), header, body, params);
} else {
return cpr::PostCallback(callback, url, header, body, params);
}
},
[=](BaseJob::Put) {
if (readCallback) {
return cpr::PutCallback(callback, url, cpr::ReadCallback(readCallback), header, params);
} else if (writeCallback) {
return cpr::PutCallback(callback, url, cpr::WriteCallback(writeCallback), header, body, params);
} else {
return cpr::PutCallback(callback, url, header, body, params);
}
},
[=](BaseJob::Delete) {
if (readCallback) {
return cpr::DeleteCallback(callback, url, cpr::ReadCallback(readCallback), header, params);
} else if (writeCallback) {
return cpr::DeleteCallback(callback, url, cpr::WriteCallback(writeCallback), header, body, params);
} else {
return cpr::DeleteCallback(callback, url, header, body, params);
}
}
}, method).share();
q->async([=]() {
userCallback(job.genResponse(res.get()));
});
}
void CprJobHandler::stop()
{
boost::asio::post(
m_d->executor,
[=] {
auto ids = zug::into_vector(
zug::map([](auto i) { return i.first; }),
m_d->timerMap);
for (auto id : ids) {
m_d->cancelAllTimersImpl(id);
}
m_d->jobQueues.clear();
});
}
}
diff --git a/src/tests/client/thread-safety-test.cpp b/src/tests/client/thread-safety-test.cpp
index 0471d94..cdb76ce 100644
--- a/src/tests/client/thread-safety-test.cpp
+++ b/src/tests/client/thread-safety-test.cpp
@@ -1,208 +1,208 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <catch2/catch.hpp>
#include <lager/event_loop/boost_asio.hpp>
#include <boost/asio.hpp>
#include <sdk.hpp>
#include <cprjobhandler.hpp>
#include <lagerstoreeventemitter.hpp>
#include <asio-promise-handler.hpp>
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
using namespace Kazv;
TEST_CASE("Thread-safety verification should work", "[client][thread-safety]")
{
auto io = boost::asio::io_context{};
auto jh = Kazv::CprJobHandler{io.get_executor()};
auto ee = Kazv::LagerStoreEventEmitter(lager::with_boost_asio_event_loop{io.get_executor()});
auto sdk = Kazv::makeSdk(
Kazv::SdkModel{},
jh,
ee,
Kazv::AsioPromiseHandler{io.get_executor()},
zug::identity
);
auto ctx = sdk.context();
REQUIRE(ctx.has<EventLoopThreadIdKeeper>());
auto client = sdk.client();
bool thrown = false;
try {
client.userId();
} catch (const ThreadNotMatchException &) {
thrown = true;
}
REQUIRE(! thrown); // event loop has not started yet, so do not throw
- boost::asio::executor_work_guard g(io.get_executor());
+ auto g = boost::asio::make_work_guard(io.get_executor());
std::thread([&io] { io.run(); }).detach();
// wait till the event loop thread is logged
std::this_thread::sleep_for(std::chrono::milliseconds(100));
thrown = false;
std::thread([=, &thrown] {
try {
client.userId();
} catch (const ThreadNotMatchException &) {
thrown = true;
}
}).join();
REQUIRE(thrown);
ctx.createResolvedPromise({})
.then([](auto &&) {})
.then([&client](auto &&) {
client.userId();
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
thrown = false;
try {
client.userId();
} catch (const ThreadNotMatchException &) {
thrown = true;
}
REQUIRE(thrown);
}
TEST_CASE("Thread-safety verification from secondary root should work", "[client][thread-safety]")
{
auto io = boost::asio::io_context{};
auto jh = Kazv::CprJobHandler{io.get_executor()};
auto ee = Kazv::LagerStoreEventEmitter(lager::with_boost_asio_event_loop{io.get_executor()});
auto sdk = Kazv::makeSdk(
Kazv::SdkModel{},
jh,
ee,
Kazv::AsioPromiseHandler{io.get_executor()},
zug::identity
);
auto ctx = sdk.context();
- boost::asio::executor_work_guard guard(io.get_executor());
+ auto guard = boost::asio::make_work_guard(io.get_executor());
std::thread([&io] { io.run(); }).detach();
auto io2 = boost::asio::io_context{};
auto sr = sdk.createSecondaryRoot(lager::with_boost_asio_event_loop{io2.get_executor()});
auto client2 = sdk.clientFromSecondaryRoot(sr);
bool thrown = false;
std::thread([client2, &thrown] {
try {
client2.userId();
} catch(const ThreadNotMatchException &) {
thrown = true;
}
}).join();
REQUIRE(thrown);
thrown = false;
std::thread([&client2, &thrown] {
try {
client2.userId();
} catch(const ThreadNotMatchException &) {
thrown = true;
}
}).join();
REQUIRE(thrown);
io2.run();
}
TEST_CASE("Thread-safety verification should properly verify toEventLoop calls", "[client][thread-safety]")
{
auto io = boost::asio::io_context{};
auto jh = Kazv::CprJobHandler{io.get_executor()};
auto ee = Kazv::LagerStoreEventEmitter(lager::with_boost_asio_event_loop{io.get_executor()});
auto sdk = Kazv::makeSdk(
Kazv::SdkModel{},
jh,
ee,
Kazv::AsioPromiseHandler{io.get_executor()},
zug::identity
);
auto ctx = sdk.context();
auto io2 = boost::asio::io_context{};
auto sr = sdk.createSecondaryRoot(lager::with_boost_asio_event_loop{io2.get_executor()});
auto client2 = sdk.clientFromSecondaryRoot(sr);
auto room2 = client2.room("!foo:example.org");
ctx.createResolvedPromise({})
- .then([client2, room2, guard=boost::asio::executor_work_guard(io.get_executor())](auto &&) {
+ .then([client2, room2, guard=boost::asio::make_work_guard(io.get_executor())](auto &&) {
bool thrown = false;
try {
client2.userId();
} catch (const ThreadNotMatchException &) {
thrown = true;
}
REQUIRE(thrown);
thrown = false;
try {
room2.roomId();
} catch (const ThreadNotMatchException &) {
thrown = true;
}
REQUIRE(thrown);
});
ctx.createResolvedPromise({})
.then([client2=client2.toEventLoop(), room2=room2.toEventLoop(),
- guard=boost::asio::executor_work_guard(io.get_executor())](auto &&) {
+ guard=boost::asio::make_work_guard(io.get_executor())](auto &&) {
bool thrown2 = false;
try {
client2.userId();
} catch (const ThreadNotMatchException &) {
thrown2 = true;
}
REQUIRE(!thrown2);
thrown2 = false;
try {
room2.roomId();
} catch (const ThreadNotMatchException &) {
thrown2 = true;
}
REQUIRE(!thrown2);
});
std::thread([&io] { io.run(); }).detach();
io2.run();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
#endif
diff --git a/src/tests/event-emitter-test.cpp b/src/tests/event-emitter-test.cpp
index 3d16bfd..89b742b 100644
--- a/src/tests/event-emitter-test.cpp
+++ b/src/tests/event-emitter-test.cpp
@@ -1,83 +1,83 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <chrono>
#include <catch2/catch.hpp>
#include <eventemitter/lagerstoreeventemitter.hpp>
#include <lager/event_loop/boost_asio.hpp>
using namespace Kazv;
TEST_CASE("Event emitter should work normally", "[eventemitter]")
{
boost::asio::io_context ioContext;
LagerStoreEventEmitter ee{lager::with_boost_asio_event_loop{ioContext.get_executor()}};
auto watchable = ee.watchable();
int counter = 0;
watchable.after<SyncSuccessful>(
[&](auto) {
++counter;
});
SECTION("Event handlers should be run properly") {
ee.emit(SyncSuccessful{});
ee.emit(SyncFailed{});
ioContext.run();
REQUIRE(counter == 1);
}
SECTION("Event handlers should be called when every event fires off") {
for (int i = 0; i < 100; ++i) {
ee.emit(SyncSuccessful{});
}
ioContext.run();
REQUIRE(counter == 100);
}
SECTION("Handler should be disconnected once watchable is destroyed") {
int counter2 = 0;
- auto guard = boost::asio::executor_work_guard(ioContext.get_executor());
+ auto guard = boost::asio::make_work_guard(ioContext.get_executor());
auto thread = std::thread([&] { ioContext.run(); });
ee.emit(SyncFailed{});
std::this_thread::sleep_for(std::chrono::milliseconds{100});
{
auto watchable2 = ee.watchable();
watchable2.after<SyncFailed>(
[&](auto) {
++counter2;
});
ee.emit(SyncSuccessful{});
ee.emit(SyncFailed{});
std::this_thread::sleep_for(std::chrono::milliseconds{100});
}
ee.emit(SyncFailed{});
guard.reset();
thread.join();
REQUIRE(counter == 1);
REQUIRE(counter2 == 1);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 2, 10:31 AM (2 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1804515
Default Alt Text
(34 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline