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 #include <optional>
19
20 #include "FreeRTOS.h"
21 #include "pw_chrono/system_clock.h"
22 #include "pw_thread/non_portable_test_thread_options.h"
23 #include "pw_thread/sleep.h"
24 #include "pw_thread/thread.h"
25 #include "pw_thread/thread_core.h"
26 #include "pw_unit_test/framework.h"
27 #include "task.h"
28
29 namespace pw::sync::freertos {
30 namespace {
31
32 using pw::chrono::SystemClock;
33 using pw::thread::Thread;
34
35 } // namespace
36
37 // These tests are targeted specifically to verify interactions between suspend
38 // and being blocked on direct task notifications and how they impact usage of
39 // the FreeRTOS optimized TimedThreadNotification backend.
40 #if INCLUDE_vTaskSuspend == 1
41
42 class TimedNotificationAcquirer : public thread::ThreadCore {
43 public:
WaitUntilRunning()44 void WaitUntilRunning() { started_notification_.acquire(); }
Release()45 void Release() { unblock_notification_.release(); }
WaitUntilFinished()46 void WaitUntilFinished() { finished_notification_.acquire(); }
notified_time() const47 std::optional<SystemClock::time_point> notified_time() const {
48 return notified_time_;
49 }
task_handle() const50 TaskHandle_t task_handle() const { return task_handle_; }
51
52 private:
Run()53 void Run() final {
54 task_handle_ = xTaskGetCurrentTaskHandle();
55 started_notification_.release();
56 if (unblock_notification_.try_acquire_until(
57 SystemClock::TimePointAfterAtLeast(std::chrono::hours(42)))) {
58 notified_time_ = SystemClock::now();
59 }
60 finished_notification_.release();
61 }
62
63 TaskHandle_t task_handle_;
64 TimedThreadNotification started_notification_;
65 TimedThreadNotification unblock_notification_;
66 ThreadNotification finished_notification_;
67 std::optional<SystemClock::time_point> notified_time_;
68 };
69
TEST(TimedThreadNotification,AcquireWithoutSuspend)70 TEST(TimedThreadNotification, AcquireWithoutSuspend) {
71 TimedNotificationAcquirer notification_acquirer;
72 // TODO: b/290860904 - Replace TestOptionsThread0 with TestThreadContext.
73 Thread thread =
74 Thread(thread::test::TestOptionsThread0(), notification_acquirer);
75
76 notification_acquirer.WaitUntilRunning();
77 // At this point the thread is blocked and waiting on the notification.
78 const SystemClock::time_point release_time = SystemClock::now();
79 notification_acquirer.Release();
80 notification_acquirer.WaitUntilFinished();
81 ASSERT_TRUE(notification_acquirer.notified_time().has_value());
82 EXPECT_GE(notification_acquirer.notified_time().value(), release_time);
83
84 // Clean up the test thread context.
85 #if PW_THREAD_JOINING_ENABLED
86 thread.join();
87 #else
88 thread.detach();
89 thread::test::WaitUntilDetachedThreadsCleanedUp();
90 #endif // PW_THREAD_JOINING_ENABLED
91 }
92
TEST(TimedThreadNotification,AcquireWithSuspend)93 TEST(TimedThreadNotification, AcquireWithSuspend) {
94 TimedNotificationAcquirer notification_acquirer;
95 Thread thread =
96 Thread(thread::test::TestOptionsThread0(), notification_acquirer);
97
98 notification_acquirer.WaitUntilRunning();
99
100 // Suspend and resume the task before notifying it, which should cause the
101 // internal xTaskNotifyWait to stop blocking and return pdFALSE upon resume.
102 vTaskSuspend(notification_acquirer.task_handle());
103 vTaskResume(notification_acquirer.task_handle());
104
105 // Sleep for at least one tick to ensure the time moved forward to let us
106 // observe the unblock time is in fact after resumed it.
107 this_thread::sleep_for(SystemClock::duration(1));
108
109 // At this point the thread is blocked and waiting on the notification.
110 const SystemClock::time_point release_time = SystemClock::now();
111 notification_acquirer.Release();
112 notification_acquirer.WaitUntilFinished();
113 ASSERT_TRUE(notification_acquirer.notified_time().has_value());
114 EXPECT_GE(notification_acquirer.notified_time().value(), release_time);
115
116 // Clean up the test thread context.
117 #if PW_THREAD_JOINING_ENABLED
118 thread.join();
119 #else
120 thread.detach();
121 thread::test::WaitUntilDetachedThreadsCleanedUp();
122 #endif // PW_THREAD_JOINING_ENABLED
123 }
124
125 #endif // INCLUDE_vTaskSuspend == 1
126
127 } // namespace pw::sync::freertos
128