blob: ed2de03e2a63171fb6e8b96480d92ca78ad43dc9 [file] [log] [blame]
// Copyright 2020 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/sync/completion.h>
#include <pthread.h>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <zxtest/zxtest.h>
// While detaching or joining a pthread_t or thrd_t multiple times is
// not well defined, our libc does detect this behavior in some
// circumstances.
//
// TODO(https://fxbug.dev/42144488) precisely define our behavior in this sort of
// situation.
namespace {
// A gate which lets one side send a message to the other, and for the
// sender to wait on the other side to finish.
template <typename Message>
class Gate {
public:
void Send(Message message) {
// Send the message.
{
std::unique_lock lock{mutex_};
message_ = message;
}
condvar_.notify_one();
// Wait for the receiver ack that it has processed the command.
{
std::unique_lock lock{mutex_};
condvar_.wait(lock, [this] { return this->MessageHandled(); });
}
}
Message PeekMessage() {
// Return a copy of any pending message. Do not ack that the message has
// been processed yet.
std::unique_lock lock{mutex_};
condvar_.wait(lock, [this] { return this->HasMessage(); });
ZX_DEBUG_ASSERT(message_.has_value());
return message_.value();
}
void AckMessage() {
// Clear out any pending message and poke the condvar, signalling to the
// transmitter that they may process the results from the previous message,
// and send the next message.
{
std::unique_lock lock{mutex_};
message_ = std::nullopt;
}
condvar_.notify_one();
}
private:
bool HasMessage() const { return message_.has_value(); }
bool MessageHandled() const { return !message_.has_value(); }
std::mutex mutex_;
std::condition_variable condvar_;
std::optional<Message> message_ = std::nullopt;
};
enum class Operation {
kExit,
kDetach,
};
// A thread which is created in the detached state, and which
// repeatedly waits to either detach or exit.
class Thread {
public:
explicit Thread(Gate<Operation>* gate) : gate_(gate) {
pthread_attr_t attrs;
int ret = pthread_attr_init(&attrs);
ASSERT_EQ(ret, 0);
ret = pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
ASSERT_EQ(ret, 0);
ret = pthread_create(&thread_, &attrs, &Thread::Handler, this);
ASSERT_EQ(ret, 0);
ret = pthread_attr_destroy(&attrs);
ASSERT_EQ(ret, 0);
}
void WaitForThreadExited() { sync_completion_wait(&thread_exited_, ZX_TIME_INFINITE); }
private:
static void* Handler(void* self) {
auto thread = static_cast<Thread*>(self);
thread->Run();
// Remember to ack our last received message. It was either a kExit
// command, or a kDetach command in the case that the test failed and
// aborted early. Either way, we do not want to leave the main thread
// waiting forever.
thread->gate_->AckMessage();
sync_completion_signal(&thread->thread_exited_);
return nullptr;
}
void Run() {
for (;;) {
Operation op = gate_->PeekMessage();
if (op == Operation::kExit) {
return;
}
int ret = pthread_detach(thread_);
// We are already detached, and testing that this returns
// EINVAL. Specifically
ASSERT_EQ(ret, EINVAL);
gate_->AckMessage();
}
}
pthread_t thread_;
Gate<Operation>* gate_;
sync_completion_t thread_exited_;
};
TEST(PthreadDetach, Idempotent) {
Gate<Operation> gate;
// Create a |Thread|, and assert that the construction of the
// pthread_t had no fatal errors.
Thread thread{&gate};
ASSERT_NO_FATAL_FAILURE();
// Now that our thread has been successfully created, run our main test from
// within the scope of the lambda. If the test encounters a fatal failure and
// we need to bail out early, we still need to wait for the thread to have
// exited, or we risk a stack-use-after-free situation.
auto run_test = [&gate]() {
// Ten is an arbitrary number greater than 1, to exercise the
// behavior a few times.
for (int count = 0; count < 10; ++count) {
ASSERT_NO_FATAL_FAILURE(gate.Send(Operation::kDetach));
}
gate.Send(Operation::kExit);
};
run_test();
// Note, do not skip this waiting step. It is not safe to remove the Thread
// and Gate instances from the stack yet, even after the acknowledgement of
// the last message. While it would be nice to use a join for this, we have
// detached this thread, so we will have to make due with a completion.
// See the write-up in https://fxbug.dev/42149486 for details.
thread.WaitForThreadExited();
}
} // namespace