blob: f2fcd778d5657f40c90adf4785581425cebe5bab [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 <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(fxbug.dev/65753) 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 to notify us back.
{
std::unique_lock lock{mutex_};
condvar_.wait(lock, [this] { return this->MessageHandled(); });
}
}
Message Receive() {
// Wait for, and receive, a message from the sender.
Message message = [this] {
std::unique_lock lock{mutex_};
condvar_.wait(lock, [this] { return this->HasMessage(); });
Message message = std::move(*message_);
message_ = std::nullopt;
return message;
}();
condvar_.notify_one();
return message;
}
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 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);
}
private:
static void* Handler(void* self) {
auto thread = static_cast<Thread*>(self);
thread->Run();
return nullptr;
}
void Run() {
for (;;) {
Operation op = gate_->Receive();
switch (op) {
case kDetach: {
int ret = pthread_detach(thread_);
// We are already detached, and testing that this returns
// EINVAL. Specifically
ASSERT_EQ(ret, EINVAL);
break;
}
case kExit: {
pthread_exit(nullptr);
break;
}
}
}
}
pthread_t thread_;
Gate<Operation>* gate_;
};
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_FAILURES();
// 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_FAILURES(gate.Send(kDetach));
}
gate.Send(kExit);
}
} // namespace