aboutsummaryrefslogtreecommitdiff
path: root/pw_sync_freertos/timed_thread_notification_test.cc
blob: 0ee87c88c3d8b2d28182828199e46a9fd4e57d14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_sync/timed_thread_notification.h"

#include <chrono>

#include "FreeRTOS.h"
#include "gtest/gtest.h"
#include "pw_chrono/system_clock.h"
#include "pw_thread/sleep.h"
#include "pw_thread/test_threads.h"
#include "pw_thread/thread.h"
#include "pw_thread/thread_core.h"
#include "task.h"

namespace pw::sync::freertos {
namespace {

using pw::chrono::SystemClock;
using pw::thread::Thread;

}  // namespace

// These tests are targeted specifically to verify interactions between suspend
// and being blocked on direct task notifications and how they impact usage of
// the FreeRTOS optimized TimedThreadNotification backend.
#if INCLUDE_vTaskSuspend == 1

class NotificationAcquirer : public thread::ThreadCore {
 public:
  void WaitUntilRunning() { started_notification_.acquire(); }
  void Release() { unblock_notification_.release(); }
  void WaitUntilFinished() { finished_notification_.acquire(); }
  std::optional<SystemClock::time_point> notified_time() const {
    return notified_time_;
  }
  TaskHandle_t task_handle() const { return task_handle_; }

 private:
  void Run() final {
    task_handle_ = xTaskGetCurrentTaskHandle();
    started_notification_.release();
    if (unblock_notification_.try_acquire_until(
            SystemClock::TimePointAfterAtLeast(std::chrono::hours(42)))) {
      notified_time_ = SystemClock::now();
    }
    finished_notification_.release();
  }

  TaskHandle_t task_handle_;
  TimedThreadNotification started_notification_;
  TimedThreadNotification unblock_notification_;
  ThreadNotification finished_notification_;
  std::optional<SystemClock::time_point> notified_time_;
};

TEST(TimedThreadNotification, AcquireWithoutSuspend) {
  NotificationAcquirer notification_acquirer;
  Thread thread =
      Thread(thread::test::TestOptionsThread0(), notification_acquirer);

  notification_acquirer.WaitUntilRunning();
  // At this point the thread is blocked and waiting on the notification.
  const SystemClock::time_point release_time = SystemClock::now();
  notification_acquirer.Release();
  notification_acquirer.WaitUntilFinished();
  ASSERT_TRUE(notification_acquirer.notified_time().has_value());
  EXPECT_GE(notification_acquirer.notified_time().value(), release_time);

  // Clean up the test thread context.
#if PW_THREAD_JOINING_ENABLED
  thread.join();
#else
  thread.detach();
  thread::test::WaitUntilDetachedThreadsCleanedUp();
#endif  // PW_THREAD_JOINING_ENABLED
}

TEST(TimedThreadNotification, AcquireWithSuspend) {
  NotificationAcquirer notification_acquirer;
  Thread thread =
      Thread(thread::test::TestOptionsThread0(), notification_acquirer);

  notification_acquirer.WaitUntilRunning();

  // Suspend and resume the task before notifying it, which should cause the
  // internal xTaskNotifyWait to stop blocking and return pdFALSE upon resume.
  vTaskSuspend(notification_acquirer.task_handle());
  vTaskResume(notification_acquirer.task_handle());

  // Sleep for at least one tick to ensure the time moved forward to let us
  // observe the unblock time is in fact after resumed it.
  this_thread::sleep_for(SystemClock::duration(1));

  // At this point the thread is blocked and waiting on the notification.
  const SystemClock::time_point release_time = SystemClock::now();
  notification_acquirer.Release();
  notification_acquirer.WaitUntilFinished();
  ASSERT_TRUE(notification_acquirer.notified_time().has_value());
  EXPECT_GE(notification_acquirer.notified_time().value(), release_time);

  // Clean up the test thread context.
#if PW_THREAD_JOINING_ENABLED
  thread.join();
#else
  thread.detach();
  thread::test::WaitUntilDetachedThreadsCleanedUp();
#endif  // PW_THREAD_JOINING_ENABLED
}

#endif  // INCLUDE_vTaskSuspend == 1

}  // namespace pw::sync::freertos