[threading linux] Implement suspendable pthreads

Use real-time event interrupt to communicate suspend in timely manner.
Use conditional_variable to implement suspend wait and resume trigger.

Ignore real-time event 36 in .gdbinit which is used to signal suspend.

Test suspending threads.
This commit is contained in:
Sandy Carter 2019-01-11 14:47:59 -05:00 committed by Rick Gibbed
parent b2912e7891
commit 4397f25325
3 changed files with 69 additions and 13 deletions

View File

@ -4,3 +4,5 @@ handle SIG34 nostop noprint
handle SIG35 nostop noprint
# Ignore PosixThread exit event
handle SIG32 nostop noprint
# Ignore PosixThread suspend event
handle SIG36 nostop noprint

View File

@ -759,8 +759,29 @@ TEST_CASE("Create and Run Thread", "Thread") {
}
TEST_CASE("Test Suspending Thread", "Thread") {
// TODO(bwrsandman): Test suspension and resume
REQUIRE(true);
std::unique_ptr<Thread> thread;
WaitResult result;
Thread::CreationParameters params = {};
auto func = [] { Sleep(20ms); };
// Create initially suspended
params.create_suspended = true;
thread = threading::Thread::Create(params, func);
result = threading::Wait(thread.get(), false, 50ms);
REQUIRE(result == threading::WaitResult::kTimeout);
thread->Resume();
result = threading::Wait(thread.get(), false, 50ms);
REQUIRE(result == threading::WaitResult::kSuccess);
params.create_suspended = false;
// Create and then suspend
thread = threading::Thread::Create(params, func);
thread->Suspend();
result = threading::Wait(thread.get(), false, 50ms);
REQUIRE(result == threading::WaitResult::kTimeout);
thread->Resume();
result = threading::Wait(thread.get(), false, 50ms);
REQUIRE(result == threading::WaitResult::kSuccess);
}
TEST_CASE("Test Thread QueueUserCallback", "Thread") {

View File

@ -38,7 +38,7 @@ inline timespec DurationToTimeSpec(
// This implementation uses the SIGRTMAX - SIGRTMIN to signal to a thread
// gdb tip, for SIG = SIGRTMIN + SignalType : handle SIG nostop
// lldb tip, for SIG = SIGRTMIN + SignalType : process handle SIG -s false
enum class SignalType { kHighResolutionTimer, kTimer, k_Count };
enum class SignalType { kHighResolutionTimer, kTimer, kThreadSuspend, k_Count };
int GetSystemSignal(SignalType num) {
auto result = SIGRTMIN + static_cast<int>(num);
@ -430,6 +430,7 @@ class PosixCondition<Timer> : public PosixConditionBase {
struct ThreadStartData {
std::function<void()> start_routine;
bool create_suspended;
Thread* thread_obj;
};
@ -438,6 +439,7 @@ class PosixCondition<Thread> : public PosixConditionBase {
enum class State {
kUninitialized,
kRunning,
kSuspended,
kFinished,
};
@ -449,7 +451,7 @@ class PosixCondition<Thread> : public PosixConditionBase {
state_(State::kUninitialized) {}
bool Initialize(Thread::CreationParameters params,
ThreadStartData* start_data) {
assert_false(params.create_suspended);
start_data->create_suspended = params.create_suspended;
pthread_attr_t attr;
if (pthread_attr_init(&attr) != 0) return false;
if (pthread_attr_setstacksize(&attr, params.stack_size) != 0) {
@ -570,15 +572,23 @@ class PosixCondition<Thread> : public PosixConditionBase {
}
bool Resume(uint32_t* out_new_suspend_count = nullptr) {
// TODO(bwrsandman)
assert_always();
return false;
// TODO(bwrsandman): implement suspend_count
assert_null(out_new_suspend_count);
WaitStarted();
std::unique_lock<std::mutex> lock(state_mutex_);
if (state_ != State::kSuspended) return false;
state_ = State::kRunning;
state_signal_.notify_all();
return true;
}
bool Suspend(uint32_t* out_previous_suspend_count = nullptr) {
// TODO(bwrsandman)
assert_always();
return false;
// TODO(bwrsandman): implement suspend_count
assert_null(out_previous_suspend_count);
WaitStarted();
int result =
pthread_kill(thread_, GetSystemSignal(SignalType::kThreadSuspend));
return result == 0;
}
void Terminate(int exit_code) {
@ -606,6 +616,13 @@ class PosixCondition<Thread> : public PosixConditionBase {
[this] { return state_ != State::kUninitialized; });
}
/// Set state to suspended and wait until it reset by another thread
void WaitSuspended() {
std::unique_lock<std::mutex> lock(state_mutex_);
state_ = State::kSuspended;
state_signal_.wait(lock, [this] { return state_ != State::kSuspended; });
}
private:
static void* ThreadStartRoutine(void* parameter);
inline bool signaled() const override { return signaled_; }
@ -618,7 +635,7 @@ class PosixCondition<Thread> : public PosixConditionBase {
pthread_t thread_;
bool signaled_;
int exit_code_;
State state_;
volatile State state_;
mutable std::mutex state_mutex_;
mutable std::condition_variable state_signal_;
};
@ -786,7 +803,8 @@ class PosixThread : public PosixConditionHandle<Thread> {
bool Initialize(CreationParameters params,
std::function<void()> start_routine) {
auto start_data = new ThreadStartData({std::move(start_routine), this});
auto start_data =
new ThreadStartData({std::move(start_routine), false, this});
return handle_.Initialize(params, start_data);
}
@ -824,6 +842,8 @@ class PosixThread : public PosixConditionHandle<Thread> {
}
void Terminate(int exit_code) override { handle_.Terminate(exit_code); }
void WaitSuspended() { handle_.WaitSuspended(); }
};
thread_local PosixThread* current_thread_ = nullptr;
@ -840,12 +860,20 @@ void* PosixCondition<Thread>::ThreadStartRoutine(void* parameter) {
auto thread = dynamic_cast<PosixThread*>(start_data->thread_obj);
auto start_routine = std::move(start_data->start_routine);
auto create_suspended = start_data->create_suspended;
delete start_data;
current_thread_ = thread;
{
std::unique_lock<std::mutex> lock(thread->handle_.state_mutex_);
if (create_suspended) {
thread->handle_.state_ = State::kSuspended;
thread->handle_.state_signal_.wait(lock, [thread] {
return thread->handle_.state_ != State::kSuspended;
});
} else {
thread->handle_.state_ = State::kRunning;
}
thread->handle_.state_signal_.notify_all();
}
@ -867,6 +895,7 @@ void* PosixCondition<Thread>::ThreadStartRoutine(void* parameter) {
std::unique_ptr<Thread> Thread::Create(CreationParameters params,
std::function<void()> start_routine) {
install_signal_handler(SignalType::kThreadSuspend);
auto thread = std::make_unique<PosixThread>();
if (!thread->Initialize(params, std::move(start_routine))) return nullptr;
assert_not_null(thread);
@ -914,6 +943,10 @@ static void signal_handler(int signal, siginfo_t* info, void* /*context*/) {
static_cast<PosixCondition<Timer>*>(info->si_value.sival_ptr);
pTimer->CompletionRoutine();
} break;
case SignalType::kThreadSuspend: {
assert_not_null(current_thread_);
current_thread_->WaitSuspended();
} break;
default:
assert_always();
}