Skip to content

semaphore_test.cc: add TimedWaitSpuriousWakeupLinux #1037

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 26, 2022
Merged
52 changes: 52 additions & 0 deletions app/tests/semaphore_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@

#include "app/src/semaphore.h"

#include <atomic>

#include "app/src/thread.h"
#include "app/src/time.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

#if FIREBASE_PLATFORM_ANDROID || FIREBASE_PLATFORM_LINUX
#include <pthread.h>

#include <csignal>
#endif // #if FIREBASE_PLATFORM_ANDROID || FIREBASE_PLATFORM_LINUX

namespace {

// Basic test of TryWait, to make sure that its successes and failures
Expand Down Expand Up @@ -76,6 +84,50 @@ TEST(SemaphoreTest, TimedWait) {
0.20 * firebase::internal::kMillisecondsPerSecond);
}

#if FIREBASE_PLATFORM_ANDROID || FIREBASE_PLATFORM_LINUX
// Use a global variable for `sigusr1_received` because there is no way to pass
// a context to the signal handler (https://stackoverflow.com/q/64713130).
std::atomic<bool> sigusr1_received;
// Tests that Timed Wait handles spurious wakeup (Linux/Android specific).
TEST(SemaphoreTest, TimedWaitSpuriousWakeupLinux) {
// Register a dummy signal handler for SIGUSR1; otherwise, sending SIGUSR1
// later on will crash the application.
sigusr1_received.store(false);
signal(SIGUSR1, [](int signum) { sigusr1_received.store(true); });

// Start a thread that will send SIGUSR1 to this thread in a few moments.
pthread_t main_thread = pthread_self();
firebase::Thread signal_sending_thread = firebase::Thread(
[](void* arg) {
firebase::internal::Sleep(500);
pthread_kill(*static_cast<pthread_t*>(arg), SIGUSR1);
},
&main_thread);

// Call Semaphore::TimedWait() and keep track of how long it blocks for.
firebase::Semaphore sem(0);
auto start_ms = firebase::internal::GetTimestamp();
const int timed_wait_timeout = 2 * firebase::internal::kMillisecondsPerSecond;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the constants should probably follow the kConstantNaming syntax

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

EXPECT_FALSE(sem.TimedWait(timed_wait_timeout));
auto finish_ms = firebase::internal::GetTimestamp();
const int actual_wait_time = static_cast<int>(finish_ms - start_ms);
EXPECT_TRUE(sigusr1_received.load());

// Wait for the "signal sending" thread to terminate, since it references
// memory on the stack and we can't have it running after this method returns.
signal_sending_thread.Join();

// Unregister the signal handler for SIGUSR1, since it's no longer needed.
signal(SIGUSR1, NULL);

// Make sure that Semaphore::TimedWait() blocked for the entire timeout, and,
// specifically, did NOT return early as a result of the SIGUSR1 interruption.
const double wait_time_error_margin =
0.20 * firebase::internal::kMillisecondsPerSecond;
ASSERT_NEAR(actual_wait_time, timed_wait_timeout, wait_time_error_margin);
}
#endif // #if FIREBASE_PLATFORM_ANDROID || FIREBASE_PLATFORM_LINUX

TEST(SemaphoreTest, MultithreadedStressTest) {
for (int i = 0; i < 10000; ++i) {
firebase::Semaphore sem(0);
Expand Down