| // Copyright 2022 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "dawn/native/AsyncTask.h" |
| |
| #include <utility> |
| |
| #include "dawn/platform/DawnPlatform.h" |
| |
| namespace dawn::native { |
| |
| AsyncTask::State::State(AsyncTaskFunction task) : task(task) { |
| DAWN_ASSERT(task); |
| } |
| |
| AsyncTask::AsyncTask(AsyncTaskManager* taskManager, AsyncTaskFunction task) |
| : mTaskManager(taskManager), mState(task) { |
| DAWN_ASSERT(mTaskManager); |
| } |
| |
| bool AsyncTask::IsCompleted() const { |
| return mState.Use([](auto state) { return state->state != AsyncTaskState::Pending; }); |
| } |
| |
| void AsyncTask::Wait() { |
| mState.Use<NotifyType::None>([](auto state) { |
| state.Wait([](auto& x) { return x.state == AsyncTaskState::CompletedCallbacks; }); |
| }); |
| } |
| |
| void AsyncTask::AddCompletionCallback(AsyncTaskCompletionCallback completionCallback) { |
| bool completeCallbackNow = false; |
| mState.Use<NotifyType::None>([&](auto state) { |
| if (state->state != AsyncTaskState::Pending) { |
| completeCallbackNow = true; |
| return; |
| } |
| state->completionCallbacks.push_back(completionCallback); |
| }); |
| |
| // Call callbacks without holding the lock if the task was already complete. |
| if (completeCallbackNow) { |
| completionCallback(); |
| } |
| } |
| |
| void AsyncTask::Run() { |
| // To ensure we only run the task once, we synchronize it with the lock, move it out when it |
| // exists, and call it without holding the lock. |
| AsyncTaskFunction task = nullptr; |
| mState.Use<NotifyType::None>([&task](auto state) { |
| task = std::move(state->task); |
| state->task = nullptr; |
| }); |
| DAWN_ASSERT(task); |
| |
| // Complete the task and update the state of the task manager. Note we need to make sure we |
| // update the state of the task manager before setting the task to Complete to ensure that at |
| // teardown when the task manager is waiting on the tasks, that the tasks no longer have a |
| // reference to the manager anymore. |
| task(); |
| mTaskManager.ExtractAsDangling()->mTasks.Use([this](auto tasks) { tasks->erase(this); }); |
| |
| // Update the state and grab the completion callbacks to call them outside the lock scope. Note |
| // that we don't notify other threads yet since the callbacks haven't completed. |
| std::vector<AsyncTaskCompletionCallback> completionCallbacks; |
| mState.Use<NotifyType::None>([&completionCallbacks](auto state) { |
| state->state = AsyncTaskState::CompletedTask; |
| |
| completionCallbacks = std::move(state->completionCallbacks); |
| state->completionCallbacks.clear(); |
| }); |
| for (auto completionCallback : completionCallbacks) { |
| completionCallback(); |
| } |
| |
| // Finally notify waiting threads. |
| mState.Use<NotifyType::All>( |
| [](auto state) { state->state = AsyncTaskState::CompletedCallbacks; }); |
| } |
| |
| ErrorGeneratingAsyncTask::ErrorGeneratingAsyncTask(AsyncTaskManager* taskManager, |
| std::function<MaybeError()> task) |
| : AsyncTask(taskManager, [this, task] { |
| // Wrap the task which returns a MaybeError in a void function and store the error in a |
| // member. |
| MaybeError taskResult = task(); |
| if (taskResult.IsError()) { |
| mErrorData = taskResult.AcquireError(); |
| } |
| }) {} |
| |
| bool ErrorGeneratingAsyncTask::IsSuccess() const { |
| DAWN_ASSERT(IsCompleted()); |
| return mErrorData == nullptr; |
| } |
| |
| bool ErrorGeneratingAsyncTask::IsError() const { |
| DAWN_ASSERT(IsCompleted()); |
| return mErrorData != nullptr; |
| } |
| |
| InternalErrorType ErrorGeneratingAsyncTask::GetErrorType() const { |
| return mErrorData ? mErrorData->GetType() : InternalErrorType::None; |
| } |
| |
| std::unique_ptr<ErrorData> ErrorGeneratingAsyncTask::AcquireError() { |
| DAWN_ASSERT(IsCompleted()); |
| return std::move(mErrorData); |
| } |
| |
| AsyncTaskManager::AsyncTaskManager(dawn::platform::WorkerTaskPool* workerTaskPool) |
| : mWorkerTaskPool(workerTaskPool) {} |
| |
| AsyncTaskManager::~AsyncTaskManager() { |
| // Pending tasks call back into this task manager. Make sure they all finish before destructing. |
| WaitAllPendingTasks(); |
| } |
| |
| void AsyncTaskManager::PostConstructedTask(Ref<AsyncTask> asyncTask) { |
| // Insert the new task and send it off to the workpool to have it completed. |
| mTasks.Use([&asyncTask](auto tasks) { tasks->emplace(asyncTask); }); |
| mWorkerTaskPool->PostWorkerTask(RunTask, asyncTask.Get()); |
| } |
| |
| void AsyncTaskManager::WaitAllPendingTasks() { |
| TaskSet allTasks; |
| mTasks.Use([&allTasks](auto tasks) { allTasks.swap(*tasks); }); |
| |
| for (auto& task : allTasks) { |
| task->Wait(); |
| } |
| } |
| |
| bool AsyncTaskManager::HasPendingTasks() const { |
| return mTasks.Use([](auto tasks) { return !tasks->empty(); }); |
| } |
| |
| void AsyncTaskManager::RunTask(void* task) { |
| // Note that we create a new Ref<AsyncTask> here because upon completion, we erase the Ref that |
| // the AsyncTaskManager holds which may result in dropping the last reference before the |
| // completion of this function otherwise. By explicitly creating a Ref here, we ensure that the |
| // last reference is only dropped after the scope of this function. |
| Ref<AsyncTask> asyncTask = static_cast<AsyncTask*>(task); |
| asyncTask->Run(); |
| } |
| |
| } // namespace dawn::native |