|
16 | 16 |
|
17 | 17 | #include "app/src/semaphore.h"
|
18 | 18 |
|
| 19 | +#include <atomic> |
| 20 | + |
19 | 21 | #include "app/src/thread.h"
|
20 | 22 | #include "app/src/time.h"
|
21 | 23 | #include "gmock/gmock.h"
|
22 | 24 | #include "gtest/gtest.h"
|
23 | 25 |
|
| 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 | + |
24 | 32 | namespace {
|
25 | 33 |
|
26 | 34 | // Basic test of TryWait, to make sure that its successes and failures
|
@@ -76,6 +84,50 @@ TEST(SemaphoreTest, TimedWait) {
|
76 | 84 | 0.20 * firebase::internal::kMillisecondsPerSecond);
|
77 | 85 | }
|
78 | 86 |
|
| 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 | + |
79 | 131 | TEST(SemaphoreTest, MultithreadedStressTest) {
|
80 | 132 | for (int i = 0; i < 10000; ++i) {
|
81 | 133 | firebase::Semaphore sem(0);
|
|
0 commit comments