1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_sync/timed_thread_notification.h"
16
17 #include <chrono>
18
19 #include "FreeRTOS.h"
20 #include "gtest/gtest.h"
21 #include "pw_chrono/system_clock.h"
22 #include "pw_thread/sleep.h"
23 #include "pw_thread/test_threads.h"
24 #include "pw_thread/thread.h"
25 #include "pw_thread/thread_core.h"
26 #include "task.h"
27
28 namespace pw::sync::freertos {
29 namespace {
30
31 using pw::chrono::SystemClock;
32 using pw::thread::Thread;
33
34 } // namespace
35
36 // These tests are targeted specifically to verify interactions between suspend
37 // and being blocked on direct task notifications and how they impact usage of
38 // the FreeRTOS optimized TimedThreadNotification backend.
39 #if INCLUDE_vTaskSuspend == 1
40
41 class NotificationAcquirer : public thread::ThreadCore {
42 public:
WaitUntilRunning()43 void WaitUntilRunning() { started_notification_.acquire(); }
Release()44 void Release() { unblock_notification_.release(); }
WaitUntilFinished()45 void WaitUntilFinished() { finished_notification_.acquire(); }
notified_time() const46 std::optional<SystemClock::time_point> notified_time() const {
47 return notified_time_;
48 }
task_handle() const49 TaskHandle_t task_handle() const { return task_handle_; }
50
51 private:
Run()52 void Run() final {
53 task_handle_ = xTaskGetCurrentTaskHandle();
54 started_notification_.release();
55 if (unblock_notification_.try_acquire_until(
56 SystemClock::TimePointAfterAtLeast(std::chrono::hours(42)))) {
57 notified_time_ = SystemClock::now();
58 }
59 finished_notification_.release();
60 }
61
62 TaskHandle_t task_handle_;
63 TimedThreadNotification started_notification_;
64 TimedThreadNotification unblock_notification_;
65 ThreadNotification finished_notification_;
66 std::optional<SystemClock::time_point> notified_time_;
67 };
68
TEST(TimedThreadNotification,AcquireWithoutSuspend)69 TEST(TimedThreadNotification, AcquireWithoutSuspend) {
70 NotificationAcquirer notification_acquirer;
71 Thread thread =
72 Thread(thread::test::TestOptionsThread0(), notification_acquirer);
73
74 notification_acquirer.WaitUntilRunning();
75 // At this point the thread is blocked and waiting on the notification.
76 const SystemClock::time_point release_time = SystemClock::now();
77 notification_acquirer.Release();
78 notification_acquirer.WaitUntilFinished();
79 ASSERT_TRUE(notification_acquirer.notified_time().has_value());
80 EXPECT_GE(notification_acquirer.notified_time().value(), release_time);
81
82 // Clean up the test thread context.
83 #if PW_THREAD_JOINING_ENABLED
84 thread.join();
85 #else
86 thread.detach();
87 thread::test::WaitUntilDetachedThreadsCleanedUp();
88 #endif // PW_THREAD_JOINING_ENABLED
89 }
90
TEST(TimedThreadNotification,AcquireWithSuspend)91 TEST(TimedThreadNotification, AcquireWithSuspend) {
92 NotificationAcquirer notification_acquirer;
93 Thread thread =
94 Thread(thread::test::TestOptionsThread0(), notification_acquirer);
95
96 notification_acquirer.WaitUntilRunning();
97
98 // Suspend and resume the task before notifying it, which should cause the
99 // internal xTaskNotifyWait to stop blocking and return pdFALSE upon resume.
100 vTaskSuspend(notification_acquirer.task_handle());
101 vTaskResume(notification_acquirer.task_handle());
102
103 // Sleep for at least one tick to ensure the time moved forward to let us
104 // observe the unblock time is in fact after resumed it.
105 this_thread::sleep_for(SystemClock::duration(1));
106
107 // At this point the thread is blocked and waiting on the notification.
108 const SystemClock::time_point release_time = SystemClock::now();
109 notification_acquirer.Release();
110 notification_acquirer.WaitUntilFinished();
111 ASSERT_TRUE(notification_acquirer.notified_time().has_value());
112 EXPECT_GE(notification_acquirer.notified_time().value(), release_time);
113
114 // Clean up the test thread context.
115 #if PW_THREAD_JOINING_ENABLED
116 thread.join();
117 #else
118 thread.detach();
119 thread::test::WaitUntilDetachedThreadsCleanedUp();
120 #endif // PW_THREAD_JOINING_ENABLED
121 }
122
123 #endif // INCLUDE_vTaskSuspend == 1
124
125 } // namespace pw::sync::freertos
126