--- a/memcache.h Thu Aug 28 00:29:39 2008 +0300
+++ b/memcache.h Thu Aug 28 01:34:14 2008 +0300
@@ -21,7 +21,24 @@
* Keys used
*/
struct memcache_key {
+ /*
+ * Pointer to a char buffer containing the key itself.
+ *
+ * A key is a text tring which should uniquely identify a cache entry.
+ *
+ * The length limit for a key is usually 250 characters, but this is imposed by the server side (a longer key will
+ * presumeably cause a CLIENT_ERROR reply).
+ *
+ * The key should presumeably also not contain any spaces, carriage returns or newlines, as these are used to
+ * delimit tokens in the protocol itself.
+ *
+ * The buffer does not need to be zero-terminated.
+ */
char *buf;
+
+ /*
+ * The length of the key buffer in bytes. This does not include a NUL byte.
+ */
size_t len;
};
@@ -29,9 +46,27 @@
* Object attributes
*/
struct memcache_obj {
+ /*
+ * An arbitrary 16-bit (32-bit for >1.2.1) that the server stores transparently.
+ */
unsigned int flags;
+
+ /*
+ * Expiration time. If non-zero, either an offset in seconds from current time (up to 60*60*24*30 seconds, or 30d),
+ * or an absolute 32-bit unix timestamp.
+ */
time_t exptime;
+
+ /*
+ * Number of bytes in the entry. This may be zero, in which case there will be no data.
+ */
size_t bytes;
+
+ /*
+ * Used for the CMD_STORE_CAS command. An unique 64-bit value that changes if the entry is modified.
+ *
+ * Not needed for other commands, and may or may not be provided by CMD_FETCH_GET (will be set to zero).
+ */
unsigned long long cas;
};
@@ -39,8 +74,21 @@
* Object data
*/
struct memcache_buf {
+ /*
+ * The char buffer containing the data.
+ */
char *data;
+
+ /*
+ * The total length of the char buffer, and thence the cache entry.
+ */
size_t len;
+
+ /*
+ * The amount of data currently available in the buffer. This is used to provide streaming fetches.
+ *
+ * This field is *IMPORTANT*! Don't disregard it, or you *will* get incomplete data.
+ */
size_t offset;
};
@@ -49,33 +97,124 @@
*/
enum memcache_command {
MEMCACHE_CMD_INVALID,
+
+ /*
+ * Retrieve the value of a key from the memcached server.
+ *
+ * If the key exists, you will get a RPL_VALUE reply followed by a RPL_END reply.
+ * If it does not exist, you will just get a RPL_END reply.
+ */
+ MEMCACHE_CMD_FETCH_GET,
- MEMCACHE_CMD_FETCH_GET,
+ /*
+ * Store this data.
+ *
+ * Returns either RPL_STORED or RPL_NOT_STORED.
+ */
MEMCACHE_CMD_STORE_SET,
+
+ /*
+ * Store this data, but only if the server doesn't already hold data for this key.
+ *
+ * Returns either RPL_STORED or RPL_NOT_STORED.
+ */
MEMCACHE_CMD_STORE_ADD,
+
+ /*
+ * Store this data, but only if the server does already hold data for this key.
+ *
+ * Returns either RPL_STORED or RPL_NOT_STORED.
+ */
MEMCACHE_CMD_STORE_REPLACE,
+
+ /*
+ * Add this data to an existing key after existing data.
+ *
+ * obj.flags and obj.exptime are ignored.
+ *
+ * Returns ???
+ */
MEMCACHE_CMD_STORE_APPEND,
+
+ /*
+ * Add this data to an existing key before existing data.
+ *
+ * obj.flags and obj.exptime are ignored.
+ *
+ * Returns ???
+ */
MEMCACHE_CMD_STORE_PREPEND,
+
+ /*
+ * Check and Set - store this data but only if no one else had updated it since I last fetched it.
+ *
+ * obj.cas is required.
+ *
+ * Returns RPL_STORED, RPL_NOT_STORED, RPL_EXISTS (data has been modified), or RPL_NOT_FOUND (the item does not
+ * exist or has been deleted).
+ */
MEMCACHE_CMD_STORE_CAS,
MEMCACHE_CMD_MAX,
};
+/*
+ * Replies from the server
+ */
enum memcache_reply {
MEMCACHE_RPL_INVALID,
+
+ /*
+ * The library sent an unknown command. This probably means the server is not compatible with said command.
+ */
+ MEMCACHE_RPL_ERROR,
- MEMCACHE_RPL_ERROR,
+ /*
+ * There was an error in the request that the library sent. The error message is printed out via ERROR.
+ */
MEMCACHE_RPL_CLIENT_ERROR,
+
+ /*
+ * There was an error with the server when processing the request. The error message is printed out via ERROR.
+ */
MEMCACHE_RPL_SERVER_ERROR,
- // MEMCACHE_CMD_FETCH_*
+ // CMD_FETCH_*
+
+ /*
+ * The given key was found in the cache. The obj attributes are now known, and the buf data is on the way.
+ */
MEMCACHE_RPL_VALUE,
+
+ /*
+ * No more RPL_VALUE replies will be sent. This may be the only reply sent if the key was not found.
+ */
MEMCACHE_RPL_END,
- // MEMCACHE_CMD_STORE_*
+ // CMD_STORE_*
+
+ /*
+ * The object was succesfully stored in the cache.
+ */
MEMCACHE_RPL_STORED,
+
+ /*
+ * The object was not stored in the cache.
+ *
+ * This could be for a number of reasons, perhaps the object is too large, the cache is full, the key is in the
+ * delete queue, or some condition imposed by the STORE_* command was not met.
+ */
MEMCACHE_RPL_NOT_STORED,
+
+ /*
+ * The item you were trying to store with a STORE_CAS request has been modified since you last fetched it (i.e.
+ * obj.cas does not match).
+ */
MEMCACHE_RPL_EXISTS,
+
+ /*
+ * The item you were trying to store with a STORE_CAS request did not exist (or has been deleted).
+ */
MEMCACHE_RPL_NOT_FOUND,
MEMCACHE_RPL_MAX,
@@ -83,15 +222,50 @@
enum memcache_req_state {
MEMCACHE_STATE_INVALID,
+
+ /*
+ * The request is queued, and has not been sent yet
+ */
+ MEMCACHE_STATE_QUEUED,
- MEMCACHE_STATE_QUEUED,
+ /*
+ * The request is being sent, and the reply has not yet been received
+ */
MEMCACHE_STATE_SEND,
+
+ /*
+ * The reply has been received.
+ *
+ * req_reply and req_obj will return a non-NULL value, but there is no reply data yet.
+ */
MEMCACHE_STATE_REPLY,
- MEMCACHE_STATE_REPLY_DATA,
+ /*
+ * The reply and part of the reply data has been received.
+ *
+ * req_reply, req_obj and req_buf will all return non-NULL values, but buf.offset will be smaller than buf.len.
+ */
+ MEMCACHE_STATE_REPLY_DATA,
+
+ /*
+ * The full reply has been received.
+ *
+ * req_reply, req_obj will return a non-NULL value, but there is no reply data.
+ */
MEMCACHE_STATE_DONE,
+
+ /*
+ * The full reply and reply data has been received.
+ *
+ * req_reply, req_obj and req_buf will all return non-NULL values, and buf.offset will be equal to buf.len.
+ */
MEMCACHE_STATE_DATA_DONE,
-
+
+ /*
+ * An error has occurred.
+ *
+ * req_reply, req_obj and req_buf may or may not work.
+ */
MEMCACHE_STATE_ERROR,
};
@@ -112,27 +286,94 @@
/*
* Attempt to fetch a key from the cache.
+ *
+ * The state machine will work as follows:
+ *
+ * req_state multi req_reply
+ * ---------------------------------------------
+ * STATE_QUEUE ?
+ * STATE_SEND
+ * STATE_REPLY RPL_VALUE
+ * STATE_REPLY_DATA * RPL_VALUE
+ * STATE_DATA_DONE RPL_END
+ *
+ * STATE_DONE RPL_END
+ *
+ * STATE_ERROR RPL_{ERROR,CLIENT_ERROR,SERVER_ERROR}
+ *
+ * The item attributes/data can be accessed via req_obj/req_buf as described in `enum memcache_state`.
*/
struct memcache_req *memcache_fetch (struct memcache *mc, const struct memcache_key *key, void *cb_arg);
/*
- * Attempt to store a key into the cache
+ * Attempt to store an item into the cache.
+ *
+ * The cmd argument can be used to specify what CMD_STORE_* command to use.
+ *
+ * The given memcache_key is copied, including the char array pointed to by buf.
+ * Both obj and buf are also copied, but buf.data will not be copied - the pointer must remain valid until the request is done.
+ *
+ * The state machine will work as follows:
+ *
+ * req_state multi req_reply
+ * ---------------------------------------------
+ * STATE_QUEUE ?
+ * STATE_SEND
+ * STATE_REPLY RPL_{STORED,NOT_STORED,EXISTS,NOT_FOUND}
+ * STATE_DONE RPL_{STORED,NOT_STORED,EXISTS,NOT_FOUND}
+ *
+ * STATE_ERROR RPL_{ERROR,CLIENT_ERROR,SERVER_ERROR}
+ *
*/
-struct memcache_req *memcache_store (struct memcache *mc, enum memcache_command cmd, const struct memcache_key *key, const struct memcache_obj *obj, void *cb_arg);
+struct memcache_req *memcache_store (struct memcache *mc, enum memcache_command cmd, const struct memcache_key *key, const struct memcache_obj *obj, const struct memcache_buf *buf, void *cb_arg);
/*
- * Request state
+ * Request state.
+ *
+ * Should always return a valid value.
*/
enum memcache_req_state memcache_req_state (struct memcache_req *req);
/*
- * Request key
+ * Request command.
+ *
+ * Should always return a valid value.
*/
-int memcache_req_key (struct memcache_req *req, const struct memcache_key *key);
+enum memcache_command memcache_req_cmd (struct memcache_req *req);
/*
- * Request data
+ * Request reply.
+ *
+ * Will return a valid value in the STATE_REPLY, STATE_REPLY_DATA, STATE_DONE and STATE_DATA_DONE states.
*/
-int memcache_req_obj (struct memcache_req *req, const struct memcache_obj *obj);
+enum memcache_reply memcache_req_reply (struct memcache_req *req);
+
+/*
+ * Request key.
+ *
+ * Will return a valid valuein all states
+ */
+const struct memcache_key *keymemcache_req_key (struct memcache_req *req);
+
+/*
+ * Request data.
+ *
+ * Will return a valid value in the STATE_REPLY, STATE_REPLY_DATA, STATE_DONE and STATE_DATA_DONE states.
+ */
+const struct memcache_obj *memcache_req_obj (struct memcache_req *req);
+
+/*
+ * Request buf.
+ *
+ * Will return a valid value in the STATE_REPLY_DATA and STATE_DATA_DONE states.
+ *
+ * Note that buf.offset may be less than buf.len in the STATE_REPLY_DATA state.
+ */
+const struct memcache_buf *memcache_req_buf (struct memcache_req *req);
+
+/*
+ * Free a req that is in the STATE_DONE, STATE_DATA_DONE or STATE_ERROR state.
+ */
+void memcache_req_free (struct memcache_req *req);
#endif /* MEMCACHE_H */
--- a/memcache/connection.c Thu Aug 28 00:29:39 2008 +0300
+++ b/memcache/connection.c Thu Aug 28 01:34:14 2008 +0300
@@ -298,7 +298,7 @@
ERROR("got reply with wrong key !?!");
// notify the request (no reply data is ready for reading yet, though)
- memcache_req_reply(conn->req, reply_type);
+ memcache_req_recv(conn->req, reply_type);
// does the reply include data?
if (has_data) {
--- a/memcache/memcache.c Thu Aug 28 00:29:39 2008 +0300
+++ b/memcache/memcache.c Thu Aug 28 01:34:14 2008 +0300
@@ -50,12 +50,12 @@
return mc->server_list.lh_first;
}
-struct memcache_req *memcache_fetch (struct memcache *mc, const struct memcache_key *key, void *cb_arg) {
+static struct memcache_req *_memcache_req (struct memcache *mc, enum memcache_command cmd, const struct memcache_key *key, const struct memcache_obj *obj, const struct memcache_buf *buf, void *cb_arg) {
struct memcache_req *req = NULL;
struct memcache_server *server = NULL;
// alloc the request
- if ((req = memcache_req_alloc(mc, MEMCACHE_CMD_FETCH_GET, key, cb_arg)) == NULL)
+ if ((req = memcache_req_alloc(mc, MEMCACHE_CMD_FETCH_GET, key, obj, buf, cb_arg)) == NULL)
ERROR("failed to allocate request");
// pick a server
@@ -76,3 +76,24 @@
return NULL;
}
+struct memcache_req *memcache_fetch (struct memcache *mc, const struct memcache_key *key, void *cb_arg) {
+ return _memcache_req(mc, MEMCACHE_CMD_FETCH_GET, key, NULL, NULL, cb_arg);
+}
+
+struct memcache_req *memcache_store (struct memcache *mc, enum memcache_command cmd, const struct memcache_key *key, const struct memcache_obj *obj, const struct memcache_buf *buf, void *cb_arg) {
+ if (
+ (cmd != MEMCACHE_CMD_STORE_SET)
+ && (cmd != MEMCACHE_CMD_STORE_ADD)
+ && (cmd != MEMCACHE_CMD_STORE_REPLACE)
+ && (cmd != MEMCACHE_CMD_STORE_APPEND)
+ && (cmd != MEMCACHE_CMD_STORE_PREPEND)
+ && (cmd != MEMCACHE_CMD_STORE_CAS)
+ )
+ ERROR("invalid command for store");
+
+ return _memcache_req(mc, cmd, key, obj, buf, cb_arg);
+
+error:
+ return NULL;
+}
+
--- a/memcache/request.c Thu Aug 28 00:29:39 2008 +0300
+++ b/memcache/request.c Thu Aug 28 01:34:14 2008 +0300
@@ -6,8 +6,11 @@
#include "memcache.h"
#include "../common.h"
-struct memcache_req *memcache_req_alloc (struct memcache *mc, enum memcache_command cmd_type, const struct memcache_key *key, void *cb_arg) {
+struct memcache_req *memcache_req_alloc (struct memcache *mc, enum memcache_command cmd_type, const struct memcache_key *key, const struct memcache_obj *obj, const struct memcache_buf *buf, void *cb_arg) {
struct memcache_req *req = NULL;
+
+ // ensure key is provided
+ assert(key != NULL);
// allocate it
if ((req = calloc(1, sizeof(*req))) == NULL)
@@ -20,9 +23,20 @@
if ((req->key.buf = malloc(key->len)) == NULL)
ERROR("malloc key buf");
- // copy over the key
memcpy(req->key.buf, key->buf, key->len);
req->key.len = key->len;
+
+ // copy the obj if provided
+ if (obj) {
+ memcpy(&req->obj, obj, sizeof(req->obj));
+ req->have_obj = 1;
+ }
+
+ // copy the buf if provided
+ if (buf) {
+ memcpy(&req->buf, buf, sizeof(req->buf));
+ req->have_buf = 1;
+ }
// store the other data
req->mc = mc;
@@ -41,6 +55,32 @@
return NULL;
}
+// accessors
+enum memcache_req_state memcache_req_state (struct memcache_req *req) {
+ return req->state;
+}
+
+enum memcache_command memcache_req_cmd (struct memcache_req *req) {
+ return req->cmd_type;
+}
+
+enum memcache_reply memcache_req_reply (struct memcache_req *req) {
+ return req->reply_type;
+}
+
+const struct memcache_key *keymemcache_req_key (struct memcache_req *req) {
+ return &req->key;
+}
+
+const struct memcache_obj *memcache_req_obj (struct memcache_req *req) {
+ return req->have_obj ? &req->obj : NULL;
+}
+
+const struct memcache_buf *memcache_req_buf (struct memcache_req *req) {
+ return req->have_buf ? &req->buf : NULL;
+}
+
+// events
static void _memcache_req_notify (struct memcache_req *req) {
req->mc->cb_fn(req, req->cb_arg);
}
@@ -57,10 +97,13 @@
// _memcache_req_notify(req);
}
-void memcache_req_reply (struct memcache_req *req, enum memcache_reply reply_type) {
+void memcache_req_recv (struct memcache_req *req, enum memcache_reply reply_type) {
req->state = MEMCACHE_STATE_REPLY;
req->reply_type = reply_type;
+ // we must surely have a valid obj now
+ req->have_obj = 1;
+
_memcache_req_notify(req);
}
@@ -68,6 +111,9 @@
assert(req->state == MEMCACHE_STATE_REPLY || req->state == MEMCACHE_STATE_REPLY_DATA);
req->state = MEMCACHE_STATE_REPLY_DATA;
+
+ // we must surely have a valid buf now
+ req->have_buf = 1;
_memcache_req_notify(req);
}
@@ -75,15 +121,27 @@
void memcache_req_done (struct memcache_req *req) {
// make sure we are in the REPLY/REPLY_DATA state
assert(req->state == MEMCACHE_STATE_REPLY || req->state == MEMCACHE_STATE_REPLY_DATA);
+
+ // are we supposed to have data?
+ if (req->buf.data) {
+ // make sure we really have the full data, if applicable
+ assert(req->buf.offset == req->buf.len);
+
+ // yes...
+ req->have_buf = 1;
- // make sure we really have the full data, if applicable
- assert(req->buf.data == NULL || req->buf.offset == req->buf.len);
+ // have data
+ req->state = MEMCACHE_STATE_DATA_DONE;
+
+ } else {
+ // no data
+ req->state = MEMCACHE_STATE_DONE;
+ }
// forget the connection
req->conn = NULL;
- // state depends on if we have data or not...
- req->state = req->buf.data ? MEMCACHE_STATE_DATA_DONE : MEMCACHE_STATE_DONE;
+ _memcache_req_notify(req);
}
void memcache_req_error (struct memcache_req *req) {
--- a/memcache/request.h Thu Aug 28 00:29:39 2008 +0300
+++ b/memcache/request.h Thu Aug 28 01:34:14 2008 +0300
@@ -21,6 +21,9 @@
struct memcache_obj obj;
struct memcache_buf buf;
+ // flags to indicate if we have valid obj/buf data
+ char have_obj, have_buf;
+
// our state
enum memcache_req_state state;
@@ -37,7 +40,7 @@
/*
* Allocate and return a new req, or NULL if unsuccesfull.
*/
-struct memcache_req *memcache_req_alloc (struct memcache *mc, enum memcache_command cmd_type, const struct memcache_key *key, void *cb_arg);
+struct memcache_req *memcache_req_alloc (struct memcache *mc, enum memcache_command cmd_type, const struct memcache_key *key, const struct memcache_obj *obj, const struct memcache_buf *buf, void *cb_arg);
/*
* The request has been queued.
@@ -57,7 +60,7 @@
*
* Note that no req data will not be available when this is first called, only once req_data/req_done is called.
*/
-void memcache_req_reply (struct memcache_req *req, enum memcache_reply reply_type);
+void memcache_req_recv (struct memcache_req *req, enum memcache_reply reply_type);
/*
* Some amount of reply data has been received. This may be called between the req_reply/MEMCACHE_RPL_VALUE and
--- a/memcache_test.c Thu Aug 28 00:29:39 2008 +0300
+++ b/memcache_test.c Thu Aug 28 01:34:14 2008 +0300
@@ -18,15 +18,19 @@
if ((mc = memcache_alloc(&_memcache_cb)) == NULL)
ERROR("memcache_alloc");
+ // fix up the endpoint
endpoint_init(&server_endpoint, 11211);
if (endpoint_parse(&server_endpoint, "localhost"))
ERROR("config_endpoint_parse");
-
+
+ // add the server
if (memcache_add_server(mc, &server_endpoint, 1))
ERROR("memcache_add_server");
+
+ // add a request or two
- // XXX: we should have a connect() running now
+
error:
return;