| // 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 "src/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 |