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

Commit 4ad0b2ba authored by Jack He's avatar Jack He
Browse files

Add LeakyBondedQueue to libosi

* LeakyBondedQueue is a fixed size queue that leaks oldest item when
  reaching its capacity. This is useful in creating memory bonded data
  structure where freshness is more important than full coverage.
* The queue is protected by a simple mutex and is thread-safe, although
  improvements could be made to lock enqueue and dequeue separately, it
  is not implemented at this moment due to lack of demand
* The queue uses unique_ptr to automatically free its content when it is
  destructed
* Add several tests to verify its API and memory management
* This data structure will be firstly used in the metrics module

Bug: 33781460
Test: Code compilation, unit tests
Change-Id: I51cb73666ac58e4792d9cba0d6f16dad30a0ff39
parent e2d64d8a
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ btosiCommonTestSrc := \
    ./test/fixed_queue_test.cc \
    ./test/future_test.cc \
    ./test/hash_map_utils_test.cc \
    ./test/leaky_bonded_queue_test.cc \
    ./test/list_test.cc \
    ./test/properties_test.cc \
    ./test/rand_test.cc \
@@ -169,7 +170,7 @@ LOCAL_SRC_FILES := $(btosiCommonTestSrc)
LOCAL_MODULE := net_test_osi
LOCAL_MODULE_TAGS := tests
LOCAL_SHARED_LIBRARIES := libc liblog libprotobuf-cpp-lite libchrome libcutils
LOCAL_STATIC_LIBRARIES := libosi libbt-protos
LOCAL_STATIC_LIBRARIES := libosi libbt-protos libgmock

LOCAL_CFLAGS += $(bluetooth_CFLAGS)
LOCAL_CONLYFLAGS += $(bluetooth_CONLYFLAGS)
@@ -188,7 +189,7 @@ LOCAL_LDLIBS := -lrt -lpthread
LOCAL_MODULE := net_test_osi
LOCAL_MODULE_TAGS := tests
LOCAL_SHARED_LIBRARIES := liblog libprotobuf-cpp-lite libchrome
LOCAL_STATIC_LIBRARIES := libosi-host libbt-protos
LOCAL_STATIC_LIBRARIES := libosi-host libbt-protos libgmock

LOCAL_CFLAGS += $(bluetooth_CFLAGS) -DOS_GENERIC
LOCAL_CONLYFLAGS += $(bluetooth_CONLYFLAGS)
+1 −0
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ executable("net_test_osi") {
    "test/eager_reader_test.cc",
    "test/future_test.cc",
    "test/hash_map_utils_test.cc",
    "test/leaky_bonded_queue_test.cc",
    "test/list_test.cc",
    "test/properties_test.cc",
    "test/rand_test.cc",
+158 −0
Original line number Diff line number Diff line
/******************************************************************************
 *
 *  Copyright (C) 2016 Google, Inc.
 *
 *  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:
 *
 *  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.
 *
 ******************************************************************************/
#pragma once

#include <memory>
#include <mutex>
#include <queue>

namespace system_bt_osi {

/*
 *   LeakyBondedQueue<T>
 *
 * - LeakyLondedQueue<T> is a fixed size queue that leaks oldest item when
 *   reaching its capacity. This is useful in creating memory bonded data
 *   structure where freshness is more important than full coverage.
 * - The queue is protected by a simple mutex and is thread-safe, although
 *   improvements could be made to lock enqueue and dequeue separately, it
 *   is not implemented at this moment due to lack of demand
 * - The queue uses unique_ptr to automatically free its content when it is
 *   destructed. It is the user's responsibility to implement T's destructor
 *   correctly.
 *
 */
template <class T>
class LeakyBondedQueue {
 public:
  LeakyBondedQueue(size_t capacity);
  /* Default destructor
   *
   * Call Clear() and free the queue structure itself
   */
  ~LeakyBondedQueue();
  /*
   * Add item NEW_ITEM to the underlining queue. If the queue is full, pop
   * the oldest item
   */
  void Enqueue(T* new_item);
  /*
   * Add item NEW_ITEM to the underlining queue. If the queue is full, dequeue
   * the oldest item and returns it to the caller. Return nullptr otherwise.
   */
  T* EnqueueWithPop(T* new_item);
  /*
   * Dequeues the oldest item from the queue. Return nullptr if queue is empty
   */
  T* Dequeue();
  /*
   * Returns the length of queue
   */
  size_t Length();
  /*
   * Returns the defined capacity of the queue
   */
  size_t Capacity();
  /*
   * Returns whether the queue is empty
   */
  bool Empty();
  /*
   * Pops all items from the queue
   */
  void Clear();

 private:
  // Put item in unique_ptr so that they get freed automatically when poped or
  // when queue_ is freed
  std::queue<std::unique_ptr<T>> queue_;
  std::mutex lock_;
  size_t capacity_;
};

/*
* Definitions must be in the header for template classes
*/

template <class T>
LeakyBondedQueue<T>::LeakyBondedQueue(size_t capacity) {
  capacity_ = capacity;
}

template <class T>
LeakyBondedQueue<T>::~LeakyBondedQueue() {}

template <class T>
void LeakyBondedQueue<T>::Enqueue(T* new_item) {
  std::lock_guard<std::mutex> lock(lock_);
  if ((queue_.size() + 1) > capacity_) {
    queue_.pop();
  }
  std::unique_ptr<T> item_ptr(new_item);
  queue_.push(std::move(item_ptr));
}

template <class T>
T* LeakyBondedQueue<T>::EnqueueWithPop(T* new_item) {
  std::lock_guard<std::mutex> lock(lock_);
  T* old_item = nullptr;
  if ((queue_.size() + 1) > capacity_) {
    std::unique_ptr<T> item = std::move(queue_.front());
    queue_.pop();
    old_item = item.release();
  }
  std::unique_ptr<T> item_ptr(new_item);
  queue_.push(std::move(item_ptr));
  return old_item;
}

template <class T>
T* LeakyBondedQueue<T>::Dequeue() {
  std::lock_guard<std::mutex> lock(lock_);
  std::unique_ptr<T> item = std::move(queue_.front());
  queue_.pop();
  return item.release();
}

template <class T>
void LeakyBondedQueue<T>::Clear() {
  std::lock_guard<std::mutex> lock(lock_);
  while (!queue_.empty()) {
    // unique_ptr does not need to be freed
    queue_.pop();
  }
}

template <class T>
size_t LeakyBondedQueue<T>::Length() {
  std::lock_guard<std::mutex> lock(lock_);
  return queue_.size();
}

template <class T>
size_t LeakyBondedQueue<T>::Capacity() {
  return capacity_;
}

template <class T>
bool LeakyBondedQueue<T>::Empty() {
  std::lock_guard<std::mutex> lock(lock_);
  return queue_.empty();
}

}  // namespace system_bt_osi
+242 −0
Original line number Diff line number Diff line
/******************************************************************************
 *
 *  Copyright (C) 2016 Google, Inc.
 *
 *  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:
 *
 *  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 <gmock/gmock.h>
#include <gtest/gtest.h>

#include <base/logging.h>

#include "osi/include/leaky_bonded_queue.h"

namespace testing {

using system_bt_osi::LeakyBondedQueue;

#define ITEM_EQ(a, b)              \
  do {                             \
    EXPECT_EQ(a, b);               \
    EXPECT_EQ(a->index, b->index); \
  } while (0)

class Item {
 public:
  Item(int i) { index = i; }
  virtual ~Item() {}
  int index;
};

class MockItem : public Item {
 public:
  MockItem(int i) : Item(i) {}
  ~MockItem() { Destruct(); }
  MOCK_METHOD0(Destruct, void());
};

TEST(LeakyBondedQueueTest, TestEnqueueDequeue) {
  MockItem* item1 = new MockItem(1);
  MockItem* item2 = new MockItem(2);
  MockItem* item3 = new MockItem(3);
  MockItem* item4 = new MockItem(4);
  LeakyBondedQueue<MockItem>* queue = new LeakyBondedQueue<MockItem>(3);
  EXPECT_EQ(queue->Capacity(), static_cast<size_t>(3));
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  queue->Enqueue(item2);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  queue->Enqueue(item3);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(3));
  EXPECT_CALL(*item1, Destruct()).Times(1);
  queue->Enqueue(item4);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(3));
  MockItem* item2_2 = queue->Dequeue();
  MockItem* item3_3 = queue->Dequeue();
  MockItem* item4_4 = queue->Dequeue();
  EXPECT_THAT(item2_2, NotNull());
  ITEM_EQ(item2_2, item2);
  EXPECT_THAT(item3_3, NotNull());
  ITEM_EQ(item3_3, item3);
  EXPECT_THAT(item4_4, NotNull());
  ITEM_EQ(item4_4, item4);
  LOG(INFO) << "All done release items";
  EXPECT_CALL(*item2_2, Destruct()).Times(1);
  delete item2_2;
  EXPECT_CALL(*item3_3, Destruct()).Times(1);
  delete item3_3;
  EXPECT_CALL(*item4_4, Destruct()).Times(1);
  delete item4_4;
  delete queue;
}

TEST(LeakyBondedQueueTest, TestEnqueueDequeue2) {
  MockItem* item1 = new MockItem(1);
  MockItem* item2 = new MockItem(2);
  MockItem* item3 = new MockItem(3);
  MockItem* item4 = new MockItem(4);
  LeakyBondedQueue<MockItem>* queue = new LeakyBondedQueue<MockItem>(2);
  EXPECT_EQ(queue->Capacity(), static_cast<size_t>(2));
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  MockItem* item1_1 = queue->Dequeue();
  ITEM_EQ(item1, item1_1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item2);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  queue->Enqueue(item3);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  EXPECT_CALL(*item2, Destruct()).Times(1);
  queue->Enqueue(item4);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  EXPECT_CALL(*item3, Destruct()).Times(1);
  queue->Enqueue(item1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  MockItem* item4_4_4 = queue->Dequeue();
  MockItem* item1_1_1 = queue->Dequeue();
  ITEM_EQ(item4_4_4, item4);
  ITEM_EQ(item1_1_1, item1);
  EXPECT_CALL(*item1_1_1, Destruct()).Times(1);
  delete item1_1_1;
  EXPECT_CALL(*item4_4_4, Destruct()).Times(1);
  delete item4_4_4;
  delete queue;
}

TEST(LeakyBondedQueueTest, TestEnqueuePop) {
  MockItem* item1 = new MockItem(1);
  MockItem* item2 = new MockItem(2);
  MockItem* item3 = new MockItem(3);
  MockItem* item4 = new MockItem(4);
  LeakyBondedQueue<MockItem>* queue = new LeakyBondedQueue<MockItem>(2);
  EXPECT_EQ(queue->Capacity(), static_cast<size_t>(2));
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  MockItem* item1_1 = queue->Dequeue();
  ITEM_EQ(item1, item1_1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item2);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  queue->Enqueue(item3);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  MockItem* item2_2 = queue->EnqueueWithPop(item4);
  EXPECT_THAT(item2_2, NotNull());
  ITEM_EQ(item2_2, item2);
  EXPECT_CALL(*item2, Destruct()).Times(1);
  delete item2_2;
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  MockItem* item3_3 = queue->EnqueueWithPop(item1);
  EXPECT_THAT(item3_3, NotNull());
  ITEM_EQ(item3_3, item3);
  EXPECT_CALL(*item3, Destruct()).Times(1);
  delete item3_3;
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  MockItem* item4_4_4 = queue->Dequeue();
  MockItem* item1_1_1 = queue->Dequeue();
  ITEM_EQ(item4_4_4, item4);
  ITEM_EQ(item1_1_1, item1);
  EXPECT_CALL(*item1_1_1, Destruct()).Times(1);
  delete item1_1_1;
  EXPECT_CALL(*item4_4_4, Destruct()).Times(1);
  delete item4_4_4;
  delete queue;
}

TEST(LeakyBondedQueueTest, TestQueueClear) {
  MockItem* item1 = new MockItem(1);
  MockItem* item2 = new MockItem(2);
  MockItem* item3 = new MockItem(3);
  MockItem* item4 = new MockItem(4);
  LeakyBondedQueue<MockItem>* queue = new LeakyBondedQueue<MockItem>(2);
  EXPECT_EQ(queue->Capacity(), static_cast<size_t>(2));
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  MockItem* item1_1 = queue->Dequeue();
  ITEM_EQ(item1, item1_1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item2);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  queue->Enqueue(item3);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  EXPECT_CALL(*item2, Destruct()).Times(1);
  queue->Enqueue(item4);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  EXPECT_CALL(*item3, Destruct()).Times(1);
  queue->Enqueue(item1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  EXPECT_CALL(*item1, Destruct()).Times(1);
  EXPECT_CALL(*item4, Destruct()).Times(1);
  queue->Clear();
  delete queue;
}

TEST(LeakyBondedQueueTest, TestQueueFree) {
  MockItem* item1 = new MockItem(1);
  MockItem* item2 = new MockItem(2);
  MockItem* item3 = new MockItem(3);
  MockItem* item4 = new MockItem(4);
  LeakyBondedQueue<MockItem>* queue = new LeakyBondedQueue<MockItem>(2);
  EXPECT_EQ(queue->Capacity(), static_cast<size_t>(2));
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  MockItem* item1_1 = queue->Dequeue();
  ITEM_EQ(item1, item1_1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(0));
  queue->Enqueue(item2);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(1));
  queue->Enqueue(item3);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  EXPECT_CALL(*item2, Destruct()).Times(1);
  queue->Enqueue(item4);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  EXPECT_CALL(*item3, Destruct()).Times(1);
  queue->Enqueue(item1);
  EXPECT_EQ(queue->Length(), static_cast<size_t>(2));
  EXPECT_CALL(*item1, Destruct()).Times(1);
  EXPECT_CALL(*item4, Destruct()).Times(1);
  delete queue;
}

TEST(LeakyBondedQueueTest, TestPushNull) {
  MockItem* item1 = nullptr;
  LeakyBondedQueue<MockItem>* queue = new LeakyBondedQueue<MockItem>(2);
  queue->Enqueue(item1);
  MockItem* item1_1 = queue->Dequeue();
  EXPECT_THAT(item1_1, IsNull());
}

TEST(LeakyBondedQueueTest, TestPushNullOverflowQueue) {
  MockItem* item1 = nullptr;
  MockItem* item2 = nullptr;
  LeakyBondedQueue<MockItem>* queue = new LeakyBondedQueue<MockItem>(1);
  queue->Enqueue(item1);
  queue->Enqueue(item2);
  MockItem* item2_2 = queue->Dequeue();
  EXPECT_THAT(item2_2, IsNull());
}

TEST(LeakyBondedQueueTest, TestPushNullDeleteQueue) {
  MockItem* item1 = nullptr;
  MockItem* item2 = nullptr;
  LeakyBondedQueue<MockItem>* queue = new LeakyBondedQueue<MockItem>(2);
  queue->Enqueue(item1);
  queue->Enqueue(item2);
  delete queue;
}
}