Skip to content

Commit 69b870d

Browse files
authored
semaphore_test.cc: add TimedWaitSpuriousWakeupLinux (#1037)
1 parent 59ddbce commit 69b870d

File tree

1 file changed

+52
-0
lines changed

1 file changed

+52
-0
lines changed

app/tests/semaphore_test.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,19 @@
1616

1717
#include "app/src/semaphore.h"
1818

19+
#include <atomic>
20+
1921
#include "app/src/thread.h"
2022
#include "app/src/time.h"
2123
#include "gmock/gmock.h"
2224
#include "gtest/gtest.h"
2325

26+
#if FIREBASE_PLATFORM_ANDROID || FIREBASE_PLATFORM_LINUX
27+
#include <pthread.h>
28+
29+
#include <csignal>
30+
#endif // #if FIREBASE_PLATFORM_ANDROID || FIREBASE_PLATFORM_LINUX
31+
2432
namespace {
2533

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

87+
#if FIREBASE_PLATFORM_ANDROID || FIREBASE_PLATFORM_LINUX
88+
// Use a global variable for `sigusr1_received` because there is no way to pass
89+
// a context to the signal handler (https://stackoverflow.com/q/64713130).
90+
std::atomic<bool> sigusr1_received;
91+
// Tests that Timed Wait handles spurious wakeup (Linux/Android specific).
92+
TEST(SemaphoreTest, TimedWaitSpuriousWakeupLinux) {
93+
// Register a dummy signal handler for SIGUSR1; otherwise, sending SIGUSR1
94+
// later on will crash the application.
95+
sigusr1_received.store(false);
96+
signal(SIGUSR1, [](int signum) { sigusr1_received.store(true); });
97+
98+
// Start a thread that will send SIGUSR1 to this thread in a few moments.
99+
pthread_t main_thread = pthread_self();
100+
firebase::Thread signal_sending_thread = firebase::Thread(
101+
[](void* arg) {
102+
firebase::internal::Sleep(500);
103+
pthread_kill(*static_cast<pthread_t*>(arg), SIGUSR1);
104+
},
105+
&main_thread);
106+
107+
// Call Semaphore::TimedWait() and keep track of how long it blocks for.
108+
firebase::Semaphore sem(0);
109+
auto start_ms = firebase::internal::GetTimestamp();
110+
const int kTimedWaitTimeout = 2 * firebase::internal::kMillisecondsPerSecond;
111+
EXPECT_FALSE(sem.TimedWait(kTimedWaitTimeout));
112+
auto finish_ms = firebase::internal::GetTimestamp();
113+
const int actual_wait_time = static_cast<int>(finish_ms - start_ms);
114+
EXPECT_TRUE(sigusr1_received.load());
115+
116+
// Wait for the "signal sending" thread to terminate, since it references
117+
// memory on the stack and we can't have it running after this method returns.
118+
signal_sending_thread.Join();
119+
120+
// Unregister the signal handler for SIGUSR1, since it's no longer needed.
121+
signal(SIGUSR1, NULL);
122+
123+
// Make sure that Semaphore::TimedWait() blocked for the entire timeout, and,
124+
// specifically, did NOT return early as a result of the SIGUSR1 interruption.
125+
const double kWaitTimeErrorMargin =
126+
0.20 * firebase::internal::kMillisecondsPerSecond;
127+
ASSERT_NEAR(actual_wait_time, kTimedWaitTimeout, kWaitTimeErrorMargin);
128+
}
129+
#endif // #if FIREBASE_PLATFORM_ANDROID || FIREBASE_PLATFORM_LINUX
130+
79131
TEST(SemaphoreTest, MultithreadedStressTest) {
80132
for (int i = 0; i < 10000; ++i) {
81133
firebase::Semaphore sem(0);

0 commit comments

Comments
 (0)