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