diff --git a/include/Async/Async.h b/include/Async/Async.h index 1554f5fb..ca38c791 100644 --- a/include/Async/Async.h +++ b/include/Async/Async.h @@ -1,5 +1,10 @@ #pragma once -#include "Scheduler.h" -#include "FileSystem.h" +#include "Lock.h" +#include "Event.h" +#include "Sleep.h" +#include "Gather.h" #include "Network.h" +#include "FileSystem.h" +#include "ThreadPool.h" + diff --git a/include/Async/Awaiter.h b/include/Async/Awaiter.h new file mode 100644 index 00000000..80c11cdd --- /dev/null +++ b/include/Async/Awaiter.h @@ -0,0 +1,96 @@ +#pragma once + +#include + +#include "Task.h" +#include "libuv.h" + +namespace clice::async::awaiter { + +template +struct uv_base; + +template + requires (is_uv_handle_v) +struct uv_base { + /// For libuv handles, `uv_close` must be called to release resources. + /// However, `uv_close` can only be async operation. When close is actually called, + /// the promise object may have already been destroyed, leading to undefined behavior + /// such as use-after-free. To avoid this situation, we allocate memory separately + /// for the handle and destroy it in the callback function. + Request& request; + + uv_base() : request(*static_cast(std::malloc(sizeof(Request)))) {} + + ~uv_base() { + uv_close(reinterpret_cast(&request), + [](uv_handle_t* handle) { std::free(handle); }); + } +}; + +template + requires (is_uv_req_v) +struct uv_base { + /// For libuv requests, they don't need to be closed. We can lay them on the promise + /// object directly. + Request request; +}; + +/// The CRTP base class for the awaiter of libuv async operations. The Derived should +/// implement the `start` and `cleanup` functions. +template +struct uv : uv_base { + int error = 0; + promise_base* continuation; + + bool await_ready() const noexcept { + return false; + } + + /// The callback function to handle the async operation. This should always called + /// in the main thread. + static void callback(Request* request, Extras... extras) { + auto& self = *static_cast(request->data); + + /// The derived should implement the cleanup function to release resources or set + /// the error code if the async operation fails. + self.cleanup(extras...); + + /// Then we resume the coroutine. It may destroy the current task, + /// If the task is cancelled and disposable. + self.continuation->resume(); + } + + template + std::coroutine_handle<> await_suspend(std::coroutine_handle waiting) noexcept { + continuation = &waiting.promise(); + this->request.data = static_cast(this); + + auto& self = *static_cast(this); + + /// Start the async operation. + error = self.start(callback); + + /// If the async operation fails, resume the coroutine immediately. + if(error < 0) { + return continuation->resume_handle(); + } + + /// Otherwise, return the coroutine handle to resume later. + return std::noop_coroutine(); + } + + std::expected await_resume() noexcept { + if(error < 0) { + return std::unexpected(std::error_code(error, category())); + } + + if constexpr(!std::is_void_v) { + return static_cast(this)->result(); + } else { + return std::expected(); + } + } +}; + +} // namespace clice::async::awaiter diff --git a/include/Async/Event.h b/include/Async/Event.h new file mode 100644 index 00000000..fce080a6 --- /dev/null +++ b/include/Async/Event.h @@ -0,0 +1,53 @@ +#pragma once + +#include "Task.h" +#include "llvm/ADT/ArrayRef.h" + +namespace clice::async { + +namespace awaiter { + +struct event { + bool ready; + llvm::SmallVectorImpl& awaiters; + + bool await_ready() const noexcept { + return ready; + } + + template + void await_suspend(std::coroutine_handle handle) const noexcept { + awaiters.emplace_back(&handle.promise()); + } + + void await_resume() const noexcept {} +}; + +} // namespace awaiter + +class Event { +public: + Event() = default; + + void set() { + ready = true; + for(auto* awaiter: awaiters) { + awaiter->schedule(); + } + } + + void clear() { + ready = false; + awaiters.clear(); + } + + auto operator co_await() { + return awaiter::event{ready, awaiters}; + } + +private: + bool ready = false; + llvm::SmallVector awaiters; +}; + +} // namespace clice::async diff --git a/include/Async/FileSystem.h b/include/Async/FileSystem.h index db85ea68..b3f7f922 100644 --- a/include/Async/FileSystem.h +++ b/include/Async/FileSystem.h @@ -4,6 +4,7 @@ #include "libuv.h" #include "Task.h" +#include "Awaiter.h" #include "Support/JSON.h" #include "Support/Enum.h" @@ -13,12 +14,6 @@ namespace clice::async { -template -using Result = std::expected; - -template -using AsyncResult = Task>; - namespace fs { using handle = uv_file; @@ -51,29 +46,29 @@ struct Mode : refl::Enum { }; /// Open the file asynchronously. -[[nodiscard]] AsyncResult open(std::string path, Mode mode); +Result open(std::string path, Mode mode); /// Close the file asynchronously. -[[nodiscard]] AsyncResult close(handle file); +Result close(handle file); /// Read the file asynchronously, make sure the buffer is valid until the task is done. -[[nodiscard]] AsyncResult read(handle file, char* buffer, std::size_t size); +Result read(handle file, char* buffer, std::size_t size); -[[nodiscard]] AsyncResult read(std::string path, Mode mode = Mode::Read); +Result read(std::string path, Mode mode = Mode::Read); /// Write the file asynchronously, make sure the buffer is valid until the task is done. -[[nodiscard]] AsyncResult write(handle file, char* buffer, std::size_t size); +Result write(handle file, char* buffer, std::size_t size); -[[nodiscard]] AsyncResult write(std::string path, - char* buffer, - std::size_t size, - Mode mode = Mode(Mode::Write, Mode::Create, Mode::Truncate)); +Result write(std::string path, + char* buffer, + std::size_t size, + Mode mode = Mode(Mode::Write, Mode::Create, Mode::Truncate)); struct Stats { std::chrono::milliseconds mtime; }; -AsyncResult stat(std::string path); +Result stat(std::string path); } // namespace fs diff --git a/include/Async/Gather.h b/include/Async/Gather.h new file mode 100644 index 00000000..ee95c9c6 --- /dev/null +++ b/include/Async/Gather.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include + +#include "Task.h" +#include "Event.h" + +namespace clice::async { + +struct none {}; + +template ::value_type> +using task_value_t = std::conditional_t, none, V>; + +template +auto gather(Tasks&&... tasks) -> Task...>> { + constexpr static std::size_t count = sizeof...(Tasks); + + Event event; + std::size_t finished = 0; + + auto run_task = [&](auto& task) -> Task> { + using V = typename std::remove_cvref_t::value_type; + if constexpr(std::is_void_v) { + co_await task; + /// Check if all tasks are finished. If so, set the event to + /// resume the gather handle. + finished += 1; + if(finished == count) { + event.set(); + } + co_return none{}; + } else { + auto result = co_await task; + finished += 1; + if(finished == count) { + event.set(); + } + co_return std::move(result); + } + }; + + auto schedule_task = [&](auto& task) { + auto core = run_task(task); + core.schedule(); + return core; + }; + + std::tuple all = {schedule_task(tasks)...}; + + /// Wait for all tasks to finish. + co_await event; + + /// Return the results of all tasks. + co_return [&](std::index_sequence) { + return std::make_tuple(std::get(all).result()...); + }(std::make_index_sequence{}); +} + +/// Run the tasks in parallel and return the results. +template +auto run(Tasks&&... tasks) { + auto core = gather(std::forward(tasks)...); + core.schedule(); + async::run(); + assert(core.done() && "run: not done"); + return core.result(); +} + +template +Task<> gather(Range&& range, + Coroutine&& coroutine, + std::size_t concurrency = std::thread::hardware_concurrency()) { + std::vector> tasks; + tasks.reserve(concurrency); + + auto iter = ranges::begin(range); + auto end = ranges::end(range); + + Event event; + std::size_t finished = 0; + + auto run_task = [&](auto& value) -> async::Task<> { + /// Execute the first task. + auto task = coroutine(value); + co_await task; + finished += 1; + + /// Check if still have tasks to run. If so, run the next task. + while(iter != end) { + auto task = coroutine(*iter); + iter++; + finished -= 1; + co_await task; + finished += 1; + } + + /// Check if all tasks are finished. If so, set the event to + /// resume the gather handle. + if(finished == tasks.size()) { + event.set(); + } + }; + + /// Fill tasks. + while(iter != end && tasks.size() < concurrency) { + tasks.emplace_back(run_task(*iter)); + tasks.back().schedule(); + iter++; + } + + co_await event; +} + +} // namespace clice::async diff --git a/include/Async/Lock.h b/include/Async/Lock.h new file mode 100644 index 00000000..958dc31f --- /dev/null +++ b/include/Async/Lock.h @@ -0,0 +1,74 @@ +#pragma once + +#include "Event.h" + +namespace clice::async { + +namespace awaiter { + +struct lock { + llvm::SmallVectorImpl& awaiters; + + bool await_ready() const noexcept { + return false; + } + + template + void await_suspend(std::coroutine_handle handle) const noexcept { + awaiters.emplace_back(&handle.promise()); + } + + void await_resume() const noexcept {} +}; + +} // namespace awaiter + +class Lock { + friend class guard; + +public: + Lock() = default; + + class Guard { + public: + Guard(Lock* lock) : lock(lock) { + assert(lock->locked && "Guard: already locked"); + } + + Guard(Guard&& other) : lock(other.lock) { + other.lock = nullptr; + } + + ~Guard() { + if(!lock) { + return; + } + + lock->locked = false; + if(!lock->awaiters.empty()) { + lock->awaiters.front()->schedule(); + lock->awaiters.erase(lock->awaiters.begin()); + } + } + + private: + Lock* lock; + }; + + /// Try to get the lock. If the lock is locked, the current coroutine will be + /// suspended and wait for the lock to be released. + Task try_lock() { + if(locked) { + co_await awaiter::lock{awaiters}; + } + + locked = true; + co_return Guard{this}; + } + +private: + bool locked = false; + llvm::SmallVector awaiters; +}; + +} // namespace clice::async diff --git a/include/Async/Scheduler.h b/include/Async/Scheduler.h deleted file mode 100644 index 5a9e08e3..00000000 --- a/include/Async/Scheduler.h +++ /dev/null @@ -1,173 +0,0 @@ -#pragma once - -#include -#include - -#include "libuv.h" -#include "Task.h" - -namespace clice::async { - -void run(); - -namespace awaiter { - -template -struct suspend { - Callback callback; - - bool await_ready() noexcept { - return false; - } - - template - void await_suspend(std::coroutine_handle handle) noexcept { - callback(&handle.promise()); - } - - void await_resume() noexcept {} -}; - -} // namespace awaiter - -template -auto suspend(Callback&& callback) { - return awaiter::suspend>{std::forward(callback)}; -} - -struct none {}; - -template ::value_type> -using task_value_t = std::conditional_t, none, V>; - -template -auto gather [[gnu::noinline]] (Tasks&&... tasks) -> Task...>> { - /// FIXME: If remove noinline, the program crashes. Figure out in the future. - (tasks.schedule(), ...); - - while(!(tasks.done() && ...)) { - co_await async::suspend([](auto handle) { handle->schedule(); }); - } - - /// If all tasks are done, return the results. - auto getResult = [](Task& task) { - if constexpr(std::is_void_v) { - return none{}; - } else { - return task.result(); - } - }; - co_return std::tuple{getResult(tasks)...}; -} - -/// Run the tasks in parallel and return the results. -template -auto run(Tasks&&... tasks) { - auto core = gather(std::forward(tasks)...); - core.schedule(); - async::run(); - assert(core.done() && "run: not done"); - return core.result(); -} - -namespace impl::awaiter { - -template -struct thread_pool_base { - std::optional value; - - Ret await_resume() noexcept { - assert(value.has_value() && "await_resume: value not set"); - return std::move(*value); - } -}; - -template <> -struct thread_pool_base { - void await_resume() noexcept {} -}; - -template -struct thread_pool : thread_pool_base { - /// The libuv work request. - uv_work_t request; - - /// The function to run in the thread pool. - Function function; - - /// The coroutine handle waiting for the result. - promise_base* waiting; - - bool await_ready() noexcept { - return false; - } - - template - void await_suspend(std::coroutine_handle waiting) noexcept { - request.data = this; - this->waiting = &waiting.promise(); - - auto work_cb = [](uv_work_t* work) { - auto& awaiter = *static_cast(work->data); - if constexpr(!std::is_void_v) { - awaiter.value.emplace(awaiter.function()); - } else { - awaiter.function(); - } - }; - - auto after_work_cb = [](uv_work_t* work, int status) { - auto& awaiter = *static_cast(work->data); - awaiter.waiting->schedule(); - }; - - uv_queue_work(uv_default_loop(), &request, work_cb, after_work_cb); - } -}; - -} // namespace impl::awaiter - -template Callback, typename R = std::invoke_result_t> -auto submit(Callback&& callback) { - using C = std::remove_cvref_t; - return impl::awaiter::thread_pool{{}, {}, std::forward(callback)}; -} - -namespace awaiter { - -struct sleep { - uv_timer_t timer; - promise_base* continuation; - std::chrono::milliseconds duration; - - bool await_ready() const noexcept { - return false; - } - - template - void await_suspend(std::coroutine_handle waiting) noexcept { - continuation = &waiting.promise(); - timer.data = this; - uv_timer_init(async::loop, &timer); - uv_timer_start( - &timer, - [](uv_timer_t* handle) { - auto& awaiter = *static_cast(handle->data); - awaiter.continuation->resume(); - uv_timer_stop(handle); - }, - duration.count(), - 0); - } - - void await_resume() noexcept {} -}; - -} // namespace awaiter - -inline auto sleep(std::chrono::milliseconds duration) { - return awaiter::sleep{{}, {}, duration}; -} - -}; // namespace clice::async - diff --git a/include/Async/Sleep.h b/include/Async/Sleep.h new file mode 100644 index 00000000..7bc94241 --- /dev/null +++ b/include/Async/Sleep.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include "Awaiter.h" + +namespace clice::async { + +namespace awaiter { + +struct sleep : uv { + std::chrono::milliseconds duration; + + int start(auto callback) { + int err = uv_timer_init(async::loop, &request); + if(err < 0) { + return err; + } + return uv_timer_start(&request, callback, duration.count(), 0); + } + + void cleanup() { + error = uv_timer_stop(&request); + } +}; + +} // namespace awaiter + +inline auto sleep(std::chrono::milliseconds duration) { + return awaiter::sleep{{}, duration}; +} + +inline auto sleep(std::size_t milliseconds) { + return sleep(std::chrono::milliseconds(milliseconds)); +} + +}; // namespace clice::async + diff --git a/include/Async/ThreadPool.h b/include/Async/ThreadPool.h new file mode 100644 index 00000000..4d99e82d --- /dev/null +++ b/include/Async/ThreadPool.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Awaiter.h" + +namespace clice::async { + +namespace awaiter { + +template +struct value { + std::optional value; +}; + +template <> +struct value {}; + +template +struct thread_pool : value, uv, uv_work_t, Ret, int> { + Work work; + + /// `uv_work_t` has two callback functions, `work_cb` is executed in the thread pool, + /// and `after_work_cb` is executed in the main thread. + static void work_cb(uv_work_t* work) { + auto& awaiter = uv_cast(work); + if constexpr(!std::is_void_v) { + awaiter.value.emplace(awaiter.work()); + } else { + awaiter.work(); + } + } + + int start(auto callback) { + return uv_queue_work(async::loop, &this->request, work_cb, callback); + } + + void cleanup(int status) { + this->error = status; + } + + Ret result() { + if constexpr(!std::is_void_v) { + return std::move(*this->value); + } + } +}; + +} // namespace awaiter + +template ()())> +auto submit(Work&& work) { + return awaiter::thread_pool, Ret>{{}, {}, std::forward(work)}; +} + +} // namespace clice::async diff --git a/include/Async/libuv.h b/include/Async/libuv.h index 6af74307..30778c3b 100644 --- a/include/Async/libuv.h +++ b/include/Async/libuv.h @@ -15,6 +15,8 @@ #include #include +#include "Support/TypeTraits.h" + namespace clice::async { /// The default event loop. @@ -26,6 +28,46 @@ T& uv_cast(U* u) { return *static_cast*>(u->data); } +#define UV_TYPE_ITER(_, name) || std::is_same_v + +/// Check if the type `T` is a libuv handle. +template +constexpr bool is_uv_handle_v = false UV_HANDLE_TYPE_MAP(UV_TYPE_ITER); + +/// Check if the type `T` is a libuv request. +template +constexpr bool is_uv_req_v = false UV_REQ_TYPE_MAP(UV_TYPE_ITER); + +template +constexpr bool is_uv_stream_v = std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v; + +#undef UV_TYPE_ITER + +template +T* uv_cast(U& u) { + if constexpr(std::is_same_v) { + static_assert(is_uv_handle_v>, "uv_cast: invalid uv handle"); + } else if constexpr(std::is_same_v) { + static_assert(is_uv_req_v>, "uv_cast: invalid uv request"); + } else if constexpr(std::is_same_v) { + static_assert(is_uv_stream_v>, "uv_cast: invalid uv stream"); + } else { + static_assert(dependent_false, "uv_cast: invalid type"); + } + return reinterpret_cast(&u); +} + +template +class Task; + +template +using Result = Task>; + const std::error_category& category(); +void init(); + +void run(); + } // namespace clice::async diff --git a/include/Support/FileSystem.h b/include/Support/FileSystem.h index 9da0fb90..d70eca03 100644 --- a/include/Support/FileSystem.h +++ b/include/Support/FileSystem.h @@ -42,15 +42,42 @@ inline llvm::Error init_resource_dir(llvm::StringRef execute) { llvm::SmallString<128> path; path::append(path, path::parent_path(execute), ".."); path::append(path, "lib", "clang", "20"); - if(auto error = real_path(path, path)) { return llvm::make_error(error.message(), error); } - resource_dir = path.str(); return llvm::Error::success(); } +inline std::expected createTemporaryFile(llvm::StringRef prefix, + llvm::StringRef suffix) { + llvm::SmallString<128> path; + auto error = llvm::sys::fs::createTemporaryFile(prefix, suffix, path); + if(error) { + return std::unexpected(error); + } + return path.str().str(); +} + +inline std::expected write(llvm::StringRef path, llvm::StringRef content) { + std::error_code EC; + llvm::raw_fd_ostream os(path, EC, llvm::sys::fs::OF_None); + if(EC) { + return std::unexpected(EC); + } + os << content; + os.flush(); + return std::expected(); +} + +inline std::expected read(llvm::StringRef path) { + auto buffer = llvm::MemoryBuffer::getFile(path); + if(!buffer) { + return std::unexpected(buffer.getError()); + } + return buffer.get()->getBuffer().str(); +} + } // namespace fs namespace vfs = llvm::vfs; diff --git a/src/Async/Async.cpp b/src/Async/Async.cpp index 58f520de..c1a9db81 100644 --- a/src/Async/Async.cpp +++ b/src/Async/Async.cpp @@ -5,41 +5,77 @@ namespace clice::async { +#define UV_CHECK_RESULT(expr) \ + do { \ + int err = (expr); \ + if(err < 0) { \ + log::warn("lib uv error: {}", uv_strerror(err)); \ + auto location = std::source_location::current(); \ + log::warn("At {}:{}:{}", \ + location.file_name(), \ + location.line(), \ + location.function_name()); \ + } \ + } while(0) + /// The default event loop. -uv_loop_t* loop = uv_default_loop(); +uv_loop_t* loop = nullptr; namespace { -/// The task queue waiting for resuming. -std::deque> tasks; +uv_loop_t instance; +uv_idle_t idle; +bool idle_running = false; +std::deque tasks; -net::Callback callback = {}; +void each(uv_idle_t* idle) { + if(idle_running && tasks.empty()) { + idle_running = false; + UV_CHECK_RESULT(uv_idle_stop(idle)); + } -uv_stream_t* writer = {}; - -/// Whether the server is listening. -bool listened = false; + /// Resume may create new tasks, we want to run them in the next iteration. + auto all = std::move(tasks); + for(auto& task: all) { + task->resume(); + } +} } // namespace void promise_base::schedule() { - uv_async_t* async = new uv_async_t; - async->data = this; - uv_async_init(loop, async, [](uv_async_t* handle) { - auto core = static_cast(handle->data); - core->resume(); - uv_close((uv_handle_t*)handle, [](uv_handle_t* handle) { delete (uv_async_t*)handle; }); - }); - uv_async_send(async); + if(loop && !idle_running && tasks.empty()) { + idle_running = true; + UV_CHECK_RESULT(uv_idle_start(&idle, each)); + } + + tasks.push_back(this); +} + +void init() { + loop = &instance; + + UV_CHECK_RESULT(uv_loop_init(loop)); + + idle_running = true; + UV_CHECK_RESULT(uv_idle_init(loop, &idle)); + UV_CHECK_RESULT(uv_idle_start(&idle, each)); } void run() { -#ifdef _WIN32 - _putenv_s("UV_THREADPOOL_SIZE", "20"); -#else - setenv("UV_THREADPOOL_SIZE", "20", 1); -#endif - uv_run(loop, UV_RUN_DEFAULT); + if(!loop) { + init(); + } + + UV_CHECK_RESULT(uv_run(loop, UV_RUN_DEFAULT)); + + uv_close(reinterpret_cast(&idle), nullptr); + + /// Run agian to cleanup the loop. + UV_CHECK_RESULT(uv_run(loop, UV_RUN_DEFAULT)); + UV_CHECK_RESULT(uv_loop_close(loop)); + + loop = nullptr; } } // namespace clice::async diff --git a/src/Async/FileSystem.cpp b/src/Async/FileSystem.cpp index 64e6c8f8..b803509b 100644 --- a/src/Async/FileSystem.cpp +++ b/src/Async/FileSystem.cpp @@ -1,61 +1,23 @@ #include "Async/FileSystem.h" -namespace clice::async::fs { +namespace clice::async::awaiter {} -namespace { +namespace clice::async::fs { namespace awaiter { template -struct fs { - uv_fs_t request; - promise_base* continuation; - int error = 0; - - bool await_ready() const noexcept { - return false; +struct fs : async::awaiter::uv, uv_fs_t, Ret> { + int start(auto callback) { + return static_cast(this)->start(callback); } - template - void await_suspend(std::coroutine_handle waiting) noexcept { - request.data = this; - continuation = &waiting.promise(); - - /// All callbacks for libuv are the same. Resume the waiting coroutine - /// and cleanup the request. - auto callback = [](uv_fs_t* req) { - auto& awaiter = *static_cast(req->data); - awaiter.continuation->schedule(); - uv_fs_req_cleanup(req); - }; - - error = static_cast(this)->schedule(callback); - - /// If the operation is not successful, we need to schedule the waiting - /// coroutine directly. - if(error < 0) { - continuation->schedule(); - } + void cleanup() { + uv_fs_req_cleanup(&this->request); } - auto make_error(int code) { - return std::unexpected(std::error_code(code, async::category())); - } - - Result await_resume() { - if(error < 0) { - return make_error(error); - } - - if(request.result < 0) { - return make_error(request.result); - } - - if constexpr(!std::is_void_v) { - return static_cast(this)->result(); - } else { - return Result(); - } + auto result() { + return static_cast(this)->result(); } }; @@ -63,7 +25,7 @@ struct open : fs { const char* path; int flags; - int schedule(uv_fs_cb cb) { + int start(uv_fs_cb cb) { /// `uv_fs_open` will copy the path, so we don't need to worry about the /// lifetime of the path. return uv_fs_open(async::loop, &request, path, flags, 0666, cb); @@ -77,7 +39,7 @@ struct open : fs { struct close : fs { handle file; - int schedule(uv_fs_cb cb) { + int start(uv_fs_cb cb) { return uv_fs_close(async::loop, &request, file, cb); } }; @@ -86,8 +48,8 @@ struct read : fs { handle file; uv_buf_t bufs[1]; - int schedule(uv_fs_cb cb) { - return uv_fs_read(async::loop, &request, file, bufs, 1, 0, cb); + int start(uv_fs_cb cb) { + return uv_fs_read(async::loop, &request, file, bufs, 1, -1, cb); } auto result() { @@ -99,7 +61,7 @@ struct write : fs { handle file; uv_buf_t bufs[1]; - int schedule(uv_fs_cb cb) { + int start(uv_fs_cb cb) { return uv_fs_write(async::loop, &request, file, bufs, 1, 0, cb); } }; @@ -107,7 +69,7 @@ struct write : fs { struct stat : fs { const char* path; - int schedule(uv_fs_cb cb) { + int start(uv_fs_cb cb) { return uv_fs_stat(async::loop, &request, path, cb); } @@ -120,8 +82,6 @@ struct stat : fs { } // namespace awaiter -} // namespace - static int transformFlags(Mode mode) { int flags = 0; @@ -156,25 +116,25 @@ static int transformFlags(Mode mode) { return flags; } -AsyncResult open(std::string path, Mode mode) { +Result open(std::string path, Mode mode) { co_return co_await awaiter::open{ .path = path.c_str(), .flags = transformFlags(mode), }; } -AsyncResult close(handle file) { +Result close(handle file) { co_return co_await awaiter::close{.file = file}; } -AsyncResult read(handle file, char* buffer, std::size_t size) { +Result read(handle file, char* buffer, std::size_t size) { co_return co_await awaiter::read{ .file = file, .bufs = {uv_buf_init(buffer, size)}, }; } -AsyncResult read(std::string path, Mode mode) { +Result read(std::string path, Mode mode) { /// Open the file. auto file = co_await open(path, mode); if(!file) { @@ -206,14 +166,14 @@ AsyncResult read(std::string path, Mode mode) { co_return content; } -AsyncResult write(handle file, char* buffer, std::size_t size) { +Result write(handle file, char* buffer, std::size_t size) { co_return co_await awaiter::write{ .file = file, .bufs = {uv_buf_init(buffer, size)}, }; } -AsyncResult write(std::string path, char* buffer, std::size_t size, Mode mode) { +Result write(std::string path, char* buffer, std::size_t size, Mode mode) { auto file = co_await open(path, mode); if(!file) { co_return std::unexpected(file.error()); @@ -227,10 +187,10 @@ AsyncResult write(std::string path, char* buffer, std::size_t size, Mode m co_return std::unexpected(result.error()); } - co_return Result(); + co_return std::expected(); } -AsyncResult stat(std::string path) { +Result stat(std::string path) { co_return co_await awaiter::stat{.path = path.c_str()}; } diff --git a/src/Async/Network.cpp b/src/Async/Network.cpp index 107de5bf..1edcf859 100644 --- a/src/Async/Network.cpp +++ b/src/Async/Network.cpp @@ -3,8 +3,18 @@ namespace clice::async::net { +/// The initialize should not have any error. If so, we can't continue. +#define UV_CHECK_RESUlT(error) \ + if(error < 0) { \ + log::fatal("{}", std::error_code(error, std::system_category())); \ + } + namespace { +net::Callback callback = {}; + +uv_stream_t* writer = {}; + void on_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { /// This function is called synchronously before `on_read`. See the implementation of /// `uv__read` in libuv/src/unix/stream.c. So it is safe to use a static buffer here. @@ -14,95 +24,67 @@ void on_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { buf->len = suggested_size; } -class MessageBuffer { -public: - MessageBuffer() = default; - - void append(llvm::StringRef message) { - buffer += message; - } - - llvm::StringRef peek() { - llvm::StringRef str = buffer; - std::size_t length = 0; - if(str.consume_front("Content-Length: ") && !str.consumeInteger(10, length) && - str.consume_front("\r\n\r\n") && str.size() >= length) { - auto result = str.substr(0, length); - pos = result.end() - buffer.begin(); - return result; - } - return {}; - } - - void consume() { - buffer.erase(buffer.begin(), buffer.begin() + pos); - pos = 0; - } - -private: - std::size_t pos; - llvm::SmallString<4096> buffer; -}; - -net::Callback callback = {}; - -uv_stream_t* writer = {}; - void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + /// If the stream is closed, we should stop reading. + if(nread == UV_EOF) [[unlikely]] { + uv_read_stop(stream); + uv_close(uv_cast(*stream), nullptr); + uv_close(uv_cast(*writer), nullptr); + return; + } + + /// If an error occurred while reading, we can't continue. + if(nread < 0) [[unlikely]] { + log::fatal("An error occurred while reading: {0}", uv_strerror(nread)); + } + /// We have at most one connection and use default event loop. So there is no data race /// risk. It is safe to use a static buffer here. + static llvm::SmallString<4096> buffer; + buffer.insert(buffer.end(), buf->base, buf->base + nread); - /// FIXME: use a more efficient data structure. - static MessageBuffer buffer; - if(nread > 0) { - buffer.append({buf->base, static_cast(nread)}); - if(auto message = buffer.peek(); !message.empty()) { - if(auto json = json::parse(message)) { - /// This is a top-level coroutine. - auto core = callback(std::move(*json)); - /// It will be destroyed in final suspend point. - /// So we release it here. - core.schedule(); - core.release(); - buffer.consume(); - } else { - log::fatal("An error occurred while parsing JSON: {0}", json.takeError()); - } + /// Parse the LSP message header. + llvm::StringRef message = buffer; + std::size_t length = 0; + if(message.consume_front("Content-Length: ") && !message.consumeInteger(10, length) && + message.consume_front("\r\n\r\n") && message.size() >= length) { + auto result = message.substr(0, length); + + if(auto input = llvm::json::parse(result)) { + /// If the message is valid, we can process it. + auto task = callback(std::move(*input)); + + /// Schedule the task and dispose it so that it can be + /// destroyed after the task is done. + task.schedule(); + task.dispose(); + } else { + /// If the message is invalid, we can't continue. + log::fatal("Unexpected JSON input: {0}", result); } - } else if(nread < 0) { - if(nread != UV_EOF) { - log::fatal("An error occurred while reading: {0}", uv_strerror(nread)); - } - uv_close((uv_handle_t*)stream, NULL); + + /// Remove the processed message from the buffer. + auto pos = result.end() - buffer.begin(); + buffer.erase(buffer.begin(), buffer.begin() + pos); } } } // namespace -#define uv_check_call(func, ...) \ - if(int error = func(__VA_ARGS__); error < 0) { \ - log::fatal("An error occurred while calling {0}: {1}", #func, uv_strerror(error)); \ - } - -#define uv_log(error) \ - if(error < 0) { \ - log::fatal("{}", std::error_code(error, std::system_category())); \ - } - void listen(Callback callback) { static uv_pipe_t in; static uv_pipe_t out; net::callback = std::move(callback); - writer = reinterpret_cast(&out); + writer = uv_cast(out); - uv_log(uv_pipe_init(async::loop, &in, 0)); - uv_log(uv_pipe_open(&in, 0)); + UV_CHECK_RESUlT(uv_pipe_init(async::loop, &in, 0)); + UV_CHECK_RESUlT(uv_pipe_open(&in, 0)); - uv_log(uv_pipe_init(async::loop, &out, 0)); - uv_log(uv_pipe_open(&out, 1)); + UV_CHECK_RESUlT(uv_pipe_init(async::loop, &out, 0)); + UV_CHECK_RESUlT(uv_pipe_open(&out, 1)); - uv_log(uv_read_start((uv_stream_t*)&in, net::on_alloc, net::on_read)); + UV_CHECK_RESUlT(uv_read_start(uv_cast(in), net::on_alloc, net::on_read)); } void listen(const char* ip, unsigned int port, Callback callback) { @@ -110,22 +92,22 @@ void listen(const char* ip, unsigned int port, Callback callback) { static uv_tcp_t client; net::callback = std::move(callback); - writer = reinterpret_cast(&client); + writer = uv_cast(client); - uv_log(uv_tcp_init(async::loop, &server)); - uv_log(uv_tcp_init(async::loop, &client)); + UV_CHECK_RESUlT(uv_tcp_init(async::loop, &server)); + UV_CHECK_RESUlT(uv_tcp_init(async::loop, &client)); struct ::sockaddr_in addr; - uv_log(uv_ip4_addr(ip, port, &addr)); - uv_log(uv_tcp_bind(&server, (const struct ::sockaddr*)&addr, 0)); + UV_CHECK_RESUlT(uv_ip4_addr(ip, port, &addr)); + UV_CHECK_RESUlT(uv_tcp_bind(&server, (const struct ::sockaddr*)&addr, 0)); auto on_connection = [](uv_stream_t* server, int status) { - uv_log(status); - uv_log(uv_accept(server, (uv_stream_t*)&client)); - uv_log(uv_read_start((uv_stream_t*)&client, net::on_alloc, net::on_read)); + UV_CHECK_RESUlT(status); + UV_CHECK_RESUlT(uv_accept(server, uv_cast(client))); + UV_CHECK_RESUlT(uv_read_start(uv_cast(client), net::on_alloc, net::on_read)); }; - uv_log(uv_listen((uv_stream_t*)&server, 1, on_connection)); + UV_CHECK_RESUlT(uv_listen(uv_cast(server), 1, on_connection)); } void spawn(llvm::StringRef path, llvm::ArrayRef args, Callback callback) { @@ -136,9 +118,9 @@ void spawn(llvm::StringRef path, llvm::ArrayRef args, Callback call net::callback = std::move(callback); writer = reinterpret_cast(&in); - uv_check_call(uv_pipe_init, async::loop, &in, 0); - uv_check_call(uv_pipe_init, async::loop, &out, 0); - uv_check_call(uv_pipe_init, async::loop, &err, 0); + UV_CHECK_RESUlT(uv_pipe_init(async::loop, &in, 0)); + UV_CHECK_RESUlT(uv_pipe_init(async::loop, &out, 0)); + UV_CHECK_RESUlT(uv_pipe_init(async::loop, &err, 0)); static uv_process_t process; static uv_process_options_t options; @@ -181,9 +163,10 @@ void spawn(llvm::StringRef path, llvm::ArrayRef args, Callback call } options.args = argv.data(); - uv_log(uv_spawn(async::loop, &process, &options)); - uv_log(uv_read_start((uv_stream_t*)&out, net::on_alloc, net::on_read)); + UV_CHECK_RESUlT(uv_spawn(async::loop, &process, &options)); + UV_CHECK_RESUlT(uv_read_start((uv_stream_t*)&out, net::on_alloc, net::on_read)); + /// FIXME: This implementation is not correct. auto on_read = [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { if(nread > 0) { log::warn("{0}", llvm::StringRef{buf->base, static_cast(nread)}); @@ -195,7 +178,7 @@ void spawn(llvm::StringRef path, llvm::ArrayRef args, Callback call } }; - uv_log(uv_read_start((uv_stream_t*)&err, net::on_alloc, on_read)); + UV_CHECK_RESUlT(uv_read_start((uv_stream_t*)&err, net::on_alloc, on_read)); } namespace awaiter { @@ -219,7 +202,7 @@ struct write { buf[0] = uv_buf_init(header.data(), header.size()); buf[1] = uv_buf_init(message.data(), message.size()); - uv_check_call(uv_write, &req, writer, buf, 2, [](uv_write_t* req, int status) { + uv_write(&req, writer, buf, 2, [](uv_write_t* req, int status) { if(status < 0) { log::fatal("An error occurred while writing: {0}", uv_strerror(status)); } diff --git a/src/Driver/clice.cc b/src/Driver/clice.cc index a82d04c9..3315e71e 100644 --- a/src/Driver/clice.cc +++ b/src/Driver/clice.cc @@ -10,7 +10,7 @@ llvm::cl::opt config("config", llvm::cl::desc("The path of the config file"), llvm::cl::value_desc("path")); -llvm::cl::opt pipe("pipe", llvm::cl::desc("Use pipe mode")); +llvm::cl::opt mode("mode", llvm::cl::desc("Use pipe mode")); llvm::cl::opt resource_dir("resource-dir", llvm::cl::desc("Resource dir path")); @@ -18,7 +18,7 @@ llvm::cl::opt resource_dir("resource-dir", llvm::cl::desc("Resource int main(int argc, const char** argv) { for(int i = 0; i < argc; ++i) { - log::warn("argv[{0}] = {1}", i, argv[i]); + log::info("argv[{0}] = {1}", i, argv[i]); } llvm::cl::SetVersionPrinter([](llvm::raw_ostream& os) { os << "clice version: 0.0.1\n"; }); @@ -46,10 +46,14 @@ int main(int argc, const char** argv) { co_await server.onReceive(value); }; - if(cl::pipe && cl::pipe.getValue()) { + async::init(); + + if(cl::mode == "pipe") { async::net::listen(loop); - } else { + log::info("Server starts listening on stdin/stdout"); + } else if(cl::mode == "socket") { async::net::listen("127.0.0.1", 50051, loop); + log::info("Server starts listening on {}:{}", "127.0.0.1", 50051); } async::run(); diff --git a/src/Server/Indexer.cpp b/src/Server/Indexer.cpp index 00e4d56a..38d8d602 100644 --- a/src/Server/Indexer.cpp +++ b/src/Server/Indexer.cpp @@ -185,7 +185,7 @@ async::Task<> Indexer::updateIndices(this Self& self, auto& SM = info.srcMgr(); - for(auto& [fid, index]: indices) { + for(auto& [fid, index]: *indices) { if(fid == SM.getMainFileID()) { if(tu->indexPath.empty()) { tu->indexPath = self.getIndexPath(tu->srcPath); @@ -309,9 +309,9 @@ async::Task<> Indexer::index(this Self& self, llvm::StringRef file) { llvm::DenseMap files; /// Otherwise, we need to update all header contexts. - self.addContexts(*info, tu, files); + self.addContexts(**info, tu, files); - co_await self.updateIndices(*info, tu, files); + co_await self.updateIndices(**info, tu, files); } async::Task<> Indexer::index(llvm::StringRef file, ASTInfo& info) { @@ -328,29 +328,16 @@ async::Task<> Indexer::indexAll() { co_await index(file); }; - auto iter = database.begin(); - auto end = database.end(); + std::vector files; + files.reserve(database.size()); - std::vector> tasks; - /// TODO: Use threads count in the future. - tasks.resize(20); + for(auto& [file, _]: database) { + files.emplace_back(file); + } log::info("Start indexing all files"); - while(iter != end || - ranges::any_of(tasks, [](auto& task) { return !task.empty() && !task.done(); })) { - for(auto& task: tasks) { - if(task.empty() || task.done()) { - if(iter != end) { - task = each(iter->first()); - task.schedule(); - ++iter; - } - } - } - - co_await async::suspend([&](auto handle) { handle->schedule(); }); - } + co_await async::gather(files, each); } std::string Indexer::getIndexPath(llvm::StringRef file) { @@ -513,11 +500,18 @@ void Indexer::loadFromDisk() { } async::Task> Indexer::read(llvm::StringRef path) { - co_return co_await async::submit([path] { + auto result = co_await async::submit([path] { auto file = llvm::MemoryBuffer::getFile(path); ASSERT(file, "Failed to open file: {}, because: {}", path, file.getError()); return std::move(file.get()); }); + + if(!result) { + log::warn("Failed to read file: {}", path); + co_return nullptr; + } + + co_return std::move(*result); } async::Task<> Indexer::lookup(llvm::ArrayRef ids, diff --git a/unittests/Async/Async.cpp b/unittests/Async/Async.cpp deleted file mode 100644 index 989c58b5..00000000 --- a/unittests/Async/Async.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "Test/Test.h" -#include "Async/Async.h" -#include - -namespace clice::testing { - -namespace { - -TEST(Async, Submit) { - auto task = []() -> async::Task { - co_return co_await async::submit([]() { - std::this_thread::sleep_for(std::chrono::seconds(1)); - return std::this_thread::get_id(); - }); - }(); - - auto task2 = []() -> async::Task { - co_return co_await async::submit([]() { - std::this_thread::sleep_for(std::chrono::seconds(1)); - return std::this_thread::get_id(); - }); - }(); - - auto task3 = []() -> async::Task { - co_return co_await async::submit([]() { - std::this_thread::sleep_for(std::chrono::seconds(1)); - return std::this_thread::get_id(); - }); - }(); - - auto result = async::run(task, task2, task3); - - EXPECT_EQ(task.done(), true); - EXPECT_EQ(task2.done(), true); - EXPECT_EQ(task3.done(), true); - - auto [id1, id2, id3] = result; - EXPECT_NE(id1, id2); - EXPECT_NE(id2, id3); - EXPECT_NE(id1, id3); -} - -} // namespace - -} // namespace clice::testing - diff --git a/unittests/Async/Event.cpp b/unittests/Async/Event.cpp new file mode 100644 index 00000000..cf672557 --- /dev/null +++ b/unittests/Async/Event.cpp @@ -0,0 +1,38 @@ +#include "Test/Test.h" +#include "Async/Async.h" + +namespace clice::testing { + +namespace { + +TEST(Async, Event) { + async::Event event; + + int x = 0; + + auto task1 = [&]() -> async::Task<> { + EXPECT_EQ(x, 0); + co_await event; + EXPECT_EQ(x, 1); + x = 2; + }; + + auto task2 = [&]() -> async::Task<> { + EXPECT_EQ(x, 0); + co_await event; + EXPECT_EQ(x, 2); + x = 3; + }; + + auto main = [&]() -> async::Task<> { + x = 1; + event.set(); + co_return; + }; + + async::run(task1(), task2(), main()); +} + +} // namespace + +} // namespace clice::testing diff --git a/unittests/Async/FileSystem.cpp b/unittests/Async/FileSystem.cpp new file mode 100644 index 00000000..b4bc9102 --- /dev/null +++ b/unittests/Async/FileSystem.cpp @@ -0,0 +1,46 @@ +#include "Test/Test.h" +#include "Async/Async.h" +#include "Support/FileSystem.h" + +namespace clice::testing { + +namespace { + +TEST(Async, FileSystemRead) { + auto path = fs::createTemporaryFile("prefix", "suffix"); + EXPECT_TRUE(path.has_value()); + + auto result = fs::write(*path, "hello"); + EXPECT_TRUE(result.has_value()); + + auto main = [&] -> async::Task<> { + auto content = co_await async::fs::read(*path); + EXPECT_TRUE(content.has_value()); + EXPECT_EQ(*content, "hello"); + }; + + async::run(main()); +} + +TEST(Async, FileSystemWrite) { + auto path = fs::createTemporaryFile("prefix", "suffix"); + EXPECT_TRUE(path.has_value()); + + auto main = [&] -> async::Task<> { + char buffer[] = "hello"; + + auto result = co_await async::fs::write(*path, buffer, 5); + EXPECT_TRUE(result.has_value()); + }; + + async::run(main()); + + auto content = fs::read(*path); + EXPECT_TRUE(content.has_value()); + EXPECT_EQ(*content, "hello"); +} + +} // namespace + +} // namespace clice::testing + diff --git a/unittests/Async/Gather.cpp b/unittests/Async/Gather.cpp new file mode 100644 index 00000000..daa8e3c3 --- /dev/null +++ b/unittests/Async/Gather.cpp @@ -0,0 +1,45 @@ +#include "Test/Test.h" +#include "Async/Async.h" + +namespace clice::testing { + +namespace { + +TEST(Async, GatherPack) { + int x = 0; + + auto task_gen = [&]() -> async::Task { + co_await async::sleep(100); + x += 1; + co_return x; + }; + + auto [a, b, c] = async::run(task_gen(), task_gen(), task_gen()); + + EXPECT_EQ(a, 1); + EXPECT_EQ(b, 2); + EXPECT_EQ(c, 3); +} + +TEST(Async, GatherRange) { + std::vector args; + for(int i = 0; i < 30; ++i) { + args.push_back(i); + } + + std::vector results; + + auto task_gen = [&](int x) -> async::Task<> { + co_await async::sleep(10); + results.push_back(x); + }; + + auto core = async::gather(args, task_gen); + async::run(core); + + EXPECT_EQ(args, results); +} + +} // namespace + +} // namespace clice::testing diff --git a/unittests/Async/Lock.cpp b/unittests/Async/Lock.cpp new file mode 100644 index 00000000..780f2260 --- /dev/null +++ b/unittests/Async/Lock.cpp @@ -0,0 +1,47 @@ +#include "Test/Test.h" +#include "Async/Async.h" + +namespace clice::testing { + +namespace { + +TEST(Async, Lock) { + async::Lock lock; + + int x = 0; + + auto task1 = [&]() -> async::Task<> { + auto guard = co_await lock.try_lock(); + co_await async::sleep(5); + EXPECT_EQ(x, 0); + co_await async::sleep(10); + EXPECT_EQ(x, 0); + co_await async::sleep(5); + x = 1; + }; + + auto task2 = [&]() -> async::Task<> { + auto guard = co_await lock.try_lock(); + co_await async::sleep(5); + EXPECT_EQ(x, 1); + co_await async::sleep(5); + EXPECT_EQ(x, 1); + co_await async::sleep(10); + x = 2; + }; + + auto task3 = [&]() -> async::Task<> { + auto guard = co_await lock.try_lock(); + co_await async::sleep(10); + EXPECT_EQ(x, 2); + co_await async::sleep(5); + EXPECT_EQ(x, 2); + co_await async::sleep(5); + }; + + async::run(task1(), task2(), task3()); +} + +} // namespace + +} // namespace clice::testing diff --git a/unittests/Async/Sleep.cpp b/unittests/Async/Sleep.cpp new file mode 100644 index 00000000..c19e2d4f --- /dev/null +++ b/unittests/Async/Sleep.cpp @@ -0,0 +1,24 @@ +#include "Test/Test.h" +#include "Async/Async.h" + +namespace clice::testing { + +namespace { + +TEST(Async, Sleep) { + int x = 1; + auto task_gen = [&]() -> async::Task<> { + x = 2; + co_await async::sleep(100); + x = 3; + }; + + auto task = task_gen(); + async::run(task); + + EXPECT_EQ(x, 3); +} + +} // namespace + +} // namespace clice::testing diff --git a/unittests/Async/Task.cpp b/unittests/Async/Task.cpp index 12fbfdaa..1eaad076 100644 --- a/unittests/Async/Task.cpp +++ b/unittests/Async/Task.cpp @@ -1,90 +1,83 @@ #include "Test/Test.h" #include "Async/Async.h" +#include "Async/ThreadPool.h" namespace clice::testing { namespace { -TEST(Async, TaskAwait) { - static auto my_task1 = []() -> async::Task { +TEST(Async, Run) { + async::run(); +} + +TEST(Async, TaskSchedule) { + auto task_gen = []() -> async::Task { co_return 1; }; - static auto my_task2 = []() -> async::Task { - auto result = co_await my_task1(); - co_return result + 1; - }; + auto task = task_gen(); + task.schedule(); - static auto my_task3 = []() -> async::Task { - auto result = co_await my_task2(); - co_return result + 1; - }; + async::run(); - auto [result] = async::run(my_task3()); - EXPECT_EQ(result, 3); + EXPECT_TRUE(task.done()); + EXPECT_EQ(task.result(), 1); } TEST(Async, TaskDispose) { - // static int x = 1; - // - // struct X { - // ~X() { - // x += 1; - // } - //}; - // - // auto my_task = [&]() -> async::Task<> { - // X x; - // co_await async::sleep(std::chrono::milliseconds(300)); - //}; - // - // auto task = my_task(); - // task.schedule(); - // task.dispose(); - // - // async::run(); - // - // EXPECT_EQ(x, 2); - // - // auto main = [&]() -> async::Task<> { - // auto task = my_task(); - // task.schedule(); - // co_await async::sleep(std::chrono::milliseconds(100)); - // task.cancel(); - // task.dispose(); - //}; - // - // auto p = main(); - // p.schedule(); - // - // async::run(); - // - // EXPECT_EQ(x, 3); + static int x = 1; + + struct X { + ~X() { + x += 1; + } + }; + + auto my_task = [&]() -> async::Task<> { + X x; + co_await async::sleep(300); + }; + + auto task = my_task(); + task.schedule(); + task.dispose(); + async::run(); + + EXPECT_EQ(x, 2); + + auto main = [&]() -> async::Task<> { + auto task = my_task(); + task.schedule(); + co_await async::sleep(100); + task.cancel(); + task.dispose(); + }; + + async::run(main()); + + EXPECT_EQ(x, 3); } TEST(Async, TaskCancel) { - // int x = 1; - // - // auto my_task = [&]() -> async::Task<> { - // x = 2; - // co_await async::sleep(std::chrono::milliseconds(300)); - // x = 3; - //}; - // - // auto main = [&]() -> async::Task<> { - // auto task = my_task(); - // task.schedule(); - // co_await async::sleep(std::chrono::milliseconds(100)); - // task.cancel(); - // task.dispose(); - //}; - // - // auto p = main(); - // p.schedule(); - // - // async::run(); - // - // EXPECT_EQ(x, 2); + int x = 1; + + auto my_task = [&]() -> async::Task<> { + x = 2; + co_await async::sleep(300); + x = 3; + }; + + auto main = [&]() -> async::Task<> { + auto task = my_task(); + task.schedule(); + co_await async::sleep(100); + task.cancel(); + task.dispose(); + }; + + async::run(main()); + + EXPECT_EQ(x, 2); } } // namespace diff --git a/unittests/Async/ThreadPool.cpp b/unittests/Async/ThreadPool.cpp new file mode 100644 index 00000000..b85a9d16 --- /dev/null +++ b/unittests/Async/ThreadPool.cpp @@ -0,0 +1,45 @@ +#include "Test/Test.h" +#include "Async/Async.h" + +namespace clice::testing { + +namespace { + +TEST(Async, ThreadPool) { + auto task_gen = []() -> async::Result { + co_return co_await async::submit([]() { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + return std::this_thread::get_id(); + }); + }; + + auto task1 = task_gen(); + auto task2 = task_gen(); + auto task3 = task_gen(); + + task1.schedule(); + task2.schedule(); + task3.schedule(); + + async::run(); + + EXPECT_TRUE(task1.done()); + EXPECT_TRUE(task2.done()); + EXPECT_TRUE(task3.done()); + + auto id1 = task1.result(); + auto id2 = task2.result(); + auto id3 = task3.result(); + + EXPECT_TRUE(id1.has_value()); + EXPECT_TRUE(id2.has_value()); + EXPECT_TRUE(id3.has_value()); + + EXPECT_NE(*id1, *id2); + EXPECT_NE(*id1, *id3); + EXPECT_NE(*id2, *id3); +} + +} // namespace + +} // namespace clice::testing diff --git a/unittests/Index/Serialization.cpp b/unittests/Index/Serialization.cpp index 12ef2c7a..c51b15c5 100644 --- a/unittests/Index/Serialization.cpp +++ b/unittests/Index/Serialization.cpp @@ -42,19 +42,18 @@ void $(2)foo() {} auto json = index.toJSON(); - llvm::SmallString<128> path; - auto error = fs::createTemporaryFile("index", ".sidx", path); - ASSERT_FALSE(error); + auto path = fs::createTemporaryFile("index", ".sidx"); + ASSERT_TRUE(path.has_value()); { std::error_code ec; - llvm::raw_fd_ostream file(path, ec); + llvm::raw_fd_ostream file(*path, ec); ASSERT_FALSE(ec); file.write(static_cast(index.base), index.size); } { - auto file = llvm::MemoryBuffer::getFile(path); + auto file = llvm::MemoryBuffer::getFile(*path); ASSERT_TRUE(bool(file)); auto& buffer = file.get(); auto size = buffer->getBufferSize();