testing: pools
authorMichael Wallner <mike@php.net>
Tue, 6 Oct 2020 07:43:17 +0000 (09:43 +0200)
committerMichael Wallner <mike@php.net>
Tue, 6 Oct 2020 07:43:17 +0000 (09:43 +0200)
CMake/_Include.cmake
test/tests/memcached/regression/lp_000-962-815.cpp [new file with mode: 0644]
test/tests/memcached/util.cpp
test/tests/memcached/util_pool_thread.cpp [new file with mode: 0644]

index a6b5463c1a1b201be45a823df8a0f5d1884e0cef..3c00c79c7306aea7e351ae4e3e9189ca01e2c5eb 100644 (file)
@@ -78,6 +78,7 @@ check_header(io.h)
 check_header(limits.h)
 check_header(netdb.h)
 check_header(poll.h)
+check_header(semaphore.h)
 check_header(stddef.h)
 check_header(stdlib.h)
 check_header(strings.h)
diff --git a/test/tests/memcached/regression/lp_000-962-815.cpp b/test/tests/memcached/regression/lp_000-962-815.cpp
new file mode 100644 (file)
index 0000000..658c339
--- /dev/null
@@ -0,0 +1,92 @@
+#include "test/lib/common.hpp"
+#include "test/lib/MemcachedCluster.hpp"
+
+#include "libmemcachedutil-1.0/pool.h"
+
+#include <atomic>
+#include <sstream>
+
+static atomic<bool> running = false;
+
+static inline void set_running(bool is) {
+  running.store(is, memory_order_release);
+}
+static inline bool is_running() {
+  return running.load(memory_order_consume);
+}
+
+struct worker_ctx {
+  memcached_pool_st *pool;
+  vector<stringstream> errors;
+
+  explicit worker_ctx(memcached_st *memc)
+  : pool{memcached_pool_create(memc, 5, 10)}
+  , errors{}
+  {
+  }
+
+  ~worker_ctx() {
+    memcached_pool_destroy(pool);
+    for (const auto &err : errors) {
+      cerr << err.str() << endl;
+    }
+  }
+
+  stringstream &err() {
+    return errors[errors.size()];
+  }
+};
+
+static void *worker(void *arg) {
+  auto *ctx = static_cast<worker_ctx *>(arg);
+
+  while (is_running()) {
+    memcached_return_t rc;
+    timespec block{5, 0};
+    auto *mc = memcached_pool_fetch(ctx->pool, &block, &rc);
+
+    if (!mc || memcached_failed(rc)) {
+      ctx->err() << "failed to fetch connection from pool: "
+                 << memcached_strerror(nullptr, rc);
+      this_thread::sleep_for(100ms);
+    }
+
+    auto rs = random_ascii_string(12);
+    rc = memcached_set(mc, rs.c_str(), rs.length(), rs.c_str(), rs.length(), 0, 0);
+    if (memcached_failed(rc)) {
+      ctx->err() << "failed to memcached_set() "
+                 << memcached_last_error_message(mc);
+    }
+    rc = memcached_pool_release(ctx->pool, mc);
+    if (memcached_failed(rc)) {
+      ctx->err() << "failed to release connection to pool: "
+                 << memcached_strerror(nullptr, rc);
+    }
+  }
+
+  return ctx;
+}
+
+TEST_CASE("memcached_regression_lp962815") {
+  auto test = MemcachedCluster::mixed();
+  auto memc = &test.memc;
+
+  constexpr auto NUM_THREADS = 20;
+  array<pthread_t, NUM_THREADS> tid;
+  worker_ctx ctx{memc};
+
+  set_running(true);
+
+  for (auto &t : tid) {
+    REQUIRE(0 == pthread_create(&t, nullptr, worker, &ctx));
+  }
+
+  this_thread::sleep_for(5s);
+  set_running(false);
+
+  for (auto t : tid) {
+    void *ret = nullptr;
+    REQUIRE(0 == pthread_join(t, &ret));
+    REQUIRE(ret == &ctx);
+  }
+}
index 9612b1d08b5d61f10d24f09cf5424f1d0e0e3062..3fb018a3a635708e624daed8fd98b0c55ecdc612 100644 (file)
@@ -73,4 +73,94 @@ TEST_CASE("memcached_util") {
       REQUIRE_SUCCESS(rc);
     }
   }
+
+  SECTION("pool") {
+    SECTION("deprecated") {
+      auto conf = "--SERVER=host10.example.com --SERVER=host11.example.com --SERVER=host10.example.com --POOL-MIN=10 --POOL-MAX=32";
+      auto pool = memcached_pool(S(conf));
+      REQUIRE(pool);
+
+      memcached_return_t rc;
+      auto memc = memcached_pool_pop(pool, false, &rc);
+      REQUIRE(memc);
+      REQUIRE(MEMCACHED_SUCCESS == rc);
+
+      REQUIRE(MEMCACHED_SUCCESS == memcached_pool_push(pool, memc));
+      REQUIRE(nullptr == memcached_pool_destroy(pool));
+    }
+
+    SECTION("basic") {
+      auto test = MemcachedCluster::mixed();
+      auto memc = &test.memc;
+      memcached_return_t rc;
+
+      constexpr auto POOL_MIN = 5;
+      constexpr auto POOL_MAX = 10;
+
+      auto pool = memcached_pool_create(memc, POOL_MIN, POOL_MAX);
+      REQUIRE(pool);
+
+      array<memcached_st *, POOL_MAX> hold{};
+      for (auto &h : hold) {
+        h = memcached_pool_fetch(pool, nullptr, &rc);
+        REQUIRE_SUCCESS(rc);
+        REQUIRE(h);
+      }
+
+      SECTION("depleted") {
+        REQUIRE(nullptr == memcached_pool_fetch(pool, nullptr, &rc));
+        REQUIRE_RC(MEMCACHED_NOTFOUND, rc);
+      }
+
+      SECTION("usable") {
+        for (auto h : hold) {
+          auto s = to_string(reinterpret_cast<uintptr_t>(h));
+          REQUIRE_SUCCESS(memcached_set(h, s.c_str(), s.length(), s.c_str(), s.length(), 0, 0));
+        }
+        for (auto h : hold) {
+          auto s = to_string(reinterpret_cast<uintptr_t>(h));
+          Malloced val(memcached_get(h, s.c_str(), s.length(), nullptr, nullptr, &rc));
+          REQUIRE_SUCCESS(rc);
+          REQUIRE(*val);
+          REQUIRE(h == reinterpret_cast<memcached_st *>(stoul(*val)));
+        }
+
+        REQUIRE_SUCCESS(memcached_set(hold[0], S(__func__), "0", 1, 0, 0));
+        uint64_t inc = 0;
+        for (auto h : hold) {
+          uint64_t val;
+          REQUIRE_SUCCESS(memcached_increment(h, S(__func__), 1, &val));
+          CHECK(++inc == val);
+        }
+      }
+
+      SECTION("behaviors") {
+        uint64_t val;
+        REQUIRE_SUCCESS(memcached_pool_behavior_get(pool, MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK, &val));
+        REQUIRE_FALSE(val == 9999);
+        REQUIRE_SUCCESS(memcached_pool_behavior_set(pool, MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK, 9999));
+        REQUIRE_SUCCESS(memcached_pool_behavior_get(pool, MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK, &val));
+        REQUIRE(val == 9999);
+
+        for (auto &h : hold) {
+          REQUIRE_FALSE(9999 == memcached_behavior_get(h, MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK));
+          REQUIRE_SUCCESS(memcached_pool_release(pool, h));
+          h = memcached_pool_fetch(pool, nullptr, &rc);
+          REQUIRE_SUCCESS(rc);
+          REQUIRE(h);
+          REQUIRE(9999 == memcached_behavior_get(h, MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK));
+        }
+      }
+
+      for (auto h : hold) {
+        REQUIRE_SUCCESS(memcached_pool_release(pool, h));
+        auto again = memcached_pool_fetch(pool, nullptr, &rc);
+        REQUIRE_SUCCESS(rc);
+        REQUIRE(again);
+        REQUIRE_SUCCESS(memcached_pool_release(pool, again));
+      }
+
+      REQUIRE(memc == memcached_pool_destroy(pool));
+    }
+  }
 }
diff --git a/test/tests/memcached/util_pool_thread.cpp b/test/tests/memcached/util_pool_thread.cpp
new file mode 100644 (file)
index 0000000..8833028
--- /dev/null
@@ -0,0 +1,104 @@
+#include "test/lib/common.hpp"
+
+#include "libmemcachedutil-1.0/pool.h"
+
+#include <cassert>
+
+#if HAVE_SEMAPHORE_H
+
+#include "semaphore.h"
+
+#ifndef __APPLE__
+struct test_pool_context_st {
+  volatile memcached_return_t rc;
+  memcached_pool_st *pool;
+  memcached_st *memc;
+  sem_t _lock;
+
+  test_pool_context_st(memcached_pool_st *pool_arg, memcached_st *memc_arg) :
+      rc(MEMCACHED_FAILURE),
+      pool(pool_arg),
+      memc(memc_arg) {
+    sem_init(&_lock, 0, 0);
+  }
+
+  void wait() {
+    sem_wait(&_lock);
+  }
+
+  void release() {
+    sem_post(&_lock);
+  }
+
+  ~test_pool_context_st() {
+    sem_destroy(&_lock);
+  }
+};
+
+static void *connection_release(void *arg) {
+  assert(arg != nullptr);
+
+  this_thread::sleep_for(2s);
+  auto res = static_cast<test_pool_context_st *>(arg);
+  res->rc = memcached_pool_release(res->pool, res->memc);
+  res->release();
+
+  pthread_exit(arg);
+}
+#endif
+
+TEST_CASE("memcached_util_pool_thread") {
+#ifdef __APPLE__
+  SUCCEED("skip: pthreads");
+#else
+  MemcachedPtr memc;
+  auto pool = memcached_pool_create(*memc, 1, 1);
+  REQUIRE(pool);
+
+  memcached_return_t rc;
+  auto pool_memc = memcached_pool_fetch(pool, nullptr, &rc);
+  REQUIRE(MEMCACHED_SUCCESS == rc);
+  REQUIRE(pool_memc);
+
+  /*
+    @note This comment was written to describe what was believed to be the original authors intent.
+
+    This portion of the test creates a thread that will wait until told to free a memcached_st
+    that will be grabbed by the main thread.
+
+    It is believed that this tests whether or not we are handling ownership correctly.
+  */
+  pthread_t tid;
+  test_pool_context_st item(pool, pool_memc);
+
+  REQUIRE(0 == pthread_create(&tid, nullptr, connection_release, &item));
+  item.wait();
+
+  memcached_st *pop_memc;
+  // We do a hard loop, and try N times
+  int counter = 5;
+  do {
+    struct timespec relative_time = {0, 0};
+    pop_memc = memcached_pool_fetch(pool, &relative_time, &rc);
+
+    if (memcached_success(rc)) {
+      break;
+    }
+cerr << "rc == " << memcached_strerror(nullptr, rc);
+    if (memcached_failed(rc)) {
+      REQUIRE_FALSE(pop_memc);
+      REQUIRE(rc != MEMCACHED_TIMEOUT); // As long as relative_time is zero, MEMCACHED_TIMEOUT is invalid
+    }
+  } while (--counter);
+
+  // Cleanup thread since we will exit once we test.
+  REQUIRE(0 == pthread_join(tid, nullptr));
+  REQUIRE(MEMCACHED_SUCCESS == rc);
+  REQUIRE(pool_memc == pop_memc);
+  REQUIRE(MEMCACHED_SUCCESS == memcached_pool_release(pool, pop_memc));
+  REQUIRE(memcached_pool_destroy(pool) == *memc);
+
+#endif __APPLE__
+}
+
+#endif // HAVE_SEMAPHORE_H