fix async redefined
All checks were successful
linux-x64-gcc / linux-gcc (Debug) (push) Successful in 1m40s
linux-x64-gcc / linux-gcc (Release) (push) Successful in 49s

This commit is contained in:
tqcq 2024-03-29 10:26:51 +08:00
parent 0c6c0cfbd1
commit cf8ee4a243
6 changed files with 245 additions and 239 deletions

View File

@ -19,7 +19,7 @@
// THE SOFTWARE. // THE SOFTWARE.
#ifndef ASYNCXX_H_ #ifndef ASYNCXX_H_
# error "Do not include this header directly, include <async++.h> instead." #error "Do not include this header directly, include <async++.h> instead."
#endif #endif
namespace async { namespace async {
@ -31,54 +31,51 @@ LIBASYNC_EXPORT std::size_t hardware_concurrency() LIBASYNC_NOEXCEPT;
// Task handle used by a wait handler // Task handle used by a wait handler
class task_wait_handle { class task_wait_handle {
detail::task_base* handle; detail::task_base *handle;
// Allow construction in wait_for_task() // Allow construction in wait_for_task()
friend LIBASYNC_EXPORT void detail::wait_for_task(detail::task_base* t); friend void detail::wait_for_task(detail::task_base *t);
task_wait_handle(detail::task_base* t) friend void detail::generic_wait_for_task(detail::task_base *wait_task);
: handle(t) {}
// Execution function for use by wait handlers task_wait_handle(detail::task_base *t) : handle(t) {}
template<typename Func>
struct wait_exec_func: private detail::func_base<Func> { // Execution function for use by wait handlers
template<typename F> template<typename Func>
explicit wait_exec_func(F&& f) struct wait_exec_func : private detail::func_base<Func> {
: detail::func_base<Func>(std::forward<F>(f)) {} template<typename F>
void operator()(detail::task_base*) explicit wait_exec_func(F &&f) : detail::func_base<Func>(std::forward<F>(f))
{ {}
// Just call the function directly, all this wrapper does is remove
// the task_base* parameter. void operator()(detail::task_base *)
this->get_func()(); {
} // Just call the function directly, all this wrapper does is remove
}; // the task_base* parameter.
this->get_func()();
}
};
public: public:
task_wait_handle() task_wait_handle() : handle(nullptr) {}
: handle(nullptr) {}
// Check if the handle is valid // Check if the handle is valid
explicit operator bool() const explicit operator bool() const { return handle != nullptr; }
{
return handle != nullptr;
}
// Check if the task has finished executing // Check if the task has finished executing
bool ready() const bool ready() const { return detail::is_finished(handle->state.load(std::memory_order_acquire)); }
{
return detail::is_finished(handle->state.load(std::memory_order_acquire));
}
// Queue a function to be executed when the task has finished executing. // Queue a function to be executed when the task has finished executing.
template<typename Func> template<typename Func>
void on_finish(Func&& func) void on_finish(Func &&func)
{ {
// Make sure the function type is callable // Make sure the function type is callable
static_assert(detail::is_callable<Func()>::value, "Invalid function type passed to on_finish()"); static_assert(detail::is_callable<Func()>::value, "Invalid function type passed to on_finish()");
auto cont = new detail::task_func<typename std::remove_reference<decltype(inline_scheduler())>::type, wait_exec_func<typename std::decay<Func>::type>, detail::fake_void>(std::forward<Func>(func)); auto cont = new detail::task_func<typename std::remove_reference<decltype(inline_scheduler())>::type,
cont->sched = std::addressof(inline_scheduler()); wait_exec_func<typename std::decay<Func>::type>,
handle->add_continuation(inline_scheduler(), detail::task_ptr(cont)); detail::fake_void>(std::forward<Func>(func));
} cont->sched = std::addressof(inline_scheduler());
handle->add_continuation(inline_scheduler(), detail::task_ptr(cont));
}
}; };
// Wait handler function prototype // Wait handler function prototype
@ -95,81 +92,78 @@ struct LIBASYNC_EXPORT_EXCEPTION task_not_executed {};
// Task handle used in scheduler, acts as a unique_ptr to a task object // Task handle used in scheduler, acts as a unique_ptr to a task object
class task_run_handle { class task_run_handle {
detail::task_ptr handle; detail::task_ptr handle;
// Allow construction in schedule_task() // Allow construction in schedule_task()
template<typename Sched> template<typename Sched>
friend void detail::schedule_task(Sched& sched, detail::task_ptr t); friend void detail::schedule_task(Sched &sched, detail::task_ptr t);
explicit task_run_handle(detail::task_ptr t)
: handle(std::move(t)) {} explicit task_run_handle(detail::task_ptr t) : handle(std::move(t)) {}
public: public:
// Movable but not copyable // Movable but not copyable
task_run_handle() = default; task_run_handle() = default;
task_run_handle(task_run_handle&& other) LIBASYNC_NOEXCEPT
: handle(std::move(other.handle)) {}
task_run_handle& operator=(task_run_handle&& other) LIBASYNC_NOEXCEPT
{
handle = std::move(other.handle);
return *this;
}
// If the task is not executed, cancel it with an exception task_run_handle(task_run_handle &&other) LIBASYNC_NOEXCEPT : handle(std::move(other.handle)) {}
~task_run_handle()
{
if (handle)
handle->vtable->cancel(handle.get(), std::make_exception_ptr(task_not_executed()));
}
// Check if the handle is valid task_run_handle &operator=(task_run_handle &&other) LIBASYNC_NOEXCEPT
explicit operator bool() const {
{ handle = std::move(other.handle);
return handle != nullptr; return *this;
} }
// Run the task and release the handle // If the task is not executed, cancel it with an exception
void run() ~task_run_handle()
{ {
handle->vtable->run(handle.get()); if (handle) handle->vtable->cancel(handle.get(), std::make_exception_ptr(task_not_executed()));
handle = nullptr; }
}
// Run the task but run the given wait handler when waiting for a task, // Check if the handle is valid
// instead of just sleeping. explicit operator bool() const { return handle != nullptr; }
void run_with_wait_handler(wait_handler handler)
{
wait_handler old = set_thread_wait_handler(handler);
run();
set_thread_wait_handler(old);
}
// Conversion to and from void pointer. This allows the task handle to be // Run the task and release the handle
// sent through C APIs which don't preserve types. void run()
void* to_void_ptr() {
{ handle->vtable->run(handle.get());
return handle.release(); handle = nullptr;
} }
static task_run_handle from_void_ptr(void* ptr)
{ // Run the task but run the given wait handler when waiting for a task,
return task_run_handle(detail::task_ptr(static_cast<detail::task_base*>(ptr))); // instead of just sleeping.
} void run_with_wait_handler(wait_handler handler)
{
wait_handler old = set_thread_wait_handler(handler);
run();
set_thread_wait_handler(old);
}
// Conversion to and from void pointer. This allows the task handle to be
// sent through C APIs which don't preserve types.
void *to_void_ptr() { return handle.release(); }
static task_run_handle from_void_ptr(void *ptr)
{
return task_run_handle(detail::task_ptr(static_cast<detail::task_base *>(ptr)));
}
}; };
namespace detail { namespace detail {
// Schedule a task for execution using its scheduler // Schedule a task for execution using its scheduler
template<typename Sched> template<typename Sched>
void schedule_task(Sched& sched, task_ptr t) void
schedule_task(Sched &sched, task_ptr t)
{ {
static_assert(is_scheduler<Sched>::value, "Type is not a valid scheduler"); static_assert(is_scheduler<Sched>::value, "Type is not a valid scheduler");
sched.schedule(task_run_handle(std::move(t))); sched.schedule(task_run_handle(std::move(t)));
} }
// Inline scheduler implementation // Inline scheduler implementation
inline void inline_scheduler_impl::schedule(task_run_handle t) inline void
inline_scheduler_impl::schedule(task_run_handle t)
{ {
t.run(); t.run();
} }
} // namespace detail }// namespace detail
} // namespace async }// namespace async

View File

@ -63,10 +63,15 @@ typedef ref_count_ptr<task_base> task_ptr;
template<typename Sched> template<typename Sched>
void schedule_task(Sched &sched, task_ptr t); void schedule_task(Sched &sched, task_ptr t);
void generic_wait_for_task(task_base *wait_task);
// Wait for the given task to finish. This will call the wait handler currently // Wait for the given task to finish. This will call the wait handler currently
// active for this thread, which causes the thread to sleep by default. // active for this thread, which causes the thread to sleep by default.
#ifndef LIBASYNC_CUSTOM_WAIT_FOR_TASK #ifndef LIBASYNC_CUSTOM_WAIT_FOR_TASK
LIBASYNC_EXPORT void wait_for_task(task_base *wait_task); inline void
wait_for_task(task_base *wait_task)
{
generic_wait_for_task(wait_task);
}
#endif #endif
// Forward-declaration for data used by threadpool_scheduler // Forward-declaration for data used by threadpool_scheduler

View File

@ -22,226 +22,233 @@
// for pthread thread_local emulation // for pthread thread_local emulation
#if defined(EMULATE_PTHREAD_THREAD_LOCAL) #if defined(EMULATE_PTHREAD_THREAD_LOCAL)
# include <pthread.h> #include <pthread.h>
#endif #endif
namespace async { namespace async {
namespace detail { namespace detail {
void* aligned_alloc(std::size_t size, std::size_t align) void *
aligned_alloc(std::size_t size, std::size_t align)
{ {
#ifdef _WIN32 #ifdef _WIN32
void* ptr = _aligned_malloc(size, align); void *ptr = _aligned_malloc(size, align);
if (!ptr) if (!ptr) LIBASYNC_THROW(std::bad_alloc());
LIBASYNC_THROW(std::bad_alloc()); return ptr;
return ptr;
#else #else
void* result; void *result;
if (posix_memalign(&result, align, size)) if (posix_memalign(&result, align, size))
LIBASYNC_THROW(std::bad_alloc()); LIBASYNC_THROW(std::bad_alloc());
else else
return result; return result;
#endif #endif
} }
void aligned_free(void* addr) LIBASYNC_NOEXCEPT void
aligned_free(void *addr) LIBASYNC_NOEXCEPT
{ {
#ifdef _WIN32 #ifdef _WIN32
_aligned_free(addr); _aligned_free(addr);
#else #else
free(addr); free(addr);
#endif #endif
} }
// Wait for a task to complete (for threads outside thread pool) // Wait for a task to complete (for threads outside thread pool)
static void generic_wait_handler(task_wait_handle wait_task) static void
generic_wait_handler(task_wait_handle wait_task)
{ {
// Create an event to wait on // Create an event to wait on
task_wait_event event; task_wait_event event;
event.init(); event.init();
// Create a continuation for the task we are waiting for // Create a continuation for the task we are waiting for
wait_task.on_finish([&event] { wait_task.on_finish([&event] {
// Just signal the thread event // Just signal the thread event
event.signal(wait_type::task_finished); event.signal(wait_type::task_finished);
}); });
// Wait for the event to be set // Wait for the event to be set
event.wait(); event.wait();
} }
#if defined(EMULATE_PTHREAD_THREAD_LOCAL) #if defined(EMULATE_PTHREAD_THREAD_LOCAL)
// Wait handler function, per-thread, defaults to generic version // Wait handler function, per-thread, defaults to generic version
struct pthread_emulation_thread_wait_handler_key_initializer { struct pthread_emulation_thread_wait_handler_key_initializer {
pthread_key_t key; pthread_key_t key;
pthread_emulation_thread_wait_handler_key_initializer() pthread_emulation_thread_wait_handler_key_initializer() { pthread_key_create(&key, nullptr); }
{
pthread_key_create(&key, nullptr); ~pthread_emulation_thread_wait_handler_key_initializer() { pthread_key_delete(key); }
}
~pthread_emulation_thread_wait_handler_key_initializer()
{
pthread_key_delete(key);
}
}; };
static pthread_key_t get_thread_wait_handler_key() static pthread_key_t
get_thread_wait_handler_key()
{ {
static pthread_emulation_thread_wait_handler_key_initializer initializer; static pthread_emulation_thread_wait_handler_key_initializer initializer;
return initializer.key; return initializer.key;
} }
#else #else
static THREAD_LOCAL wait_handler thread_wait_handler = generic_wait_handler; static THREAD_LOCAL wait_handler thread_wait_handler = generic_wait_handler;
#endif #endif
static void set_thread_wait_handler(wait_handler handler) static void
set_thread_wait_handler(wait_handler handler)
{ {
#if defined(EMULATE_PTHREAD_THREAD_LOCAL) #if defined(EMULATE_PTHREAD_THREAD_LOCAL)
// we need to call this here, because the pthread initializer is lazy, // we need to call this here, because the pthread initializer is lazy,
// this means the it could be null and we need to set it before trying to // this means the it could be null and we need to set it before trying to
// get or set it // get or set it
pthread_setspecific(get_thread_wait_handler_key(), reinterpret_cast<void*>(handler)); pthread_setspecific(get_thread_wait_handler_key(), reinterpret_cast<void *>(handler));
#else #else
thread_wait_handler = handler; thread_wait_handler = handler;
#endif #endif
} }
static wait_handler get_thread_wait_handler() static wait_handler
get_thread_wait_handler()
{ {
#if defined(EMULATE_PTHREAD_THREAD_LOCAL) #if defined(EMULATE_PTHREAD_THREAD_LOCAL)
// we need to call this here, because the pthread initializer is lazy, // we need to call this here, because the pthread initializer is lazy,
// this means the it could be null and we need to set it before trying to // this means the it could be null and we need to set it before trying to
// get or set it // get or set it
wait_handler handler = (wait_handler) pthread_getspecific(get_thread_wait_handler_key()); wait_handler handler = (wait_handler) pthread_getspecific(get_thread_wait_handler_key());
if(handler == nullptr) { if (handler == nullptr) { return generic_wait_handler; }
return generic_wait_handler; return handler;
}
return handler;
#else #else
return thread_wait_handler; return thread_wait_handler;
#endif #endif
} }
// Wait for a task to complete // Wait for a task to complete
void wait_for_task(task_base* wait_task) void
generic_wait_for_task(task_base *wait_task)
{ {
// Dispatch to the current thread's wait handler // Dispatch to the current thread's wait handler
wait_handler thread_wait_handler = get_thread_wait_handler(); wait_handler thread_wait_handler = get_thread_wait_handler();
thread_wait_handler(task_wait_handle(wait_task)); thread_wait_handler(task_wait_handle(wait_task));
} }
// The default scheduler is just a thread pool which can be configured // The default scheduler is just a thread pool which can be configured
// using environment variables. // using environment variables.
class default_scheduler_impl: public threadpool_scheduler { class default_scheduler_impl : public threadpool_scheduler {
static std::size_t get_num_threads() static std::size_t get_num_threads()
{ {
// Get the requested number of threads from the environment // Get the requested number of threads from the environment
// If that fails, use the number of CPUs in the system. // If that fails, use the number of CPUs in the system.
std::size_t num_threads; std::size_t num_threads;
#ifdef _MSC_VER #ifdef _MSC_VER
char* s; char *s;
# ifdef __cplusplus_winrt #ifdef __cplusplus_winrt
// Windows store applications do not support environment variables // Windows store applications do not support environment variables
s = nullptr; s = nullptr;
# else
// MSVC gives an error when trying to use getenv, work around this
// by using _dupenv_s instead.
_dupenv_s(&s, nullptr, "LIBASYNC_NUM_THREADS");
# endif
#else #else
const char *s = std::getenv("LIBASYNC_NUM_THREADS"); // MSVC gives an error when trying to use getenv, work around this
// by using _dupenv_s instead.
_dupenv_s(&s, nullptr, "LIBASYNC_NUM_THREADS");
#endif #endif
if (s) #else
num_threads = std::strtoul(s, nullptr, 10); const char *s = std::getenv("LIBASYNC_NUM_THREADS");
else #endif
num_threads = hardware_concurrency(); if (s)
num_threads = std::strtoul(s, nullptr, 10);
else
num_threads = hardware_concurrency();
#if defined(_MSC_VER) && !defined(__cplusplus_winrt) #if defined(_MSC_VER) && !defined(__cplusplus_winrt)
// Free the string allocated by _dupenv_s // Free the string allocated by _dupenv_s
free(s); free(s);
#endif #endif
// Make sure the thread count is reasonable // Make sure the thread count is reasonable
if (num_threads < 1) if (num_threads < 1) num_threads = 1;
num_threads = 1; return num_threads;
return num_threads; }
}
public: public:
default_scheduler_impl() default_scheduler_impl() : threadpool_scheduler(get_num_threads()) {}
: threadpool_scheduler(get_num_threads()) {}
}; };
// Thread scheduler implementation // Thread scheduler implementation
void thread_scheduler_impl::schedule(task_run_handle t) void
thread_scheduler_impl::schedule(task_run_handle t)
{ {
// A shared_ptr is used here because not all implementations of // A shared_ptr is used here because not all implementations of
// std::thread support move-only objects. // std::thread support move-only objects.
std::thread([](const std::shared_ptr<task_run_handle>& t) { std::thread([](const std::shared_ptr<task_run_handle> &t) { t->run(); },
t->run(); std::make_shared<task_run_handle>(std::move(t)))
}, std::make_shared<task_run_handle>(std::move(t))).detach(); .detach();
} }
} // namespace detail }// namespace detail
threadpool_scheduler& default_threadpool_scheduler() threadpool_scheduler &
default_threadpool_scheduler()
{ {
return detail::singleton<detail::default_scheduler_impl>::get_instance(); return detail::singleton<detail::default_scheduler_impl>::get_instance();
} }
// FIFO scheduler implementation // FIFO scheduler implementation
struct fifo_scheduler::internal_data { struct fifo_scheduler::internal_data {
detail::fifo_queue queue; detail::fifo_queue queue;
std::mutex lock; std::mutex lock;
}; };
fifo_scheduler::fifo_scheduler()
: impl(new internal_data) {} fifo_scheduler::fifo_scheduler() : impl(new internal_data) {}
fifo_scheduler::~fifo_scheduler() {} fifo_scheduler::~fifo_scheduler() {}
void fifo_scheduler::schedule(task_run_handle t)
void
fifo_scheduler::schedule(task_run_handle t)
{ {
std::lock_guard<std::mutex> locked(impl->lock); std::lock_guard<std::mutex> locked(impl->lock);
impl->queue.push(std::move(t)); impl->queue.push(std::move(t));
}
bool fifo_scheduler::try_run_one_task()
{
task_run_handle t;
{
std::lock_guard<std::mutex> locked(impl->lock);
t = impl->queue.pop();
}
if (t) {
t.run();
return true;
}
return false;
}
void fifo_scheduler::run_all_tasks()
{
while (try_run_one_task()) {}
} }
std::size_t hardware_concurrency() LIBASYNC_NOEXCEPT bool
fifo_scheduler::try_run_one_task()
{ {
// Cache the value because calculating it may be expensive task_run_handle t;
static std::size_t value = std::thread::hardware_concurrency(); {
std::lock_guard<std::mutex> locked(impl->lock);
// Always return at least 1 core t = impl->queue.pop();
return value == 0 ? 1 : value; }
if (t) {
t.run();
return true;
}
return false;
} }
wait_handler set_thread_wait_handler(wait_handler handler) LIBASYNC_NOEXCEPT void
fifo_scheduler::run_all_tasks()
{ {
wait_handler old = detail::get_thread_wait_handler(); while (try_run_one_task()) {}
detail::set_thread_wait_handler(handler);
return old;
} }
} // namespace async std::size_t
hardware_concurrency() LIBASYNC_NOEXCEPT
{
// Cache the value because calculating it may be expensive
static std::size_t value = std::thread::hardware_concurrency();
// Always return at least 1 core
return value == 0 ? 1 : value;
}
wait_handler
set_thread_wait_handler(wait_handler handler) LIBASYNC_NOEXCEPT
{
wait_handler old = detail::get_thread_wait_handler();
detail::set_thread_wait_handler(handler);
return old;
}
}// namespace async
#ifndef LIBASYNC_STATIC #ifndef LIBASYNC_STATIC
#if defined(__GNUC__) && !defined(_WIN32) #if defined(__GNUC__) && !defined(_WIN32)
# pragma GCC visibility pop #pragma GCC visibility pop
#endif #endif
#endif #endif

View File

@ -8,14 +8,14 @@ class FiberScheduler;
namespace async { namespace async {
sled::FiberScheduler &default_scheduler(); sled::FiberScheduler &default_scheduler();
class task_base;
namespace detail { namespace detail {
class task_base;
void wait_for_task(task_base *wait_task); void wait_for_task(task_base *wait_task);
} }// namespace detail
}// namespace async }// namespace async
#define LIBASYNC_CUSTON_EVENT #define LIBASYNC_CUSTOM_WAIT_FOR_TASK
#define LIBASYNC_CUSTOM_DEFAULT_SCHEDULER #define LIBASYNC_CUSTOM_DEFAULT_SCHEDULER
#include <async++.h> #include <async++.h>

View File

@ -532,7 +532,7 @@ URI::href() const
{ {
std::stringstream ss; std::stringstream ss;
if (!scheme().empty()) { ss << scheme() << ":"; } if (!scheme().empty()) { ss << scheme() << ":"; }
if (!user_info.empty()) { ss << user_info() << "@"; } if (!user_info().empty()) { ss << user_info() << "@"; }
if (!authority().empty()) { ss << authority(); } if (!authority().empty()) { ss << authority(); }
ss << path(); ss << path();
ss << "?" << query_string(); ss << "?" << query_string();

View File

@ -7,7 +7,7 @@ TEST(URI, Absolute)
EXPECT_EQ(uri.scheme(), "http"); EXPECT_EQ(uri.scheme(), "http");
EXPECT_EQ(uri.host(), "example.com"); EXPECT_EQ(uri.host(), "example.com");
EXPECT_EQ(uri.port(), 1234); EXPECT_EQ(uri.port(), 1234);
EXPECT_EQ(uri.path(), "dir1/dir2/file"); EXPECT_EQ(uri.path(), "/dir1/dir2/file");
EXPECT_EQ(uri.query().size(), 1); EXPECT_EQ(uri.query().size(), 1);
EXPECT_EQ(uri.query()["a"], "1"); EXPECT_EQ(uri.query()["a"], "1");
EXPECT_EQ(uri.anchor(), "anchor"); EXPECT_EQ(uri.anchor(), "anchor");