1 /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://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,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include "tensorflow/core/kernels/batching_util/periodic_function.h"
17
18 #include <memory>
19 #include <string>
20
21 #include "tensorflow/core/kernels/batching_util/fake_clock_env.h"
22 #include "tensorflow/core/platform/test.h"
23
24 namespace tensorflow {
25 namespace serving {
26
27 namespace internal {
28
29 class PeriodicFunctionTestAccess {
30 public:
PeriodicFunctionTestAccess(PeriodicFunction * periodic_function)31 explicit PeriodicFunctionTestAccess(PeriodicFunction* periodic_function)
32 : periodic_function_(periodic_function) {}
33
NotifyStop()34 void NotifyStop() { periodic_function_->NotifyStop(); }
35
36 private:
37 PeriodicFunction* const periodic_function_;
38 };
39
40 } // namespace internal
41
42 namespace {
43
44 using test_util::FakeClockEnv;
45
StopPeriodicFunction(PeriodicFunction * periodic_function,FakeClockEnv * fake_clock_env,const uint64 pf_interval_micros)46 void StopPeriodicFunction(PeriodicFunction* periodic_function,
47 FakeClockEnv* fake_clock_env,
48 const uint64 pf_interval_micros) {
49 fake_clock_env->BlockUntilThreadsAsleep(1);
50 internal::PeriodicFunctionTestAccess(periodic_function).NotifyStop();
51 fake_clock_env->AdvanceByMicroseconds(pf_interval_micros);
52 }
53
TEST(PeriodicFunctionTest,ObeyInterval)54 TEST(PeriodicFunctionTest, ObeyInterval) {
55 const int64 kPeriodMicros = 2;
56 const int kCalls = 10;
57
58 int actual_calls = 0;
59 {
60 FakeClockEnv fake_clock_env(Env::Default());
61 PeriodicFunction::Options options;
62 options.env = &fake_clock_env;
63 PeriodicFunction periodic_function([&actual_calls]() { ++actual_calls; },
64 kPeriodMicros, options);
65
66 for (int i = 0; i < kCalls; ++i) {
67 fake_clock_env.BlockUntilThreadsAsleep(1);
68 fake_clock_env.AdvanceByMicroseconds(kPeriodMicros);
69 }
70 StopPeriodicFunction(&periodic_function, &fake_clock_env, kPeriodMicros);
71 }
72
73 // The function gets called kCalls+1 times: once at time 0, once at time
74 // kPeriodMicros, once at time kPeriodMicros*2, up to once at time
75 // kPeriodMicros*kCalls.
76 ASSERT_EQ(actual_calls, kCalls + 1);
77 }
78
TEST(PeriodicFunctionTest,ObeyStartupDelay)79 TEST(PeriodicFunctionTest, ObeyStartupDelay) {
80 const int64 kDelayMicros = 10;
81 const int64 kPeriodMicros = kDelayMicros / 10;
82
83 int actual_calls = 0;
84 {
85 PeriodicFunction::Options options;
86 options.startup_delay_micros = kDelayMicros;
87 FakeClockEnv fake_clock_env(Env::Default());
88 options.env = &fake_clock_env;
89 PeriodicFunction periodic_function([&actual_calls]() { ++actual_calls; },
90 kPeriodMicros, options);
91
92 // Wait for the thread to start up.
93 fake_clock_env.BlockUntilThreadsAsleep(1);
94 // Function shouldn't have been called yet.
95 EXPECT_EQ(0, actual_calls);
96 // Give enough time for startup delay to expire.
97 fake_clock_env.AdvanceByMicroseconds(kDelayMicros);
98 StopPeriodicFunction(&periodic_function, &fake_clock_env, kDelayMicros);
99 }
100
101 // Function should have been called at least once.
102 EXPECT_EQ(1, actual_calls);
103 }
104
105 // Test for race in calculating the first time the callback should fire.
TEST(PeriodicFunctionTest,StartupDelayRace)106 TEST(PeriodicFunctionTest, StartupDelayRace) {
107 const int64 kDelayMicros = 10;
108 const int64 kPeriodMicros = kDelayMicros / 10;
109
110 mutex mu;
111 int counter = 0;
112 std::unique_ptr<Notification> listener(new Notification);
113
114 FakeClockEnv fake_clock_env(Env::Default());
115 PeriodicFunction::Options options;
116 options.env = &fake_clock_env;
117 options.startup_delay_micros = kDelayMicros;
118 PeriodicFunction periodic_function(
119 [&mu, &counter, &listener]() {
120 mutex_lock l(mu);
121 counter++;
122 listener->Notify();
123 },
124 kPeriodMicros, options);
125
126 fake_clock_env.BlockUntilThreadsAsleep(1);
127 fake_clock_env.AdvanceByMicroseconds(kDelayMicros);
128 listener->WaitForNotification();
129 {
130 mutex_lock l(mu);
131 EXPECT_EQ(1, counter);
132 // A notification can only be notified once.
133 listener.reset(new Notification);
134 }
135 fake_clock_env.BlockUntilThreadsAsleep(1);
136 fake_clock_env.AdvanceByMicroseconds(kPeriodMicros);
137 listener->WaitForNotification();
138 {
139 mutex_lock l(mu);
140 EXPECT_EQ(2, counter);
141 }
142 StopPeriodicFunction(&periodic_function, &fake_clock_env, kPeriodMicros);
143 }
144
145 // If this test hangs forever, its probably a deadlock caused by setting the
146 // PeriodicFunction's interval to 0ms.
TEST(PeriodicFunctionTest,MinInterval)147 TEST(PeriodicFunctionTest, MinInterval) {
148 PeriodicFunction periodic_function(
149 []() { Env::Default()->SleepForMicroseconds(20 * 1000); }, 0);
150 }
151
152 class PeriodicFunctionWithFakeClockEnvTest : public ::testing::Test {
153 protected:
154 const int64 kPeriodMicros = 50;
PeriodicFunctionWithFakeClockEnvTest()155 PeriodicFunctionWithFakeClockEnvTest()
156 : fake_clock_env_(Env::Default()),
157 counter_(0),
158 pf_(
159 [this]() {
160 mutex_lock l(counter_mu_);
161 ++counter_;
162 },
163 kPeriodMicros, GetPeriodicFunctionOptions()) {}
164
GetPeriodicFunctionOptions()165 PeriodicFunction::Options GetPeriodicFunctionOptions() {
166 PeriodicFunction::Options options;
167 options.thread_name_prefix = "ignore";
168 options.env = &fake_clock_env_;
169 return options;
170 }
171
SetUp()172 void SetUp() override {
173 // Note: counter_ gets initially incremented at time 0.
174 ASSERT_TRUE(AwaitCount(1));
175 }
176
TearDown()177 void TearDown() override {
178 StopPeriodicFunction(&pf_, &fake_clock_env_, kPeriodMicros);
179 }
180
181 // The FakeClockEnv tests below advance simulated time and then expect the
182 // PeriodicFunction thread to run its function. This method helps the tests
183 // wait for the thread to execute, and then check the count matches the
184 // expectation.
AwaitCount(int expected_counter)185 bool AwaitCount(int expected_counter) {
186 fake_clock_env_.BlockUntilThreadsAsleep(1);
187 {
188 mutex_lock lock(counter_mu_);
189 return counter_ == expected_counter;
190 }
191 }
192
193 FakeClockEnv fake_clock_env_;
194 mutex counter_mu_;
195 int counter_;
196 PeriodicFunction pf_;
197 };
198
TEST_F(PeriodicFunctionWithFakeClockEnvTest,FasterThanRealTime)199 TEST_F(PeriodicFunctionWithFakeClockEnvTest, FasterThanRealTime) {
200 fake_clock_env_.AdvanceByMicroseconds(kPeriodMicros / 2);
201 for (int i = 2; i < 7; ++i) {
202 fake_clock_env_.AdvanceByMicroseconds(
203 kPeriodMicros); // advance past a tick
204 EXPECT_TRUE(AwaitCount(i));
205 }
206 }
207
TEST_F(PeriodicFunctionWithFakeClockEnvTest,SlowerThanRealTime)208 TEST_F(PeriodicFunctionWithFakeClockEnvTest, SlowerThanRealTime) {
209 Env::Default()->SleepForMicroseconds(
210 125 * 1000); // wait for any unexpected breakage
211 EXPECT_TRUE(AwaitCount(1));
212 }
213
TEST(PeriodicFunctionDeathTest,BadInterval)214 TEST(PeriodicFunctionDeathTest, BadInterval) {
215 EXPECT_DEBUG_DEATH(PeriodicFunction periodic_function([]() {}, -1),
216 ".* should be >= 0");
217
218 EXPECT_DEBUG_DEATH(PeriodicFunction periodic_function(
219 []() {}, -1, PeriodicFunction::Options()),
220 ".* should be >= 0");
221 }
222
223 } // namespace
224 } // namespace serving
225 } // namespace tensorflow
226