blob: b08a2cc0fad882d25d73579b8ad609c88e87bc5d [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <object/state_tracker.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#ifndef _KERNEL
#include <zircon/assert.h>
#define DEBUG_ASSERT(args...) ZX_DEBUG_ASSERT(args)
#define thread_reschedule() ((void)0)
#endif
using fbl::AutoLock;
namespace {
template <typename Func>
StateObserver::Flags CancelWithFunc(StateTracker::ObserverList* observers,
fbl::Mutex* observer_lock, Func f) {
StateObserver::Flags flags = 0;
StateTracker::ObserverList obs_to_remove;
{
AutoLock lock(observer_lock);
for (auto it = observers->begin(); it != observers->end();) {
StateObserver::Flags it_flags = f(it.CopyPointer());
flags |= it_flags;
if (it_flags & StateObserver::kNeedRemoval) {
auto to_remove = it;
++it;
obs_to_remove.push_back(observers->erase(to_remove));
} else {
++it;
}
}
}
while (!obs_to_remove.is_empty()) {
obs_to_remove.pop_front()->OnRemoved();
}
// We've processed the removal flag, so strip it
return flags & (~StateObserver::kNeedRemoval);
}
} // namespace
void StateTracker::AddObserver(StateObserver* observer, const StateObserver::CountInfo* cinfo) {
canary_.Assert();
DEBUG_ASSERT(observer != nullptr);
StateObserver::Flags flags;
{
AutoLock lock(&lock_);
flags = observer->OnInitialize(signals_, cinfo);
if (!(flags & StateObserver::kNeedRemoval))
observers_.push_front(observer);
}
if (flags & StateObserver::kNeedRemoval)
observer->OnRemoved();
if (flags & StateObserver::kWokeThreads)
thread_reschedule();
}
void StateTracker::RemoveObserver(StateObserver* observer) {
canary_.Assert();
AutoLock lock(&lock_);
DEBUG_ASSERT(observer != nullptr);
observers_.erase(*observer);
}
bool StateTracker::Cancel(Handle* handle) {
canary_.Assert();
StateObserver::Flags flags = CancelWithFunc(&observers_, &lock_, [handle](StateObserver* obs) {
return obs->OnCancel(handle);
});
// We could request a reschedule if kWokeThreads is asserted,
// but cancellation is not likely to benefit from aggressive
// rescheduling.
return flags & StateObserver::kHandled;
}
bool StateTracker::CancelByKey(Handle* handle, const void* port, uint64_t key) {
canary_.Assert();
StateObserver::Flags flags = CancelWithFunc(&observers_, &lock_, [handle, port, key](StateObserver* obs) {
return obs->OnCancelByKey(handle, port, key);
});
// We could request a reschedule if kWokeThreads is asserted,
// but cancellation is not likely to benefit from aggressive
// rescheduling.
return flags & StateObserver::kHandled;
}
void StateTracker::UpdateState(zx_signals_t clear_mask,
zx_signals_t set_mask) {
canary_.Assert();
StateObserver::Flags flags;
ObserverList obs_to_remove;
{
AutoLock lock(&lock_);
auto previous_signals = signals_;
signals_ &= ~clear_mask;
signals_ |= set_mask;
if (previous_signals == signals_)
return;
flags = UpdateInternalLocked(&obs_to_remove, signals_);
}
while (!obs_to_remove.is_empty()) {
obs_to_remove.pop_front()->OnRemoved();
}
if (flags & StateObserver::kWokeThreads)
thread_reschedule();
}
void StateTracker::UpdateLastHandleSignal(uint32_t* count) {
canary_.Assert();
if (count == nullptr)
return;
StateObserver::Flags flags = 0;
ObserverList obs_to_remove;
{
AutoLock lock(&lock_);
auto previous_signals = signals_;
// We assume here that the value pointed by |count| can mutate by
// other threads.
signals_ = (*count == 1u) ?
signals_ | ZX_SIGNAL_LAST_HANDLE : signals_ & ~ZX_SIGNAL_LAST_HANDLE;
if (previous_signals == signals_)
return;
flags = UpdateInternalLocked(&obs_to_remove, signals_);
}
while (!obs_to_remove.is_empty()) {
obs_to_remove.pop_front()->OnRemoved();
}
if (flags & StateObserver::kWokeThreads)
thread_reschedule();
}
zx_status_t StateTracker::SetCookie(CookieJar* cookiejar, zx_koid_t scope, uint64_t cookie) {
if (cookiejar == nullptr)
return ZX_ERR_NOT_SUPPORTED;
AutoLock lock(&lock_);
if (cookiejar->scope_ == ZX_KOID_INVALID) {
cookiejar->scope_ = scope;
cookiejar->cookie_ = cookie;
return ZX_OK;
}
if (cookiejar->scope_ == scope) {
cookiejar->cookie_ = cookie;
return ZX_OK;
}
return ZX_ERR_ACCESS_DENIED;
}
zx_status_t StateTracker::GetCookie(CookieJar* cookiejar, zx_koid_t scope, uint64_t* cookie) {
if (cookiejar == nullptr)
return ZX_ERR_NOT_SUPPORTED;
AutoLock lock(&lock_);
if (cookiejar->scope_ == scope) {
*cookie = cookiejar->cookie_;
return ZX_OK;
}
return ZX_ERR_ACCESS_DENIED;
}
zx_status_t StateTracker::InvalidateCookie(CookieJar* cookiejar) {
if (cookiejar == nullptr)
return ZX_ERR_NOT_SUPPORTED;
AutoLock lock(&lock_);
cookiejar->scope_ = ZX_KOID_KERNEL;
return ZX_OK;
}
StateObserver::Flags StateTracker::UpdateInternalLocked(ObserverList* obs_to_remove, zx_signals_t signals) {
StateObserver::Flags flags = 0;
for (auto it = observers_.begin(); it != observers_.end();) {
StateObserver::Flags it_flags = it->OnStateChange(signals);
flags |= it_flags;
if (it_flags & StateObserver::kNeedRemoval) {
auto to_remove = it;
++it;
obs_to_remove->push_back(observers_.erase(to_remove));
} else {
++it;
}
}
// Filter out NeedRemoval flag because we processed that here
return flags & (~StateObserver::kNeedRemoval);
}