hashkit_return_t set_distribution_function(hashkit_hash_algorithm_t hash_algorithm)
{
- return hashkit_set_function(&self, hash_algorithm);
+ return hashkit_set_distribution_function(&self, hash_algorithm);
}
~Hashkit()
add_executable(catch_main
main.cpp
- lib/random_.cpp
+ lib/common.cpp
lib/Cluster.cpp
- lib/Poll.cpp
+ lib/Connection.cpp
+ lib/ForkAndExec.cpp
+ lib/MemcachedCluster.cpp
lib/Server.cpp
- lib/WaitForConn.cpp
- lib/WaitForExec.cpp
+ lib/Retry.cpp
- server.cpp
+ lib.cpp
+ hashkit/basic.cpp
+ memcached/basic.cpp
+ memcached/callbacks.cpp
+ memcached/servers.cpp
+ memcached/dump.cpp
+ memcached/encoding_key.cpp
+ memcached/exist.cpp
)
set_target_properties(catch_main PROPERTIES
CXX_STANDARD 17)
+
+target_link_libraries(catch_main libhashkit libmemcached)
--- /dev/null
+#include "../lib/common.hpp"
+
+#include "libhashkit-1.0/hashkit.hpp"
+
+static const char *input[] = {
+ "apple",
+ "beat",
+ "carrot",
+ "daikon",
+ "eggplant",
+ "flower",
+ "green",
+ "hide",
+ "ick",
+ "jack",
+ "kick",
+ "lime",
+ "mushrooms",
+ "nectarine",
+ "orange",
+ "peach",
+ "quant",
+ "ripen",
+ "strawberry",
+ "tang",
+ "up",
+ "volumne",
+ "when",
+ "yellow",
+ "zip",
+};
+
+static const uint32_t output[][sizeof(input)/sizeof(*input)] = {
+ // one_at_a_time
+ { 2297466611U, 3902465932U, 469785835U, 1937308741U,
+ 261917617U, 3785641677U, 1439605128U, 1649152283U,
+ 1493851484U, 1246520657U, 2221159044U, 1973511823U,
+ 384136800U, 214358653U, 2379473940U, 4269788650U,
+ 2864377005U, 2638630052U, 427683330U, 990491717U,
+ 1747111141U, 792127364U, 2599214128U, 2553037199U,
+ 2509838425U },
+
+ // md5
+ { 3195025439U, 2556848621U, 3724893440U, 3332385401U,
+ 245758794U, 2550894432U, 121710495U, 3053817768U,
+ 1250994555U, 1862072655U, 2631955953U, 2951528551U,
+ 1451250070U, 2820856945U, 2060845566U, 3646985608U,
+ 2138080750U, 217675895U, 2230934345U, 1234361223U,
+ 3968582726U, 2455685270U, 1293568479U, 199067604U,
+ 2042482093U },
+
+ // crc
+ { 10542U, 22009U, 14526U, 19510U, 19432U, 10199U, 20634U,
+ 9369U, 11511U, 10362U, 7893U, 31289U, 11313U, 9354U,
+ 7621U, 30628U, 15218U, 25967U, 2695U, 9380U,
+ 17300U, 28156U, 9192U, 20484U, 16925U },
+
+ // fnv1_64
+ { 473199127U, 4148981457U, 3971873300U, 3257986707U,
+ 1722477987U, 2991193800U, 4147007314U, 3633179701U,
+ 1805162104U, 3503289120U, 3395702895U, 3325073042U,
+ 2345265314U, 3340346032U, 2722964135U, 1173398992U,
+ 2815549194U, 2562818319U, 224996066U, 2680194749U,
+ 3035305390U, 246890365U, 2395624193U, 4145193337U,
+ 1801941682U },
+
+ // fnv1a_64
+ { 1488911807U, 2500855813U, 1510099634U, 1390325195U,
+ 3647689787U, 3241528582U, 1669328060U, 2604311949U,
+ 734810122U, 1516407546U, 560948863U, 1767346780U,
+ 561034892U, 4156330026U, 3716417003U, 3475297030U,
+ 1518272172U, 227211583U, 3938128828U, 126112909U,
+ 3043416448U, 3131561933U, 1328739897U, 2455664041U,
+ 2272238452U },
+
+ // fnv1_32
+ { 67176023U, 1190179409U, 2043204404U, 3221866419U,
+ 2567703427U, 3787535528U, 4147287986U, 3500475733U,
+ 344481048U, 3865235296U, 2181839183U, 119581266U,
+ 510234242U, 4248244304U, 1362796839U, 103389328U,
+ 1449620010U, 182962511U, 3554262370U, 3206747549U,
+ 1551306158U, 4127558461U, 1889140833U, 2774173721U,
+ 1180552018U },
+
+ // fnv1a_32
+ { 280767167U, 2421315013U, 3072375666U, 855001899U,
+ 459261019U, 3521085446U, 18738364U, 1625305005U,
+ 2162232970U, 777243802U, 3323728671U, 132336572U,
+ 3654473228U, 260679466U, 1169454059U, 2698319462U,
+ 1062177260U, 235516991U, 2218399068U, 405302637U,
+ 1128467232U, 3579622413U, 2138539289U, 96429129U,
+ 2877453236U },
+
+ // hsieh
+#ifdef HAVE_HSIEH_HASH
+ { 3738850110U, 3636226060U, 3821074029U, 3489929160U, 3485772682U, 80540287U,
+ 1805464076U, 1895033657U, 409795758U, 979934958U, 3634096985U, 1284445480U,
+ 2265380744U, 707972988U, 353823508U, 1549198350U, 1327930172U, 9304163U,
+ 4220749037U, 2493964934U, 2777873870U, 2057831732U, 1510213931U, 2027828987U,
+ 3395453351U },
+#else
+ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+#endif
+
+ // murmur
+#ifdef HAVE_MURMUR_HASH
+ // murmur
+ { 4142305122U, 734504955U, 3802834688U, 4076891445U,
+ 387802650U, 560515427U, 3274673488U, 3150339524U,
+ 1527441970U, 2728642900U, 3613992239U, 2938419259U,
+ 2321988328U, 1145154116U, 4038540960U, 2224541613U,
+ 264013145U, 3995512858U, 2400956718U, 2346666219U,
+ 926327338U, 442757446U, 1770805201U, 560483147U,
+ 3902279934U },
+ // murmur3
+ { 1120212521U, 1448785489U, 4186307405U, 2686268514U,
+ 444808887U, 221750260U, 3074673162U, 1946933257U,
+ 2826416675U, 2430719166U, 3200429559U, 297894347U,
+ 732888124U, 4050076964U, 3298336176U, 1336207361U,
+ 810553576U, 3748182674U, 3860119212U, 3439537197U,
+ 3044240981U, 1464271804U, 3896193724U, 2915115798U,
+ 1702843840U },
+#else
+ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+#endif
+
+ // jenkins
+ { 1442444624U, 4253821186U, 1885058256U, 2120131735U,
+ 3261968576U, 3515188778U, 4232909173U, 4288625128U,
+ 1812047395U, 3689182164U, 2502979932U, 1214050606U,
+ 2415988847U, 1494268927U, 1025545760U, 3920481083U,
+ 4153263658U, 3824871822U, 3072759809U, 798622255U,
+ 3065432577U, 1453328165U, 2691550971U, 3408888387U,
+ 2629893356U }
+};
+
+TEST_CASE("hashkit") {
+ hashkit_st st, *hp = hashkit_create(nullptr);
+ Hashkit stack;
+ Hashkit *heap = new Hashkit;
+
+ REQUIRE(hashkit_create(&st));
+ REQUIRE(hp);
+
+ SECTION("can copy") {
+ Hashkit stack_copy(stack);
+ Hashkit *heap_copy(heap);
+ hashkit_st st_copy, *st_ptr;
+
+ (void) stack_copy;
+ (void) heap_copy;
+
+ st_ptr = hashkit_clone(&st_copy, &st);
+ REQUIRE(st_ptr == &st_copy);
+ REQUIRE(hashkit_compare(st_ptr, &st_copy));
+
+ SUCCEED("OK");
+ }
+
+ SECTION("can assign") {
+ Hashkit stack_copy;
+
+ stack_copy = stack;
+ (void) stack_copy;
+
+ SUCCEED("OK");
+ }
+
+ SECTION("can digest default") {
+ REQUIRE(2297466611U == stack.digest(LITERAL("apple")));
+ REQUIRE(2297466611U == hashkit_digest(&st, LITERAL("apple")));
+ }
+
+ SECTION("can set hash function") {
+ for (int f = HASHKIT_HASH_DEFAULT; f < HASHKIT_HASH_MAX; ++f) {
+ auto h = static_cast<hashkit_hash_algorithm_t>(f);
+
+ if (h == HASHKIT_HASH_CUSTOM) {
+ continue;
+ }
+ if (!libhashkit_has_algorithm(h)) {
+ WARN("hashkit algorithm not enabled: " << libhashkit_string_hash(h) << " (" << f << ")");
+ continue;
+ }
+
+ REQUIRE(HASHKIT_SUCCESS == stack.set_function(h));
+ REQUIRE(HASHKIT_SUCCESS == hashkit_set_function(&st, h));
+
+ SECTION("can digest set hash function") {
+ auto n = 0;
+
+ for (auto i : input) {
+ CHECK(output[f][n] == stack.digest(LITERAL(i)));
+ CHECK(output[f][n] == hashkit_digest(&st, LITERAL(i)));
+ CHECK(output[f][n] == libhashkit_digest(LITERAL(i), h));
+ ++n;
+ }
+ }
+ }
+ }
+
+ SECTION("is comparable") {
+ REQUIRE(*heap == stack);
+ REQUIRE(hashkit_compare(&st, hp));
+
+ stack.set_function(HASHKIT_HASH_MD5);
+ hashkit_set_function(&st, HASHKIT_HASH_MD5);
+
+ REQUIRE_FALSE(*heap == stack);
+ REQUIRE_FALSE(hashkit_compare(&st, hp));
+ }
+
+ delete heap;
+ hashkit_free(&st);
+ hashkit_free(hp);
+}
--- /dev/null
+#include "lib/common.hpp"
+#include "lib/Cluster.hpp"
+#include "lib/Retry.hpp"
+#include "lib/Server.hpp"
+
+TEST_CASE("lib/Server") {
+ Server server{"memcached"};
+
+ SECTION("starts and listens") {
+
+ REQUIRE(server.start().has_value());
+
+ Retry server_is_listening{[&server] {
+ return server.isListening();
+ }};
+ REQUIRE(server_is_listening());
+
+ SECTION("stops") {
+
+ REQUIRE(server.stop());
+
+ SECTION("is waitable") {
+
+ REQUIRE(server.wait());
+
+ SECTION("stopped") {
+
+ REQUIRE_FALSE(server.check());
+ }
+ }
+ }
+ }
+}
+
+TEST_CASE("lib/Cluster") {
+ Cluster cluster{Server{"memcached", {
+ random_socket_or_port_arg(),
+ }}};
+
+ SECTION("starts and listens") {
+
+ REQUIRE(cluster.start());
+
+ Retry cluster_is_listening{[&cluster] {
+ return cluster.isListening();
+ }};
+ REQUIRE(cluster_is_listening());
+
+ SECTION("stops") {
+
+ cluster.stop();
+ cluster.wait();
+
+ SECTION("stopped") {
+
+ REQUIRE(cluster.isStopped());
+ }
+ }
+ }
+}
#include <sys/wait.h>
+Cluster::Cluster(Server &&serv, uint16_t cnt)
+: count{cnt}
+, proto{forward<Server>(serv)}
+{
+ if (!cnt) {
+ count = thread::hardware_concurrency()/2 ?: 4;
+ }
+ reset();
+}
+
+Cluster::~Cluster() {
+ stop();
+ wait();
+}
+
+const vector<Server> &Cluster::getServers() const {
+ return cluster;
+}
+
void Cluster::reset() {
pids.clear();
cluster.clear();
for (auto &server : cluster) {
auto pid = server.start();
- if (started &= pid.has_value()) {
+ if (pid.has_value()) {
pids[*pid] = &server;
+ } else {
+ started = false;
}
}
void Cluster::stop() {
for (auto &server : cluster) {
- server.stop();
+ // no cookies for memcached; TERM is just too slow
+ server.signal(SIGKILL);
}
}
return true;
}
-bool Cluster::isListening(int max_timeout) {
- vector<WaitForConn::conn_t> conns;
-
+bool Cluster::isListening() {
for (auto &server : cluster) {
- auto conn = server.createSocket();
- if (!conn) {
- return false;
+ if (!server.isListening()) {
+ // zombie?
+ auto old_pid = server.getPid();
+ if (server.tryWait()) {
+ pids.erase(old_pid);
+ auto pid = server.start();
+ if (pid.has_value()) {
+ pids[*pid] = &server;
+ }
+ }
+ return server.isListening();
}
- conns.emplace_back(conn.value());
}
- WaitForConn wait_for_conn{
- std::move(conns),
- Poll{POLLOUT, 2, max_timeout}
- };
- return wait_for_conn();
+ return true;
}
void Cluster::wait() {
return;
}
- if (pids[inf.si_pid]) {
- pids[inf.si_pid]->wait();
+ auto server = pids.find(inf.si_pid);
+ if (server != pids.end()) {
+ server->second->wait();
}
}
}
+
#pragma once
+#include "common.hpp"
#include "Server.hpp"
-#include <map>
-#include <thread>
-
class Cluster {
-private:
- uint16_t count;
- Server proto;
- vector<Server> cluster;
-
- map<pid_t, Server *> pids;
-
public:
explicit
- Cluster(Server &&serv, uint16_t cnt = 0)
- : count{cnt}
- , proto{serv}
- {
- if (!cnt) {
- count = thread::hardware_concurrency() ?: 4;
- }
- reset();
- }
+ Cluster(Server &&serv, uint16_t cnt = 0);
+
+ ~Cluster();
+
+ Cluster(const Cluster &c) = delete;
+ Cluster &operator = (const Cluster &c) = delete;
+
+ Cluster(Cluster &&c) = default;
+ Cluster &operator = (Cluster &&c) = default;
+
+ const vector<Server> &getServers() const;
bool start();
void stop();
void reset();
bool isStopped();
- bool isListening(int max_timeout = 1000);
+ bool isListening();
void wait();
+
+private:
+ uint16_t count;
+ Server proto;
+ vector<Server> cluster;
+ map<pid_t, Server *> pids;
};
--- /dev/null
+#include "Connection.hpp"
+
+#include <cerrno>
+#include <sys/poll.h>
+#include <unistd.h>
+
+Connection::Connection(socket_or_port_t socket_or_port) {
+ if (holds_alternative<string>(socket_or_port)) {
+ const auto path = get<string>(socket_or_port);
+ const auto safe = path.c_str();
+ const auto zlen = path.length() + 1;
+ const auto ulen = sizeof(sockaddr_un) - sizeof(sa_family_t);
+
+ if (zlen >= ulen) {
+ throw invalid_argument(error({"socket(): path too long '", path, "'"}));
+ }
+
+ if (0 > (sock = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) {
+ throw runtime_error(error({"socket(): ", strerror(errno)}));
+ }
+
+ auto sa = reinterpret_cast<sockaddr_un *>(&addr);
+ sa->sun_family = AF_UNIX;
+ copy(safe, safe + zlen, sa->sun_path);
+
+ size = UNIX;
+
+ } else {
+ if (0 > (sock = socket(AF_INET6, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) {
+ throw runtime_error(error({"socket(): ", strerror(errno)}));
+ }
+
+ const auto port = get<int>(socket_or_port);
+ auto sa = reinterpret_cast<struct sockaddr_in6 *>(&addr);
+ sa->sin6_family = AF_INET6;
+ sa->sin6_port = htons(static_cast<unsigned short>(port));
+ sa->sin6_addr = IN6ADDR_LOOPBACK_INIT;
+
+ size = INET6;
+ }
+}
+
+Connection::~Connection() {
+ close();
+}
+
+void swap(Connection &a, Connection &b) {
+ a.swap(b);
+}
+
+void Connection::swap(Connection &conn) {
+ Connection copy(conn);
+ conn.sock = sock;
+ conn.addr = addr;
+ conn.size = size;
+ conn.last_err = last_err;
+ sock = exchange(copy.sock, -1);
+ addr = copy.addr;
+ size = copy.size;
+ last_err = copy.last_err;
+}
+
+Connection::Connection(const Connection &conn) {
+ if (conn.sock > -1) {
+ sock = dup(conn.sock);
+ }
+ addr = conn.addr;
+ size = conn.size;
+ last_err = conn.last_err;
+}
+
+Connection &Connection::operator=(const Connection &conn) {
+ Connection copy(conn);
+ copy.swap(*this);
+ return *this;
+}
+
+Connection::Connection(Connection &&conn) noexcept {
+ close();
+ swap(conn);
+}
+
+Connection &Connection::operator=(Connection &&conn) noexcept {
+ Connection copy(forward<Connection>(conn));
+ copy.swap(*this);
+ return *this;
+}
+
+void Connection::close() {
+ if (sock > -1) {
+ ::close(sock);
+ sock = -1;
+ last_err = -1;
+ }
+}
+
+int Connection::getError() {
+ int err = -1;
+ socklen_t len = sizeof(int);
+ if (sock > -1) {
+ errno = 0;
+ if (0 > getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len)) {
+ err = errno;
+ }
+ }
+ last_err = err;
+ return err;
+}
+
+int Connection::getLastError() {
+ if (last_err == -1) {
+ return getError();
+ }
+ return last_err;
+}
+
+bool Connection::isWritable() {
+ pollfd fd{sock, POLLOUT, 0};
+ if (1 > poll(&fd, 1, 0)) {
+ return false;
+ }
+ if (fd.revents & (POLLNVAL|POLLERR|POLLHUP)) {
+ return false;
+ }
+ return fd.revents & POLLOUT;
+}
+
+bool Connection::isOpen() {
+ if (sock > -1){
+ if (isWritable()) {
+ return getError() == 0;
+ } else if (open()) {
+ if (isWritable()) {
+ return getError() == 0;
+ }
+ }
+ }
+ return false;
+}
+
+bool Connection::open() {
+ if (connected) {
+ return true;
+ }
+ connect_again:
+ errno = 0;
+ if (0 == ::connect(sock, reinterpret_cast<sockaddr *>(&addr), size)) {
+ connected = true;
+ return true;
+ }
+
+ switch (errno) {
+ case EINTR:
+ goto connect_again;
+ case EISCONN:
+ connected = true;
+ [[fallthrough]];
+ case EAGAIN:
+ case EALREADY:
+ case EINPROGRESS:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+string Connection::error(const initializer_list<string> &args) {
+ stringstream ss;
+
+ for (auto &arg : args) {
+ ss << arg;
+ }
+
+ cerr << ss.str() << endl;
+ return ss.str();
+}
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+class Connection {
+public:
+ explicit Connection(socket_or_port_t socket_or_port);
+ ~Connection();
+
+ friend void swap(Connection &a, Connection &b);
+ void swap(Connection &conn);
+
+ Connection(const Connection &conn);
+ Connection &operator = (const Connection &conn);
+
+ Connection(Connection &&conn) noexcept ;
+ Connection &operator = (Connection &&conn) noexcept ;
+
+ int getError();
+ int getLastError();
+
+ bool isWritable();
+ bool isOpen();
+
+ bool open();
+ void close();
+
+private:
+ int sock{-1}, last_err{-1};
+ sockaddr_storage addr{0, {0}, 0};
+ enum sockaddr_size {
+ NONE = 0,
+ UNIX = sizeof(sockaddr_un),
+ INET = sizeof(sockaddr_in),
+ INET6 = sizeof(sockaddr_in6)
+ } size;
+ bool connected{false};
+
+ static string error(const initializer_list<string> &args);
+};
--- /dev/null
+#include "ForkAndExec.hpp"
+
+#include <cerrno>
+#include <cstdio>
+
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <unistd.h>
+
+ForkAndExec::ForkAndExec(const char *binary_, char **argv_)
+: binary{binary_}
+, argv{argv_}
+{
+ if (pipe2(pipes, O_CLOEXEC|O_NONBLOCK)) {
+ int error = errno;
+ perror("Server::start pipe2()");
+ throw system_error(error, system_category());
+ }
+}
+
+ForkAndExec::~ForkAndExec() {
+ if (pipes[0] != -1) {
+ close(pipes[0]);
+ }
+ if (pipes[1] != -1) {
+ close(pipes[1]);
+ }
+}
+
+optional<pid_t> ForkAndExec::operator()() {
+ if (pipes[0] == -1) {
+ return {};
+ }
+ if (pipes[1] != -1) {
+ close(pipes[1]);
+ pipes[1] = -1;
+ }
+
+ switch (pid_t pid = fork()) {
+ case 0:
+ execvp(binary, argv);
+ [[fallthrough]];
+ case -1:
+ perror("fork() && exec()");
+ return {};
+
+ default:
+ pollfd fd{pipes[0], 0, 0};
+ if (1 > poll(&fd, 1, 5000)) {
+ cerr << "exec() timed out" << endl;
+ }
+ return pid;
+ }
+}
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+class ForkAndExec {
+public:
+ ForkAndExec(const char *binary, char **argv);
+ ~ForkAndExec();
+
+ ForkAndExec(const ForkAndExec &) = delete;
+ ForkAndExec &operator = (const ForkAndExec &) = delete;
+ ForkAndExec(ForkAndExec &&) = default;
+ ForkAndExec &operator = (ForkAndExec &&) = default;
+
+ optional<pid_t> operator () ();
+
+private:
+ int pipes[2];
+ const char *binary;
+ char **argv;
+};
--- /dev/null
+#include "MemcachedCluster.hpp"
+#include "Retry.hpp"
+
+void MemcachedCluster::init() {
+ REQUIRE(cluster.start());
+
+ REQUIRE(memcached_create(&memc));
+ for (const auto &server : cluster.getServers()) {
+ auto target = server.getSocketOrPort();
+ if (holds_alternative<string>(target)) {
+ REQUIRE(MEMCACHED_SUCCESS == memcached_server_add_unix_socket(&memc, get<string>(target).c_str()));
+ } else {
+ REQUIRE(MEMCACHED_SUCCESS == memcached_server_add(&memc, "localhost", get<int>(target)));
+ }
+ }
+
+ Retry cluster_is_listening([this]() {
+ return cluster.isListening();
+ });
+ REQUIRE(cluster_is_listening());
+}
+
+MemcachedCluster::~MemcachedCluster() {
+ memcached_free(&memc);
+}
+
+void MemcachedCluster::flush() {
+ REQUIRE(MEMCACHED_SUCCESS == memcached_flush(&memc, 0));
+}
+
+MemcachedCluster::MemcachedCluster()
+: cluster{Server{getenv_else("MEMCACHED_BINARY", "memcached"), {random_socket_or_port_arg()}}}
+{
+ init();
+}
+
+MemcachedCluster::MemcachedCluster(Cluster &&cluster_)
+: cluster{forward<Cluster>(cluster_)}
+{
+ init();
+}
+
+MemcachedCluster MemcachedCluster::mixed() {
+ return MemcachedCluster{};
+}
+
+MemcachedCluster MemcachedCluster::net() {
+ return MemcachedCluster{Cluster{Server{getenv_else("MEMCACHED_BINARY", "memcached"), {"-p", random_socket_or_port_string}}}};
+}
+
+MemcachedCluster MemcachedCluster::socket() {
+ return MemcachedCluster{Cluster{Server{getenv_else("MEMCACHED_BINARY", "memcached"), {"-s", random_socket_or_port_string}}}};
+}
+
+void MemcachedCluster::enableBinary(bool enable) {
+ REQUIRE(MEMCACHED_SUCCESS == memcached_behavior_set(&memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, enable));
+}
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "Cluster.hpp"
+
+class MemcachedCluster {
+public:
+ Cluster cluster;
+ memcached_st memc;
+
+ MemcachedCluster();
+ explicit
+ MemcachedCluster(Cluster &&cluster);
+ ~MemcachedCluster();
+
+ void enableBinary(bool enable = true);
+ void flush();
+
+ static MemcachedCluster mixed();
+ static MemcachedCluster net();
+ static MemcachedCluster socket();
+
+private:
+ void init();
+};
+++ /dev/null
-#include "Poll.hpp"
-
-#include <cmath>
-#include <cstdio>
-#include <algorithm>
-#include <iostream>
-
-#include <poll.h>
-
-using namespace std;
-
-bool Poll::operator() (const vector<int> &fds) {
- vector<pollfd> pfds;
-
- pfds.reserve(fds.size());
- for (auto fd : fds) {
- pfds.emplace_back(pollfd{fd, events, 0});
- }
-
- while (!pfds.empty() && timeout <= max) {
- auto nfds = poll(pfds.data(), pfds.size(), timeout);
-
- if (nfds == -1) {
- perror("Poll::() poll()");
- return false;
- }
-
- /* timeout */
- if (!nfds) {
- timeout = ceil(static_cast<float>(timeout) * growth);
- continue;
- }
-
- auto pred = [](const pollfd &pfd){
- return pfd.revents & POLLHUP
- || pfd.revents & POLLERR
- || pfd.revents & pfd.events;
- };
- auto iter = remove_if(pfds.begin(), pfds.end(), pred);
- pfds.erase(iter, pfds.end());
- }
-
- return pfds.empty();
-}
+++ /dev/null
-#pragma once
-
-#include <vector>
-
-using namespace std;
-
-class Poll {
-private:
- short events;
- int timeout, max;
- float growth;
-
-public:
- explicit Poll(short events_ = 0, int timeout_ = 20, int max_ = 1000, float growth_ = 1.1)
- : events{events_}
- , timeout{timeout_}
- , max{max_}
- , growth{growth_}
- {}
-
- bool operator () (const vector<int> &fds);
-};
--- /dev/null
+#include "Retry.hpp"
+
+Retry::Retry(predicate &&pred_, unsigned int max_, chrono::milliseconds sleep_for_)
+: max{max_}
+, sleep_for{sleep_for_}
+, pred{forward<predicate>(pred_)}
+{}
+
+bool Retry::operator()() {
+ auto cnt = max;
+ auto dur = sleep_for;
+
+ while (cnt--) {
+ if (pred()) {
+ return true;
+ }
+ this_thread::sleep_for(dur);
+ dur *= 2;
+ }
+
+ return false;
+}
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+class Retry {
+public:
+
+ using predicate = function<bool()>;
+
+ explicit Retry(predicate &&pred_, unsigned max_ = 10, chrono::milliseconds sleep_for_ = 20ms);
+
+ bool operator () ();
+
+private:
+ unsigned max;
+ chrono::milliseconds sleep_for;
+ predicate pred;
+};
+
#include "Server.hpp"
-#include "WaitForExec.hpp"
-#include "WaitForConn.hpp"
+#include "Connection.hpp"
+#include "ForkAndExec.hpp"
-#include <netinet/in.h>
-#include <sys/poll.h>
-#include <sys/socket.h>
-#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
-#include <iostream>
-#include <tuple>
-
-using namespace std;
-
-[[nodiscard]]
-auto Server::createArgv() {
- auto i = 0, port = -1, socket = -1;
- auto arr = new char *[str_args.size() + dyn_args.size()*2 + 2] {
- strdup(binary.c_str())
- };
-
- for (auto &arg : str_args) {
- arr[++i] = strdup(arg.c_str());
- if (arg == "-p") {
- port = i + 1;
- } else if (arg == "-s") {
- socket = i + 1;
- }
- }
- for (auto &arg : dyn_args) {
- arr[++i] = strdup(arg.first.c_str());
- arr[++i] = strdup(arg.second(arg.first).c_str());
- if (arg.first == "-p") {
- port = i;
- } else if (arg.first == "-s") {
- socket = i;
- }
+Server::Server(string &&binary_, Server::argv_t &&args_)
+ : binary{forward<string>(binary_)}
+ , args{forward<argv_t>(args_)}
+{}
+Server::~Server() {
+ stop();
+ wait();
+ if (holds_alternative<string>(socket_or_port)) {
+ unlink(get<string>(socket_or_port).c_str());
}
- arr[i+1] = nullptr;
+}
- if (socket > -1) {
- socket_or_port = arr[socket];
- } else if (port > -1) {
- socket_or_port = stoi(arr[port]);
+static inline string extractArg(const Server::arg_t &arg_cont, const string &func_arg) {
+ if (holds_alternative<string>(arg_cont)) {
+ return get<string>(arg_cont);
} else {
- socket_or_port = 11211;
+ return get<Server::arg_func_t>(arg_cont)(func_arg);
}
-
- return arr;
}
-[[nodiscard]]
-optional<WaitForConn::conn_t> Server::createSocket() {
- sockaddr_storage addr;
- unsigned size = 0;
- int sock;
-
- if (holds_alternative<string>(socket_or_port)) {
- const auto path = get<string>(socket_or_port);
- const auto safe = path.c_str();
- const auto zlen = path.length() + 1;
- const auto ulen = sizeof(sockaddr_un) - sizeof(sa_family_t);
-
- if (zlen >= ulen) {
- cerr << "Server::isListening socket(): path too long '" << path << "'\n";
- return {};
- }
-
- if (0 > (sock = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) {
- perror("Server::isListening socket()");
- return {};
- }
+static inline void pushArg(vector<char *> &arr, const string &arg) {
+ auto len = arg.size();
+ auto str = arg.data(), end = str + len + 1;
+ auto ptr = new char[len + 1];
+ copy(str, end, ptr);
+ arr.push_back(ptr);
+}
- auto sa = reinterpret_cast<sockaddr_un *>(&addr);
- sa->sun_family = AF_UNIX;
- strncpy(sa->sun_path, safe, zlen);
+optional<string> Server::handleArg(vector<char *> &arr, const string &arg, const arg_func_t &next_arg) {
+ pushArg(arr, arg);
+ if (arg == "-p" || arg == "--port") {
+ auto port = next_arg(arg);
+ pushArg(arr, port);
+ pushArg(arr, "-U");
+ pushArg(arr, port);
+ socket_or_port = stoi(port);
+ return port;
+ } else if (arg == "-s" || arg == "--unix-socket") {
+ auto sock = next_arg(arg);
+ pushArg(arr, sock);
+ socket_or_port = sock;
+ return sock;
+ }
+ return {};
+}
- size = sizeof(*sa);
- } else {
- if (0 > (sock = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0))) {
- perror("Server::isListening socket()");
- return {};
+[[nodiscard]]
+vector<char *> Server::createArgv() {
+ vector<char *> arr;
+
+ pushArg(arr, binary);
+ pushArg(arr, "-v");
+
+ for (auto it = args.cbegin(); it != args.cend(); ++it) {
+ if (holds_alternative<arg_t>(*it)) {
+ // a single argument
+ auto arg = extractArg(get<arg_t>(*it), binary);
+ handleArg(arr, arg, [&it](const string &arg_) {
+ return extractArg(get<arg_t>(*++it), arg_);
+ });
+ } else {
+ // an argument pair
+ auto &[one, two] = get<arg_pair_t>(*it);
+ auto arg_one = extractArg(one, binary);
+ auto arg_two = extractArg(two, arg_one);
+
+ auto next = handleArg(arr, arg_one, [&arg_two](const string &) {
+ return arg_two;
+ });
+
+ if (!next.has_value()) {
+ pushArg(arr, arg_two);
+ }
}
-
- const auto port = get<int>(socket_or_port);
- auto sa = reinterpret_cast<struct sockaddr_in *>(&addr);
- sa->sin_family = AF_INET;
- sa->sin_port = htons(static_cast<unsigned short>(port)),
- sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-
- size = sizeof(*sa);
}
- return optional<WaitForConn::conn_t>{make_tuple(sock, addr, size)};
+ arr.push_back(nullptr);
+
+ return arr;
}
optional<pid_t> Server::start() {
return pid;
}
- WaitForExec wait_for_exec;
+ auto argv = createArgv();
+ auto child = ForkAndExec{binary.c_str(), argv.data()}();
- switch (pid = fork()) {
- case 0:
- execvp(binary.c_str(), createArgv());
- [[fallthrough]];
- case -1:
- perror("Server::start fork() & exec()");
- return {};
+ for (auto argp : argv) {
+ delete [] argp;
+ }
- default:
- if (!wait_for_exec()) {
- cerr << "Server::start exec(): incomplete\n";
- }
- return pid;
+ if (child.has_value()) {
+ pid = child.value();
}
+
+ return child;
}
-bool Server::isListening(int max_timeout) {
- auto conn = createSocket();
+bool Server::isListening() {
+ Connection conn(socket_or_port);
- if (!conn) {
+ if (!conn.open()) {
return false;
}
-
- WaitForConn wait_for_conn{
- {conn.value()},
- Poll{POLLOUT, 2, max_timeout}
- };
- return wait_for_conn();
+ return conn.isOpen();
}
bool Server::stop() {
bool Server::tryWait() {
return wait(WNOHANG);
}
+
+Server::Server(const Server &s) {
+ binary = s.binary;
+ args = s.args;
+ socket_or_port = s.socket_or_port;
+}
+
+Server &Server::operator=(const Server &s) {
+ binary = s.binary;
+ args = s.args;
+ socket_or_port = s.socket_or_port;
+ return *this;
+}
+
+pid_t Server::getPid() const {
+ return pid;
+}
+
+const string &Server::getBinary() const {
+ return binary;
+}
+
+const Server::argv_t &Server::getArgs() const {
+ return args;
+}
+
+const socket_or_port_t &Server::getSocketOrPort() const {
+ return socket_or_port;
+}
+
#pragma once
-#include "WaitForConn.hpp"
+#include "common.hpp"
#include <csignal>
-#include <functional>
-#include <iostream>
-#include <string>
-#include <variant>
-#include <vector>
-
-using namespace std;
-
class Server {
- friend class Cluster;
-
public:
- using str_args_t = vector<string>;
- using dyn_args_t = unordered_map<string, function<string(string)>>;
- using socket_or_port_t = variant<string, int>;
-private:
- string binary;
- str_args_t str_args;
- dyn_args_t dyn_args;
- pid_t pid = 0;
+ friend class Cluster;
- int status = 0;
- unordered_map<int, unsigned> signalled;
- socket_or_port_t socket_or_port{11211};
+ using arg_func_t = function<string(string)>;
+ using arg_t = variant<string, arg_func_t>;
+ using arg_pair_t = pair<arg_t, arg_t>;
+ using argv_t = vector<variant<arg_t, arg_pair_t>>;
-public:
explicit
- Server(string &&binary_, str_args_t &&str_args_ = {}, dyn_args_t &&dyn_args_ = {})
- : binary{binary_}
- , str_args{str_args_}
- , dyn_args{dyn_args_}
- {}
-
- Server(string &&binary_, dyn_args_t &&dyn_args_)
- : binary{binary_}
- , str_args{}
- , dyn_args{dyn_args_}
- {}
-
- ~Server() {
- stop();
- wait();
- }
-
- Server &operator = (const Server &s) = default;
- Server(const Server &s) = default;
+ Server(string &&binary_, argv_t && args_ = {});
+
+ ~Server();
+
+ Server(const Server &s);
+ Server &operator = (const Server &s);
Server &operator = (Server &&s) = default;
Server(Server &&s) = default;
- pid_t getPid() const {
- return pid;
- }
+ pid_t getPid() const;
- const string &getBinary() const {
- return binary;
- }
+ const string &getBinary() const;
- const socket_or_port_t &getSocketOrPort() const {
- return socket_or_port;
- }
+ const argv_t &getArgs() const;
+
+ const socket_or_port_t &getSocketOrPort() const;
optional<pid_t> start();
bool stop();
bool signal(int signo = SIGTERM);
bool check();
- bool isListening(int max_timeout = 1000);
+ bool isListening();
bool wait(int flags = 0);
bool tryWait();
private:
- [[nodiscard]]
- auto createArgv();
+ string binary;
+ argv_t args;
+ pid_t pid = 0;
+ int status = 0;
+ unordered_map<int, unsigned> signalled;
+ socket_or_port_t socket_or_port = 11211;
[[nodiscard]]
- optional<WaitForConn::conn_t> createSocket();
+ vector<char *> createArgv();
+ optional<string> handleArg(vector<char *> &arr, const string &arg, const arg_func_t &next_arg);
};
+++ /dev/null
-#include "WaitForConn.hpp"
-
-#include <cerrno>
-#include <cstdio>
-#include <sys/socket.h>
-#include <unistd.h>
-
-bool WaitForConn::connect(const conn_t &conn) {
- int sock;
- unsigned size;
- sockaddr_storage addr;
-
- tie(sock, addr, size) = conn;
-
- connect_again:
- if (0 == ::connect(sock, reinterpret_cast<sockaddr *>(&addr), size)) {
- close(sock);
- return true;
- }
-
- switch (errno) {
- case EINTR:
- goto connect_again;
- case EAGAIN:
- case EALREADY:
- case EINPROGRESS:
- case EISCONN:
- case ETIMEDOUT:
- break;
- default:
- perror("WaitForConn::connect connect()");
- close(sock);
- return false;
- }
-
- return true;
-}
-
-bool WaitForConn::operator () () {
- vector<int> fds;
-
- fds.reserve(conns.size());
- for (const auto &conn : conns) {
- if (!connect(conn)) {
- return false;
- }
- fds.push_back(get<0>(conn));
- }
-
- return poll(fds);
-}
+++ /dev/null
-#pragma once
-
-#include "Poll.hpp"
-
-#include <netinet/in.h>
-#include <sys/poll.h>
-
-#include <tuple>
-#include <vector>
-
-using namespace std;
-
-class WaitForConn {
-public:
- using conn_t = tuple<int, sockaddr_storage, unsigned int>;
-
-private:
- vector<conn_t> conns;
- Poll poll;
-
-public:
- explicit
- WaitForConn(vector<conn_t> &&conns_, Poll &&poll_ = Poll{POLLOUT})
- : conns{conns_}
- , poll{poll_}
- { }
-
- static bool connect(const conn_t &conn);
-
- bool operator () ();
-};
+++ /dev/null
-#include "WaitForExec.hpp"
-
-#include <cerrno>
-#include <cstdio>
-
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <system_error>
-
-using namespace std;
-
-WaitForExec::WaitForExec(Poll &&poll_)
-: poll{poll_}
-{
- if (pipe2(pipes, O_CLOEXEC|O_NONBLOCK)) {
- int error = errno;
- perror("Server::start pipe2()");
- throw system_error(error, system_category());
- }
-}
-
-WaitForExec::~WaitForExec() {
- if (pipes[0] != -1) {
- close(pipes[0]);
- }
- if (pipes[1] != -1) {
- close(pipes[1]);
- }
-}
-
-bool WaitForExec::operator()() {
- if (pipes[0] == -1) {
- return false;
- }
- if (pipes[1] != -1) {
- close(pipes[1]);
- pipes[1] = -1;
- }
-
- return poll(initializer_list<int>{pipes[0]});
-}
+++ /dev/null
-#pragma once
-
-#include "Poll.hpp"
-
-class WaitForExec {
-private:
- Poll poll;
- int pipes[2];
-
-public:
- explicit WaitForExec(Poll &&poll = Poll{0});
- ~WaitForExec();
-
- WaitForExec(const WaitForExec &) = delete;
- WaitForExec(WaitForExec &&) = default;
-
- bool operator () ();
-};
--- /dev/null
+#include "common.hpp"
+#include "Connection.hpp"
+
+#include <cstdlib>
+#include <sys/random.h>
+#include <unistd.h>
+
+unsigned random_num(unsigned min, unsigned max) {
+ unsigned p;
+ getrandom(&p, sizeof(p), 0);
+ return (p % (max - min + 1)) + min;
+}
+
+unsigned random_port() {
+ retry:
+ int port = random_num(2<<9, 2<<15);
+ Connection conn(port);
+
+ if (!conn.open()) {
+ return port;
+ }
+ if (!conn.isOpen()) {
+ return port;
+ }
+ goto retry;
+}
+
+string random_socket() {
+ return "/tmp/libmc." + to_string(random_num(1, UINT32_MAX)) + "@" + to_string(getpid()) + ".sock";
+}
+
+string random_socket_or_port_string(const string &what) {
+ if (what == "-s") {
+ return random_socket();
+ }
+
+ return to_string(random_port());
+}
+
+string random_socket_or_port_flag(const string &binary) {
+ (void) binary;
+ return random_num(0, 1) ? "-p" : "-s";
+}
+
+const char *getenv_else(const char *var, const char *defval) {
+ auto val = getenv(var);
+ if (val && *val) {
+ return val;
+ }
+ return defval;
+}
+
--- /dev/null
+#pragma once
+
+#include <chrono>
+#include <iostream>
+#include <map>
+#include <optional>
+#include <string>
+#include <sstream>
+#include <stdexcept>
+#include <thread>
+#include <variant>
+#include <vector>
+
+#include "../lib/catch.hpp"
+
+#include "libmemcached/memcached.h"
+
+#define LITERAL(s) (s),strlen(s)
+#define LOOPED_SECTION(tests) \
+ auto i_=0; \
+ for (auto &&test : tests) DYNAMIC_SECTION("test" << i_++)
+
+using namespace std;
+
+using socket_or_port_t = variant<string, int>;
+
+const char *getenv_else(const char *var, const char *defval);
+unsigned random_num(unsigned min, unsigned max);
+unsigned random_port();
+string random_socket();
+string random_socket_or_port_string(const string &what);
+string random_socket_or_port_flag(const string &binary);
+
+inline auto random_socket_or_port_arg() {
+ return make_pair(&random_socket_or_port_flag, &random_socket_or_port_string);
+}
+
+class MemcachedPtr {
+public:
+ memcached_st memc;
+
+ explicit
+ MemcachedPtr(memcached_st *memc_) {
+ memset(&memc, 0, sizeof(memc));
+ REQUIRE(memcached_clone(&memc, memc_));
+ }
+ MemcachedPtr()
+ : MemcachedPtr(nullptr)
+ {}
+ ~MemcachedPtr() {
+ memcached_free(&memc);
+ }
+ memcached_st *operator * () {
+ return &memc;
+ }
+};
+
+class Malloced {
+ void *ptr;
+public:
+ Malloced(void *ptr_)
+ : ptr{ptr_}
+ {}
+ ~Malloced() {
+ free(ptr);
+ }
+ void *operator *() {
+ return ptr;
+ }
+};
+++ /dev/null
-#include "random_.hpp"
-
-#include <cstdio>
-#include <iostream>
-#include <sys/random.h>
-
-unsigned random_num(unsigned min, unsigned max) {
- unsigned p;
- getrandom(&p, sizeof(p), 0);
- return (p % (max - min + 1)) + min;
-}
-
-unsigned random_port() {
- return random_num(34567, 65000);
-}
-
-string random_socket() {
- auto sock = tempnam(nullptr, "libmc");
-
- if (!sock) {
- perror("random_socket tempnam()");
- return {};
- }
-
- return sock;
-}
-
-string random_socket_or_port_string(const string &what) {
- if (what == "-s") {
- return random_socket();
- }
-
- return to_string(random_port());
-}
-
-string random_socket_or_port_flag() {
- return random_num(0, 1) ? "-p" : "-s";
-}
+++ /dev/null
-#pragma once
-
-#include <string>
-#include <tuple>
-#include "Server.hpp"
-
-using namespace std;
-
-unsigned random_num(unsigned min, unsigned max);
-unsigned random_port();
-string random_socket();
-string random_socket_or_port_string(const string &what);
-string random_socket_or_port_flag();
-
-inline Server::dyn_args_t::value_type random_socket_or_port_arg() {
- return {random_socket_or_port_flag(), &random_socket_or_port_string};
-}
--- /dev/null
+#include "../lib/common.hpp"
+
+#include "libmemcached/is.h"
+
+TEST_CASE("memcached basic") {
+ memcached_st memc, *memc_ptr;
+
+ memc_ptr = memcached_create(&memc);
+
+ REQUIRE(memc_ptr);
+ REQUIRE(memc_ptr == &memc);
+
+ SECTION("can be cloned") {
+ memc_ptr = memcached_clone(nullptr, &memc);
+ REQUIRE(memc_ptr);
+ REQUIRE(memcached_is_allocated(memc_ptr));
+ memcached_free(memc_ptr);
+ }
+
+ SECTION("can be reset") {
+ memc_ptr = memcached_clone(nullptr, &memc);
+ REQUIRE(MEMCACHED_SUCCESS == memcached_reset(&memc));
+ REQUIRE_FALSE(memcached_is_allocated(&memc));
+ REQUIRE(MEMCACHED_SUCCESS == memcached_reset(memc_ptr));
+ REQUIRE(memcached_is_allocated(memc_ptr));
+ memcached_free(memc_ptr);
+ }
+}
--- /dev/null
+#include "../lib/common.hpp"
+
+static memcached_return_t delete_trigger(memcached_st *, const char *, size_t) {
+ return MEMCACHED_SUCCESS;
+}
+
+TEST_CASE("memcached callbacks") {
+ void *fptr = reinterpret_cast<void *>(reinterpret_cast<intptr_t>(&delete_trigger));
+ MemcachedPtr memc;
+
+ SECTION("set delete trigger") {
+ REQUIRE(MEMCACHED_SUCCESS == memcached_callback_set(*memc, MEMCACHED_CALLBACK_DELETE_TRIGGER, fptr));
+ }
+
+ SECTION("set delete trigger fails w/ NOREPLY") {
+ REQUIRE(MEMCACHED_SUCCESS == memcached_behavior_set(*memc, MEMCACHED_BEHAVIOR_NOREPLY, true));
+ REQUIRE_FALSE(MEMCACHED_SUCCESS == memcached_callback_set(*memc, MEMCACHED_CALLBACK_DELETE_TRIGGER, fptr));
+ }
+}
--- /dev/null
+#include "../lib/common.hpp"
+#include "../lib/MemcachedCluster.hpp"
+
+memcached_return_t dump_cb(const memcached_st *, const char *, size_t, void *ctx) {
+ size_t *c = reinterpret_cast<size_t *>(ctx);
+ ++(*c);
+ return MEMCACHED_SUCCESS;
+}
+
+TEST_CASE("memcached dump") {
+ MemcachedCluster tests[]{
+ MemcachedCluster::mixed(),
+ MemcachedCluster::net(),
+ MemcachedCluster::socket()
+ };
+
+ LOOPED_SECTION(tests) {
+ auto memc = &test.memc;
+
+ SECTION("prepared with 64 KVs") {
+ for (int i = 0; i < 64; ++i) {
+ char key[8];
+ int len = snprintf(key, sizeof(key) - 1, "k_%d", i);
+
+ CHECKED_IF(len) {
+ auto rc = memcached_set(memc, key, len, key, len, 0, 0);
+ INFO("last error: " << memcached_last_error(memc));
+ REQUIRE(MEMCACHED_SUCCESS == rc);
+ }
+ }
+
+ memcached_quit(memc);
+
+ // let memcached sort itself
+ using namespace chrono_literals;
+ this_thread::sleep_for(3s);
+
+ SECTION("dumps 64 KVs") {
+ size_t counter = 0;
+ memcached_dump_fn fn[] = {dump_cb};
+
+ REQUIRE(MEMCACHED_SUCCESS == memcached_dump(memc, fn, &counter, 1));
+ REQUIRE(counter == 64);
+ }
+ }
+ }
+}
--- /dev/null
+#include "../lib/common.hpp"
+#include "../lib/MemcachedCluster.hpp"
+
+#define TEST_KEY LITERAL("test")
+#define INITIAL_VAL LITERAL("initial")
+#define REPLACED_VAL LITERAL("replaced")
+
+static inline void check(memcached_st *enc, memcached_st *raw, const char *val, size_t len) {
+ memcached_return_t enc_rc, raw_rc;
+ size_t enc_length, raw_length;
+ Malloced enc_value(memcached_get(enc, TEST_KEY, &enc_length, nullptr, &enc_rc));
+ Malloced raw_value(memcached_get(raw, TEST_KEY, &raw_length, nullptr, &raw_rc));
+
+ REQUIRE(enc_rc == MEMCACHED_SUCCESS);
+ REQUIRE(raw_rc == MEMCACHED_SUCCESS);
+ REQUIRE_FALSE(enc_length == raw_length);
+ REQUIRE(memcmp(*raw_value, *enc_value, raw_length));
+ REQUIRE(enc_length == len);
+ REQUIRE_FALSE(memcmp(val, *enc_value, enc_length));
+}
+
+TEST_CASE("memcached encoding_key") {
+ MemcachedCluster tests[]{
+ MemcachedCluster::mixed(),
+ MemcachedCluster::net(),
+ MemcachedCluster::socket()
+ };
+
+ LOOPED_SECTION(tests) {
+ auto memc = &test.memc;
+
+ SECTION("accepts encoding key") {
+ MemcachedPtr copy(memc);
+
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_set_encoding_key(memc, LITERAL(__func__)));
+
+ SECTION("sets encoded value") {
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
+
+ SECTION("gets encoded value") {
+ check(memc, ©.memc, INITIAL_VAL);
+ }
+
+ SECTION("cloned gets encoded value") {
+ MemcachedPtr dupe(memc);
+
+ check(&dupe.memc, ©.memc, INITIAL_VAL);
+ }
+ }
+
+ SECTION("adds encoded value") {
+
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
+ REQUIRE(MEMCACHED_NOTSTORED ==
+ memcached_add(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+
+ check(memc, ©.memc, INITIAL_VAL);
+
+ test.flush();
+
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_add(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+
+ SECTION("gets encoded value") {
+ check(memc, ©.memc, REPLACED_VAL);
+ }
+ }
+
+ SECTION("replaces encoded value") {
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_set(memc, TEST_KEY, INITIAL_VAL, 0, 0));
+
+ check(memc, ©.memc, INITIAL_VAL);
+
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_replace(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+
+ SECTION("gets encoded value") {
+ check(memc, ©.memc, REPLACED_VAL);
+ }
+ }
+
+ SECTION("unsupported") {
+ REQUIRE(MEMCACHED_NOT_SUPPORTED ==
+ memcached_increment(memc, TEST_KEY, 0, nullptr));
+ REQUIRE(MEMCACHED_NOT_SUPPORTED ==
+ memcached_decrement(memc, TEST_KEY, 0, nullptr));
+ REQUIRE(MEMCACHED_NOT_SUPPORTED ==
+ memcached_increment_with_initial(memc, TEST_KEY, 0, 0,
+ 0, nullptr));
+ REQUIRE(MEMCACHED_NOT_SUPPORTED ==
+ memcached_decrement_with_initial(memc, TEST_KEY, 0, 0,
+ 0, nullptr));
+ REQUIRE(MEMCACHED_NOT_SUPPORTED ==
+ memcached_append(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+ REQUIRE(MEMCACHED_NOT_SUPPORTED ==
+ memcached_prepend(memc, TEST_KEY, REPLACED_VAL, 0, 0));
+ }
+ }
+ }
+}
--- /dev/null
+#include "../lib/common.hpp"
+#include "../lib/MemcachedCluster.hpp"
+
+TEST_CASE("memcached exist") {
+ MemcachedCluster tests[]{
+ MemcachedCluster::mixed(),
+ MemcachedCluster::net(),
+ MemcachedCluster::socket()
+ };
+
+ tests[0].enableBinary();
+
+ LOOPED_SECTION(tests) {
+ auto memc = &test.memc;
+
+ SECTION("initial not found") {
+ REQUIRE(
+ MEMCACHED_NOTFOUND == memcached_exist(memc, LITERAL("frog")));
+ }
+
+ SECTION("set found") {
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_set(memc, LITERAL("frog"), LITERAL("frog"), 0,
+ 0));
+ REQUIRE(
+ MEMCACHED_SUCCESS == memcached_exist(memc, LITERAL("frog")));
+
+ SECTION("deleted not found") {
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_delete(memc, LITERAL("frog"), 0));
+ REQUIRE(MEMCACHED_NOTFOUND ==
+ memcached_exist(memc, LITERAL("frog")));
+ }
+ }
+
+ SECTION("by key") {
+ SECTION("initial not found") {
+ REQUIRE(MEMCACHED_NOTFOUND ==
+ memcached_exist_by_key(memc, LITERAL("master"),
+ LITERAL("frog")));
+ }
+
+ SECTION("set found") {
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_set_by_key(memc, LITERAL("master"),
+ LITERAL("frog"), LITERAL("frog"), 0, 0));
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_exist_by_key(memc, LITERAL("master"),
+ LITERAL("frog")));
+
+ SECTION("deleted not found") {
+ REQUIRE(MEMCACHED_SUCCESS ==
+ memcached_delete_by_key(memc, LITERAL("master"),
+ LITERAL("frog"), 0));
+ REQUIRE(MEMCACHED_NOTFOUND ==
+ memcached_exist_by_key(memc, LITERAL("master"),
+ LITERAL("frog")));
+ }
+ }
+ }
+ }
+}
--- /dev/null
+#include "../lib/common.hpp"
+
+TEST_CASE("memcached servers") {
+ SECTION("memcached_servers_parse") {
+ SECTION("does not leak memory") {
+ memcached_server_st *s = memcached_servers_parse("1.2.3.4:1234");
+ REQUIRE(s);
+ memcached_server_free(s);
+ }
+ }
+
+ SECTION("memcached_server_list") {
+ SECTION("append with weight - all zeros") {
+ memcached_server_st *sl = memcached_server_list_append_with_weight(
+ nullptr, nullptr, 0, 0, 0);
+ REQUIRE(sl);
+ memcached_server_list_free(sl);
+ }
+ SECTION("append with weight - host set only") {
+ memcached_server_st *sl = memcached_server_list_append_with_weight(
+ nullptr, "localhost", 0, 0, 0);
+ REQUIRE(sl);
+ memcached_server_list_free(sl);
+ }
+ SECTION("append with weight - error set only") {
+ memcached_return_t rc;
+ memcached_server_st *sl = memcached_server_list_append_with_weight(
+ nullptr, nullptr, 0, 0, &rc);
+ REQUIRE(sl);
+ REQUIRE(MEMCACHED_SUCCESS == rc);
+ memcached_server_list_free(sl);
+ }
+ }
+
+ SECTION("no configured servers") {
+ MemcachedPtr memc;
+
+ REQUIRE(MEMCACHED_NO_SERVERS == memcached_increment(*memc, LITERAL("key"), 1, nullptr));
+ }
+}
+++ /dev/null
-#include "lib/catch.hpp"
-#include "lib/Cluster.hpp"
-
-#include "lib/random_.hpp"
-
-TEST_CASE("Server") {
- Server server{"memcached"};
-
- SECTION("starts and listens") {
-
- REQUIRE(server.start().has_value());
- REQUIRE(server.isListening());
-
- SECTION("stops") {
-
- REQUIRE(server.stop());
-
- SECTION("is waitable") {
-
- REQUIRE(server.wait());
-
- SECTION("stopped") {
- REQUIRE(server.is)
- }
- }
- }
- }
-}
-
-TEST_CASE("Cluster") {
- Cluster cluster{Server{"memcached", {
- random_socket_or_port_arg(),
- }}};
-
- SECTION("starts and listens") {
-
- REQUIRE(cluster.start());
- REQUIRE(cluster.isListening());
-
- SECTION("stops") {
-
- cluster.stop();
- cluster.wait();
-
- SECTION("stopped") {
-
- REQUIRE(cluster.isStopped());
- }
- }
- }
-}