Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 513d1a64 authored by Erwin Jansen's avatar Erwin Jansen Committed by Gerrit Code Review
Browse files

Merge "Fix crash in emulator unit tests"

parents 10aaf084 ad9b2bda
Loading
Loading
Loading
Loading
+20 −3
Original line number Diff line number Diff line
@@ -364,6 +364,7 @@ class AsyncManager::AsyncTaskManager {
    std::chrono::steady_clock::time_point time;
    bool periodic;
    std::chrono::milliseconds period{};
    std::mutex in_callback; // Taken when the callback is active
    TaskCallback callback;
    AsyncTaskId task_id;
    AsyncUserId user_id;
@@ -381,8 +382,22 @@ class AsyncManager::AsyncTaskManager {
    if (tasks_by_id_.count(async_task_id) == 0) {
      return false;
    }

    // Now make sure we are not running this task.
    // 2 cases:
    // - This is called from thread_, this means a running
    //   scheduled task is actually unregistering. All bets are off.
    // - Another thread is calling us, let's make sure the task is not active.
    if (thread_.get_id() != std::this_thread::get_id()) {
      auto task = tasks_by_id_[async_task_id];
      const std::lock_guard<std::mutex> lock(task->in_callback);
      task_queue_.erase(task);
      tasks_by_id_.erase(async_task_id);
    } else {
      task_queue_.erase(tasks_by_id_[async_task_id]);
      tasks_by_id_.erase(async_task_id);
    }

    return true;
  }

@@ -437,11 +452,12 @@ class AsyncManager::AsyncTaskManager {
  void ThreadRoutine() {
    while (running_) {
      TaskCallback callback;
      std::shared_ptr<Task> task_p;
      bool run_it = false;
      {
        std::unique_lock<std::mutex> guard(internal_mutex_);
        if (!task_queue_.empty()) {
          std::shared_ptr<Task> task_p = *(task_queue_.begin());
          task_p = *(task_queue_.begin());
          if (task_p->time < std::chrono::steady_clock::now()) {
            run_it = true;
            callback = task_p->callback;
@@ -458,6 +474,7 @@ class AsyncManager::AsyncTaskManager {
        }
      }
      if (run_it) {
        const std::lock_guard<std::mutex> lock(task_p->in_callback);
        callback();
      }
      {
+10 −10
Original line number Diff line number Diff line
@@ -77,18 +77,18 @@ class AsyncManager {
                                    std::chrono::milliseconds period,
                                    const TaskCallback& callback);

  // Cancels the/every future occurrence of the action specified by this id. It
  // is guaranteed that the associated callback will not be called after this
  // method returns (it could be called during the execution of the method).
  // The calling thread may block until the scheduling thread acknowledges the
  // cancellation.
  // Cancels the/every future occurrence of the action specified by this id.
  // The following invariants will hold:
  // - The task will not be invoked after this method returns
  // - If the task is currently running it will block until the task is
  //   completed, unless cancel is called from the running task.
  bool CancelAsyncTask(AsyncTaskId async_task_id);

  // Cancels the/every future occurrence of the action specified by this id. It
  // is guaranteed that the associated callback will not be called after this
  // method returns (it could be called during the execution of the method).
  // The calling thread may block until the scheduling thread acknowledges the
  // cancellation.
  // Cancels the/every future occurrence of the action specified by this id.
  // The following invariants will hold:
  // - The task will not be invoked after this method returns
  // - If the task is currently running it will block until the task is
  //   completed, unless cancel is called from the running task.
  bool CancelAsyncTasksFromUser(AsyncUserId user_id);

  // Execs the given code in a synchronized manner. It is guaranteed that code
+4 −0
Original line number Diff line number Diff line
@@ -52,6 +52,10 @@ TestModel::TestModel(
  model_user_id_ = get_user_id_();
}

TestModel::~TestModel() {
  StopTimer();
}

void TestModel::SetTimerPeriod(std::chrono::milliseconds new_period) {
  timer_period_ = new_period;

+1 −1
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ class TestModel {
      std::function<void(AsyncTaskId)> cancel,
      std::function<std::shared_ptr<Device>(const std::string&, int, Phy::Type)>
          connect_to_remote);
  ~TestModel() = default;
  ~TestModel();

  TestModel(TestModel& model) = delete;
  TestModel& operator=(const TestModel& model) = delete;
+43 −1
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@
#include <ratio>               // for ratio
#include <string>              // for string
#include <tuple>               // for tuple
#include <thread>

#include "osi/include/osi.h"  // for OSI_NO_INTR

@@ -123,6 +124,7 @@ class AsyncManagerSocketTest : public ::testing::Test {

  void SetUp() override {
    memset(server_buffer_, 0, kBufferSize);
    memset(client_buffer_, 0, kBufferSize);

    socket_fd_ = StartServer();

@@ -137,7 +139,7 @@ class AsyncManagerSocketTest : public ::testing::Test {
  void TearDown() override {
    async_manager_.StopWatchingFileDescriptor(socket_fd_);
    close(socket_fd_);
    ASSERT_TRUE(CheckBufferEquals());
    ASSERT_EQ(std::string_view(server_buffer_, kBufferSize), std::string_view(client_buffer_, kBufferSize));
  }

  int ConnectClient() {
@@ -215,6 +217,46 @@ TEST_F(AsyncManagerSocketTest, CanUnsubscribeInCallback) {
  close(socket_cli_fd);
}


TEST_F(AsyncManagerSocketTest, CanUnsubscribeTaskFromWithinTask) {
  Event running;
  using namespace std::chrono_literals;
  async_manager_.ExecAsyncPeriodically(1, 1ms, 2ms, [&running, this]() {
     EXPECT_TRUE(async_manager_.CancelAsyncTask(1)) << "We were scheduled, so cancel should return true";
     EXPECT_FALSE(async_manager_.CancelAsyncTask(1)) << "We were not scheduled, so cancel should return false";
     running.set(true);
  });

  EXPECT_TRUE(running.wait_for(10ms));
}


TEST_F(AsyncManagerSocketTest, UnsubScribeWaitsUntilCompletion) {
  using namespace std::chrono_literals;
  Event running;
  bool cancel_done = false;
  bool task_complete = false;
  async_manager_.ExecAsyncPeriodically(1, 1ms, 2ms, [&running, &cancel_done, &task_complete]() {
      // Let the other thread now we are in the callback..
      running.set(true);
      // Wee bit of a hack that relies on timing..
      std::this_thread::sleep_for(20ms);
      EXPECT_FALSE(cancel_done) << "Task cancellation did not wait for us to complete!";
      task_complete = true;
  });

  EXPECT_TRUE(running.wait_for(10ms));
  auto start = std::chrono::system_clock::now();

  // There is a 20ms wait.. so we know that this should take some time.
  EXPECT_TRUE(async_manager_.CancelAsyncTask(1)) << "We were scheduled, so cancel should return true";
  cancel_done = true;
  EXPECT_TRUE(task_complete) << "We managed to cancel a task while it was not yet finished.";
  auto end = std::chrono::system_clock::now();
  auto passed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
  EXPECT_GT(passed_ms.count(), 10);
}

TEST_F(AsyncManagerSocketTest, NoEventsAfterUnsubscribe) {
  // This tests makes sure the AsyncManager never fires an event
  // after calling StopWatchingFileDescriptor.