blob: 72e3d776058cea53236bd81d3322c14d2bf685dd [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/zircon-internal/unique-backtrace.h>
#include <lib/zx/result.h>
#include <zircon/syscalls.h>
#include <tuple>
#include "thread-list.h"
#include "thread-storage.h"
#include "thread.h"
namespace LIBC_NAMESPACE_DECL {
using ThreadState = zxr_thread_t::State;
zx::result<intptr_t> ThreadJoin(Thread& thread) {
auto wait = [&thread](ThreadState old_state) {
do {
switch (_zx_futex_wait(thread.zxr_thread.StateFutex(), static_cast<int>(old_state),
ZX_HANDLE_INVALID, ZX_TIME_INFINITE)) {
case ZX_ERR_BAD_STATE: // Never blocked because it had changed.
case ZX_OK: // Woke up because it might have changed.
old_state = thread.zxr_thread.state.load(std::memory_order_acquire);
break;
default:
CRASH_WITH_UNIQUE_BACKTRACE();
}
// Wait until we reach the DONE state, even if we observe the
// intermediate EXITING state.
} while (old_state == ThreadState::JOINED || old_state == ThreadState::EXITING);
if (old_state != ThreadState::DONE)
CRASH_WITH_UNIQUE_BACKTRACE();
};
// Try to claim the join slot on this thread.
if (auto old_state = thread.zxr_thread.JoinOrDetachState(ThreadState::JOINED); !old_state) {
wait(ThreadState::JOINED);
} else {
switch (*old_state) {
case ThreadState::JOINED:
case ThreadState::DETACHED:
return zx::error{ZX_ERR_INVALID_ARGS};
case ThreadState::EXITING:
// Since it is undefined to call ThreadJoin on a thread that has
// already been detached or joined, we assume the state prior to
// EXITING was JOINABLE, and act as if we had successfully transitioned
// to JOINED.
wait(ThreadState::EXITING);
[[fallthrough]];
case ThreadState::DONE:
break;
default:
CRASH_WITH_UNIQUE_BACKTRACE();
}
}
// Take the handle and synchronize with readers. Then close the handle.
if (zx::thread handle = thread.zxr_thread.TakeHandle(ThreadState::DONE); !handle) {
CRASH_WITH_UNIQUE_BACKTRACE();
}
// Now the Thread object can be removed from the list of all threads.
AllThreads().erase(thread);
// Extract the return value passed to ThreadExit().
intptr_t value = std::exchange(thread.join_value, 0);
// Move the ThreadStorage out of the Thread itself; it's inside that storage.
// Then immediately let the storage object die, freeing the thread block.
std::ignore = ThreadStorage::FromThread(thread, true);
return zx::ok(value);
}
} // namespace LIBC_NAMESPACE_DECL