text: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you 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
  //
  //   http://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 <gtest/gtest.h>
  #include <gflags/gflags.h>
  #include "bthread/sys_futex.h"
  #include "bthread/timer_thread.h"
  #include "bthread/bthread.h"
  #include "butil/logging.h"
  
  namespace {
  
  long timespec_diff_us(const timespec& ts1, const timespec& ts2) {
      return (ts1.tv_sec - ts2.tv_sec) * 1000000L +
          (ts1.tv_nsec - ts2.tv_nsec) / 1000;
  }
  
  // A simple class, could keep track of the time when it is invoked.
  class TimeKeeper {
  public:
      TimeKeeper(timespec run_time)
          : _expect_run_time(run_time), _name(NULL), _sleep_ms(0) {}
      TimeKeeper(timespec run_time, const char* name/*must be string constant*/)
          : _expect_run_time(run_time), _name(name), _sleep_ms(0) {}
      TimeKeeper(timespec run_time, const char* name/*must be string constant*/,
                 int sleep_ms)
          : _expect_run_time(run_time), _name(name), _sleep_ms(sleep_ms) {}
  
      void schedule(bthread::TimerThread* timer_thread) {
          _task_id = timer_thread->schedule(
              TimeKeeper::routine, this, _expect_run_time);
      }
  
      void run()
      {
          timespec current_time;
          clock_gettime(CLOCK_REALTIME, &current_time);
          if (_name) {
              LOG(INFO) << "Run `" << _name << "' task_id=" << _task_id;
          } else {
              LOG(INFO) << "Run task_id=" << _task_id;
          }
          _run_times.push_back(current_time);
          const int saved_sleep_ms = _sleep_ms;
          if (saved_sleep_ms > 0) {
              timespec timeout = butil::milliseconds_to_timespec(saved_sleep_ms);
              bthread::futex_wait_private(&_sleep_ms, saved_sleep_ms, &timeout);
          }
      }
  
      void wakeup() {
          if (_sleep_ms != 0) {
              _sleep_ms = 0;
              bthread::futex_wake_private(&_sleep_ms, 1);
          } else {
              LOG(ERROR) << "No need to wakeup "
                         << (_name ? _name : "") << " task_id=" << _task_id;
          }
      }
  
      // verify the first run is in specified time range.
      void expect_first_run()
      {
          expect_first_run(_expect_run_time);
      }
  
      void expect_first_run(timespec expect_run_time)
      {
          ASSERT_TRUE(!_run_times.empty());
          long diff = timespec_diff_us(_run_times[0], expect_run_time);
          EXPECT_LE(labs(diff), 50000);
      }
      
      void expect_not_run() {
          EXPECT_TRUE(_run_times.empty());
      }
  
      static void routine(void *arg)
      {
          TimeKeeper* keeper = (TimeKeeper*)arg;
          keeper->run();
      }
  
      timespec _expect_run_time;
      bthread::TimerThread::TaskId _task_id;
  
  private:
      const char* _name;
      int _sleep_ms;
      std::vector<timespec> _run_times;
  };
  
  TEST(TimerThreadTest, RunTasks) {
      bthread::TimerThread timer_thread;
      ASSERT_EQ(0, timer_thread.start(NULL));
  
      timespec _2s_later = butil::seconds_from_now(2);
      TimeKeeper keeper1(_2s_later, "keeper1");
      keeper1.schedule(&timer_thread);
  
      TimeKeeper keeper2(_2s_later, "keeper2");  // same time with keeper1
      keeper2.schedule(&timer_thread);
      
      timespec _1s_later = butil::seconds_from_now(1);
      TimeKeeper keeper3(_1s_later, "keeper3");
      keeper3.schedule(&timer_thread);
  
      timespec _10s_later = butil::seconds_from_now(10);
      TimeKeeper keeper4(_10s_later, "keeper4");
      keeper4.schedule(&timer_thread);
  
      TimeKeeper keeper5(_10s_later, "keeper5");
      keeper5.schedule(&timer_thread);
      
      // sleep 1 second, and unschedule task2
      LOG(INFO) << "Sleep 1s";
      sleep(1);
      timer_thread.unschedule(keeper2._task_id);
      timer_thread.unschedule(keeper4._task_id);
  
      timespec old_time = { 0, 0 };
      TimeKeeper keeper6(old_time, "keeper6");
      keeper6.schedule(&timer_thread);
      const timespec keeper6_addtime = butil::seconds_from_now(0);
  
      // sleep 10 seconds and stop.
      LOG(INFO) << "Sleep 2s";
      sleep(2);
      LOG(INFO) << "Stop timer_thread";
      butil::Timer tm;
      tm.start();
      timer_thread.stop_and_join();
      tm.stop();
      ASSERT_LE(tm.m_elapsed(), 15);
  
      // verify all runs in expected time range.
      keeper1.expect_first_run();
      keeper2.expect_not_run();
      keeper3.expect_first_run();
      keeper4.expect_not_run();
      keeper5.expect_not_run();
      keeper6.expect_first_run(keeper6_addtime);
  }
  
  // If the scheduled time is before start time, then should run it
  // immediately.
  TEST(TimerThreadTest, start_after_schedule) {
      bthread::TimerThread timer_thread;
      timespec past_time = { 0, 0 };
      TimeKeeper keeper(past_time, "keeper1");
      keeper.schedule(&timer_thread);
      ASSERT_EQ(bthread::TimerThread::INVALID_TASK_ID, keeper._task_id);
      ASSERT_EQ(0, timer_thread.start(NULL));
      keeper.schedule(&timer_thread);
      ASSERT_NE(bthread::TimerThread::INVALID_TASK_ID, keeper._task_id);
      timespec current_time = butil::seconds_from_now(0);
      sleep(1);  // make sure timer thread start and run
      timer_thread.stop_and_join();
      keeper.expect_first_run(current_time);
  }
  
  class TestTask {
  public:
      TestTask(bthread::TimerThread* timer_thread, TimeKeeper* keeper1,
               TimeKeeper* keeper2, int expected_unschedule_result)
          : _timer_thread(timer_thread)
          , _keeper1(keeper1)
          , _keeper2(keeper2)
          , _expected_unschedule_result(expected_unschedule_result) {
      }
              
      void run()
      {
          clock_gettime(CLOCK_REALTIME, &_running_time);
          EXPECT_EQ(_expected_unschedule_result,
                    _timer_thread->unschedule(_keeper1->_task_id));
          _keeper2->schedule(_timer_thread);
      }
  
      static void routine(void* arg)
      {
          TestTask* task = (TestTask*)arg;
          task->run();
      }
      timespec _running_time;
  
  private:
      bthread::TimerThread* _timer_thread;  // not owned.
      TimeKeeper* _keeper1;  // not owned.
      TimeKeeper* _keeper2;  // not owned.
      int _expected_unschedule_result;
  };
  
  // Perform schedule and unschedule inside a running task
  TEST(TimerThreadTest, schedule_and_unschedule_in_task) {
      bthread::TimerThread timer_thread;
      timespec past_time = { 0, 0 };
      timespec future_time = { std::numeric_limits<int>::max(), 0 };
      const timespec _500ms_after = butil::milliseconds_from_now(500);
  
      TimeKeeper keeper1(future_time, "keeper1");
      TimeKeeper keeper2(past_time, "keeper2");
      TimeKeeper keeper3(past_time, "keeper3");
      TimeKeeper keeper4(past_time, "keeper4");
      TimeKeeper keeper5(_500ms_after, "keeper5", 10000/*10s*/);
  
      ASSERT_EQ(0, timer_thread.start(NULL));
      keeper1.schedule(&timer_thread);  // start keeper1
      keeper3.schedule(&timer_thread);  // start keeper3
      timespec keeper3_addtime = butil::seconds_from_now(0);
      keeper5.schedule(&timer_thread);  // start keeper5
      sleep(1);  // let keeper1/3/5 run
  
      TestTask test_task1(&timer_thread, &keeper1, &keeper2, 0);
      timer_thread.schedule(TestTask::routine, &test_task1, past_time);
  
      TestTask test_task2(&timer_thread, &keeper3, &keeper4, -1);
      timer_thread.schedule(TestTask::routine, &test_task2, past_time);
  
      sleep(1);
      // test_task1/2 should be both blocked by keeper5.
      keeper2.expect_not_run();
      keeper4.expect_not_run();
  
      // unscheduling (running) keeper5 should have no effect and returns 1
      ASSERT_EQ(1, timer_thread.unschedule(keeper5._task_id));
      
      // wake up keeper5 to let test_task1/2 run.
      keeper5.wakeup();
      sleep(1);
  
      timer_thread.stop_and_join();
      timespec finish_time;
      clock_gettime(CLOCK_REALTIME, &finish_time);
  
      keeper1.expect_not_run();
      keeper2.expect_first_run(test_task1._running_time);
      keeper3.expect_first_run(keeper3_addtime);
      keeper4.expect_first_run(test_task2._running_time);
      keeper5.expect_first_run();
  }
  
  } // end namespace
  