blob: 344b327a4d690ba3601df8110587589448982bad [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_async2/internal/logging.h"
// logging.h must be included first
#include <mutex>
#include "pw_allocator/allocator.h"
#include "pw_allocator/internal/control_block.h"
#include "pw_assert/check.h"
#include "pw_async2/dispatcher.h"
#include "pw_async2/internal/config.h"
#include "pw_async2/task.h"
#include "pw_async2_private/yield.h"
#include "pw_log/log.h"
#include "pw_preprocessor/compiler.h"
#include "pw_thread/sleep.h"
#define PW_TASK_NAME_FMT() PW_LOG_TOKEN_FMT("pw_async2")
namespace pw::async2 {
Task::~Task() {
PW_DCHECK_INT_EQ(
state_,
State::kUnposted,
"Tasks must be deregistered before they are destroyed; "
"the " PW_TASK_NAME_FMT() " task is still posted to a dispatcher",
name_);
}
void Task::RemoveAllWakersLocked() {
while (!wakers_.empty()) {
Waker& waker = wakers_.front();
wakers_.pop_front();
waker.ClearTask();
}
}
bool Task::IsRegistered() const {
std::lock_guard lock(internal::lock());
return state_ != State::kUnposted;
}
void Task::Deregister() {
while (!TryDeregister()) {
internal::YieldToAnyThread();
}
}
bool Task::TryDeregister() {
// This function does not use std::lock_guard since the UnpostAndReleaseRef
// function releases the lock. Lock correctness is ensured by Clang's
// thread safety annotations.
internal::lock().lock();
switch (state_) {
case State::kUnposted:
internal::lock().unlock();
return true;
case State::kSleeping:
dispatcher_->RemoveSleepingTaskLocked(*this);
break;
case State::kWokenWhileRunning:
dispatcher_->RemoveWokenTaskLocked(*this);
[[fallthrough]];
case State::kRunning:
// Mark the task as deregistered. The dispatcher thread running the task
// completes deregistration and moves the task to the unposted state.
state_ = State::kDeregisteredButRunning;
[[fallthrough]];
case State::kDeregisteredButRunning:
internal::lock().unlock();
return false;
case State::kWoken:
dispatcher_->RemoveWokenTaskLocked(*this);
break;
}
dispatcher_->DeregisterTask(*this);
return true;
}
void Task::Join() {
while (true) {
{
std::lock_guard lock(internal::lock());
if (state_ == State::kUnposted) {
return;
}
}
internal::YieldToAnyThread();
}
}
allocator::internal::ControlBlock* Task::Unpost() {
state_ = State::kUnposted;
dispatcher_ = nullptr;
RemoveAllWakersLocked();
return std::exchange(control_block_, nullptr);
}
void Task::UnpostAndReleaseRef() {
allocator::internal::ControlBlock* const control_block = Unpost();
internal::lock().unlock();
if (control_block != nullptr) {
ReleaseSharedRef(control_block);
}
}
void Task::UnpostAndReleaseRefFromDispatcherDestructor() {
allocator::internal::ControlBlock* const control_block = Unpost();
if (control_block != nullptr) {
internal::lock().unlock();
ReleaseSharedRef(control_block);
internal::lock().lock();
}
}
// Called by the dispatcher to run this task.
RunTaskResult Task::RunInDispatcher() {
PW_LOG_DEBUG("Dispatcher running task " PW_TASK_NAME_FMT() ":%p",
name_,
static_cast<const void*>(this));
// Reset the waker requirement flag before every run.
waker_requirement_ = kWakerNeeded;
// The task is pended without the lock held.
const bool complete = Pend(*this).IsReady();
// This function does not use std::lock_guard since the UnpostAndReleaseRef
// function releases the lock. Lock correctness is ensured by Clang's
// thread safety annotations.
internal::lock().lock();
if (complete || state_ == State::kDeregisteredButRunning) {
switch (state_) {
case State::kUnposted:
// Invalid state -- if unregistered from another thread, the state
// becomes kDeregisteredButRunning.
case State::kSleeping:
// If the task is sleeping, then another thread must have run the
// dispatcher, which is invalid.
PW_DASSERT(false);
PW_UNREACHABLE;
case State::kRunning:
case State::kDeregisteredButRunning:
break;
case State::kWokenWhileRunning:
case State::kWoken:
dispatcher_->RemoveWokenTaskLocked(*this);
break;
}
PW_LOG_DEBUG("Task " PW_TASK_NAME_FMT() ":%p completed",
name_,
static_cast<const void*>(this));
UnpostAndReleaseRef();
return RunTaskResult::kCompleted;
}
if (state_ == State::kRunning) {
PW_LOG_DEBUG(
"Dispatcher adding task " PW_TASK_NAME_FMT() ":%p to sleep queue",
name_,
static_cast<const void*>(this));
if (waker_requirement_ == kWakerNeeded) {
PW_CHECK(!wakers_.empty(),
"Task " PW_TASK_NAME_FMT()
":%p returned Pending() without registering a waker",
name_,
static_cast<const void*>(this));
state_ = State::kSleeping;
dispatcher_->AddSleepingTaskLocked(*this);
} else {
state_ = State::kUnposted;
dispatcher_ = nullptr;
}
} else if (state_ == State::kWokenWhileRunning) {
state_ = State::kWoken;
}
internal::lock().unlock();
PW_LOG_DEBUG(
"Task " PW_TASK_NAME_FMT() ":%p finished its run and is still pending",
name_,
static_cast<const void*>(this));
return RunTaskResult::kActive;
}
void Task::Wake() {
PW_LOG_DEBUG("Dispatcher waking task " PW_TASK_NAME_FMT() ":%p",
name_,
static_cast<const void*>(this));
switch (state_) {
case State::kUnposted:
PW_UNREACHABLE;
case State::kSleeping:
dispatcher_->RemoveSleepingTaskLocked(*this);
// Wake away!
state_ = State::kWoken;
break;
case State::kRunning:
// Wake again to indicate that this task should be run once more,
// as the state of the world may have changed since the task
// started running.
state_ = State::kWokenWhileRunning;
break;
case State::kDeregisteredButRunning:
internal::lock().unlock();
return; // Do nothing: will be deregistered when the run finishes
case State::kWokenWhileRunning:
case State::kWoken:
// Do nothing: this has already been woken.
internal::lock().unlock();
return;
}
dispatcher_->AddWokenTaskLocked(*this);
dispatcher_->Wake();
}
} // namespace pw::async2