Permalink
Browse files

Updating the API to use a Redis-like line-wire protocol. Added a PHP …

…extension client library.
  • Loading branch information...
1 parent 1690dac commit 38723746213d50a0256005cf9f7c6dc2bfd0be6f Nick committed Feb 23, 2010
View
162 README
@@ -4,158 +4,107 @@ Barbershop is a fast, lightweight priority queue system. The goal is to
create a dead simple network service with libevent to manage priority
queue.
-This system doesn't use pqueue, but it probably could be made to do so.
-
# Usage
This application exists to allow priority queue workers to scale out. The
-idea is that for a given dataset (a list of ints), some things need to be
+idea is that for a given dataset (a list of numbers), some things need to be
processed periodical and the order in which they processed may change over
time. The workers shouldn't have to concern themselves with the why or how
of a priority change, just which item to process next.
- * Over a period of time, clients send update calls to barbershop.
+Over a period of time, clients send update calls to barbershop:
- update 61231 4
- update 12353 1
- update 12342 1
+ UPDATE 61231 4
+ UPDATE 12353 1
+ UPDATE 12342 1
- * Periodically, workers want the next item to be processed.
+Workers want the next item to be processed:
- next
+ NEXT
- * Monitors want to know the state of the system through stat calls.
+Monitors want to know the state of the system through info calls:
- stats
+ INFO
# Protocol
-Clients of barbershop communicate with server through TCP connections.
-A given running barbershop server listens on some port; clients connect
-to that port, send commands to the server, read responses and eventually
-close the connection.
-
-There is no need to send any command to end the session. A client may
-just close the connection at any moment it no longer needs it. Note,
-however, that clients are encouraged to cache their connections rather
-than reopen them every time they need to store or retrieve data. Caching
-connections will eliminate the overhead associated with establishing a
-TCP connection (the overhead of preparing for a new connection on the
-server side is insignificant compared to this).
-
-Commands lines are always terminated by \r\n. Unstructured data is also
-terminated by \r\n, even though \r, \n or any other 8-bit characters
-may also appear inside the data. Therefore, when a client retrieves
-data from a server, it must use the length of the data block (which it
-will be provided with) to determine where the data block ends, and not
-the fact that \r\n follows the end of the data block, even though it
-does.
-
-## Items and Priority Values
-
-Items and their priority scores are unsigned 32bit integers.
-
-## Commands
-
-There are three types of commands. The update command is used to create
-or update an item's priority. If the item given is being introduced then
-it's base priority is set, else the priority is incremented by the given
-amount.
-
-The next command is used to return the next ordered by priority.
-
-Last, the stats command is used to get barbershop daemon stats and usage
-information.
+This protocol is based loosely on the Redis protocol specification.
-# Error responses
+ http://code.google.com/p/redis/wiki/ProtocolSpecification
-Each command sent by a client may be answered with an error string
-from the server. These error strings come in three types:
+A client connects to a Barbershop server creating a TCP connection to the
+port 8002. Every barbershop command or data transmitted by the client and
+the server is terminated by "\r\n" (CRLF).
-"ERROR\r\n"
+The simplest commands are the inline commands. This is an example of a
+server/client chat (the server chat starts with S:, the client chat with C:).
-The client sent a nonexistent command name.
+ C: UPDATE 61231 5\r\n
+ S: +OK\r\n
+ C: NEXT\r\n
+ S: +61231\r\n
-"CLIENT_ERROR <error>\r\n"
+An inline command is a CRLF-terminated string sent to the client. The server
+can reply to commands in different ways:
-Some sort of client error in the input line, i.e. the input doesn't
-conform to the protocol in some way. <error> is a human-readable error
-string.
+ With an error message (the first byte of the reply will be "-")
+ With a single line reply (the first byte of the reply will be "+)
-"SERVER\_ERROR <error>\r\n"
+This service does not support bulk commands or responses.
-Some sort of server error prevents the server from carrying out the
-command. <error> is a human-readable error string. In cases of severe
-server errors, which make it impossible to continue serving the client
-(this shouldn't normally happen), the server will close the connection
-after sending the error line. This is the only case in which the server
-closes a connection to a client.
-
-In the descriptions of individual commands below, these error lines
-are not again specifically mentioned, but clients must allow for their
-possibility.
-
-## Storage commands
-
-First, the client sends a command line which looks like this:
-
- <command name> <item id> <priority>\r\n
-
-* <command name> is "update".
-* <item id> is the unique 32bit integer representing the item.
-* <priority> is a 32bit integer that the priority will either be set to or increment by.
-
-After sending the command line the client awaits the reply which may be:
-
-* "OK\r\n", to indicate success.
-* One of the possible client or server error messages listed above.
+## Commands
+There are only a handful of commands supported at this point.
-## Retrieval command
+'UPDATE <item id> <value>'
-The retrieval commands "next" and "peak" operates like this:
+Update the priority of a given item by X.
- next\r\n
+ C: UPDATE 61231 5\r\n
+ S: +OK\r\n
- peak\r\n
+'NEXT'
-After sending the command line the client awaits the reply which may be:
+Return the next item in the queue.
-* "-1\r\n", indicates that there are no items to act on.
-* "<32u>\r\n", the item id for the client to process.
-* One of the possible client or server error messages listed above.
+ C: NEXT\r\n
+ S: +61231\r\n
-## Statistics
+When there are no more items to return a '-1' is returned.
-The command "stats" is used to query the server about statistics it
-maintains and other internal data.
+ C: NEXT\r\n
+ S: +-1\r\n
- stats\r\n
+'PEAK'
-Upon receiving the "stats" command the server sends a number of lines
-which look like this:
+Return the next item in the queue without removing it from the queue.
- STAT <name> <value>\r\n
+ C: NEXT\r\n
+ S: +61231\r\n
-The server terminates this list with the line:
+When there are no more items to return a '-1' is returned.
- END\r\n
+ C: NEXT\r\n
+ S: +-1\r\n
-In each line of statistics, <name> is the name of this statistic, and
-<value> is the data. The following is the list of all names sent in
-response to the "stats" command, together with the type of the value
-sent for this name, and the meaning of the value.
+'INFO'
-In the type column below, "32u" means a 32-bit unsigned integer, "64u"
-means a 64-bit unsigned integer. '32u:32u' means two 32-but unsigned
-integers separated by a colon.
+Return some server stats. This command deviates from the standard response
+format by returning a list of key-value pairs separated by a ':'.
* 'uptime' (32u) Number of seconds this server has been running.
* 'version' (string) Version string of this server.
* 'updates' (32u) Number of update commands received by this server.
* 'items' (32u) Number of items.
* 'pools' (32u) Number of pools.
+ C: INFO\r\n
+ S: uptime:60000\r\n
+ S: version:0.1.3\r\n
+ S: updates:9742851\r\n
+ S: items:2132931\r\n
+ S: pools:47831\r\n
+
# TODO
* Add daemonize functionality.
@@ -164,3 +113,4 @@ integers separated by a colon.
* port to bind to
* to snapshot or not
* snapshot interval
+ * Add support for the 'GET' command which returns the priority of an item.
View
0 clients/php/.deps
No changes.
View
32 clients/php/README
@@ -0,0 +1,32 @@
+
+Barbershop-PHP
+
+This extension provides an API for communicating with Barbershop.
+
+# Building
+
+ phpize
+ ./configure
+ make
+ sudo make install
+
+Once built and installed, be sure to add 'barbershop.so' to the php.ini
+extensions configuration.
+
+ extension=barbershop.so
+
+# Use
+
+This extension exposes one class, 'Barbershop'.
+
+ $bs = new Barbershop();
+ $bs->connect('127.0.0.1', 8002);
+ assert($bs->update('5000', 1) == "+OK");
+ assert($bs->peak() == "+5000");
+ assert($bs->next() == "+5000");
+ assert($bs->next() == "+-1");
+
+# TODO
+
+ * Create real class documentation.
+ * Change the class method responses.
View
671 clients/php/barbershop.c
@@ -0,0 +1,671 @@
+/*
+Copyright (c) 2010 Nick Gerakines <nick at gerakines dot net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_barbershop.h"
+#include <zend_exceptions.h>
+
+static int le_barbershop_sock;
+static zend_class_entry *barbershop_ce;
+static zend_class_entry *barbershop_exception_ce;
+static zend_class_entry *spl_ce_RuntimeException = NULL;
+
+ZEND_DECLARE_MODULE_GLOBALS(barbershop)
+
+static zend_function_entry barbershop_functions[] = {
+ PHP_ME(Barbershop, __construct, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Barbershop, connect, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Barbershop, close, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Barbershop, next, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Barbershop, peak, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Barbershop, update, NULL, ZEND_ACC_PUBLIC)
+ PHP_ME(Barbershop, info, NULL, ZEND_ACC_PUBLIC)
+ PHP_MALIAS(Barbershop, open, connect, NULL, ZEND_ACC_PUBLIC)
+ {NULL, NULL, NULL}
+};
+
+zend_module_entry barbershop_module_entry = {
+#if ZEND_MODULE_API_NO >= 20010901
+ STANDARD_MODULE_HEADER,
+#endif
+ "barbershop",
+ NULL,
+ PHP_MINIT(barbershop),
+ PHP_MSHUTDOWN(barbershop),
+ PHP_RINIT(barbershop),
+ PHP_RSHUTDOWN(barbershop),
+ PHP_MINFO(barbershop),
+#if ZEND_MODULE_API_NO >= 20010901
+ PHP_BARBERSHOP_VERSION,
+#endif
+ STANDARD_MODULE_PROPERTIES
+};
+
+#ifdef COMPILE_DL_BARBERSHOP
+ZEND_GET_MODULE(barbershop)
+#endif
+
+void add_constant_long(zend_class_entry *ce, char *name, int value) {
+ zval *constval;
+ constval = pemalloc(sizeof(zval), 1);
+ INIT_PZVAL(constval);
+ ZVAL_LONG(constval, value);
+ zend_hash_add(&ce->constants_table, name, 1 + strlen(name), (void*)&constval, sizeof(zval*), NULL);
+}
+
+/**
+ * This command behave somehow like printf, except that strings need 2 arguments:
+ * Their data and their size (strlen).
+ * Supported formats are: %d, %i, %s
+ */
+static int barbershop_cmd_format(char **ret, char *format, ...) {
+ char *p, *s;
+ va_list ap;
+
+ int total = 0, sz, ret_sz;
+ int i, ci;
+ unsigned int u;
+ double dbl;
+ char *double_str;
+ int double_len;
+
+ int stage;
+ for (stage = 0; stage < 2; ++stage) {
+ va_start(ap, format);
+ total = 0;
+ for (p = format; *p; ) {
+ if (*p == '%') {
+ switch (*(p+1)) {
+ case 's':
+ s = va_arg(ap, char*);
+ sz = va_arg(ap, int);
+ if (stage == 1) {
+ memcpy((*ret) + total, s, sz);
+ }
+ total += sz;
+ break;
+ case 'f':
+ /* use spprintf here */
+ dbl = va_arg(ap, double);
+ double_len = spprintf(&double_str, 0, "%f", dbl);
+ if (stage == 1) {
+ memcpy((*ret) + total, double_str, double_len);
+ }
+ total += double_len;
+ efree(double_str);
+ break;
+ case 'i':
+ case 'd':
+ i = va_arg(ap, int);
+ /* compute display size of integer value */
+ sz = 0;
+ ci = abs(i);
+ while (ci>0) {
+ ci = (ci/10);
+ sz += 1;
+ }
+ if (i == 0) { /* log 0 doesn't make sense. */
+ sz = 1;
+ } else if(i < 0) { /* allow for neg sign as well. */
+ sz++;
+ }
+ if (stage == 1) {
+ sprintf((*ret) + total, "%d", i);
+ }
+ total += sz;
+ break;
+ }
+ p++;
+ } else {
+ if (stage == 1) {
+ (*ret)[total] = *p;
+ }
+ total++;
+ }
+ p++;
+ }
+ if (stage == 0) {
+ ret_sz = total;
+ (*ret) = emalloc(ret_sz+1);
+ } else {
+ (*ret)[ret_sz] = 0;
+ return ret_sz;
+ }
+ }
+}
+
+PHPAPI BarbershopSock* barbershop_sock_create(char *host, int host_len, unsigned short port, long timeout) {
+ BarbershopSock *barbershop_sock;
+ barbershop_sock = emalloc(sizeof *barbershop_sock);
+ barbershop_sock->host = emalloc(host_len + 1);
+ barbershop_sock->stream = NULL;
+ barbershop_sock->status = BARBERSHOP_SOCK_STATUS_DISCONNECTED;
+
+ memcpy(barbershop_sock->host, host, host_len);
+ barbershop_sock->host[host_len] = '\0';
+ barbershop_sock->port = port;
+ barbershop_sock->timeout = timeout;
+
+ return barbershop_sock;
+}
+
+PHPAPI int barbershop_sock_connect(BarbershopSock *barbershop_sock TSRMLS_DC) {
+ struct timeval tv, *tv_ptr = NULL;
+ char *host = NULL, *hash_key = NULL, *errstr = NULL;
+ int host_len, err = 0;
+
+ if (barbershop_sock->stream != NULL) {
+ barbershop_sock_disconnect(barbershop_sock TSRMLS_CC);
+ }
+
+ tv.tv_sec = barbershop_sock->timeout;
+ tv.tv_usec = 0;
+
+ host_len = spprintf(&host, 0, "%s:%d", barbershop_sock->host, barbershop_sock->port);
+
+ if (tv.tv_sec != 0) {
+ tv_ptr = &tv;
+ }
+ barbershop_sock->stream = php_stream_xport_create(host, host_len, ENFORCE_SAFE_MODE, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, hash_key, tv_ptr, NULL, &errstr, &err);
+
+ efree(host);
+
+ if (!barbershop_sock->stream) {
+ efree(errstr);
+ return -1;
+ }
+
+ php_stream_auto_cleanup(barbershop_sock->stream);
+
+ if (tv.tv_sec != 0) {
+ php_stream_set_option(barbershop_sock->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
+ }
+ php_stream_set_option(barbershop_sock->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
+
+ barbershop_sock->status = BARBERSHOP_SOCK_STATUS_CONNECTED;
+ return 0;
+}
+
+PHPAPI int barbershop_sock_server_open(BarbershopSock *barbershop_sock, int force_connect TSRMLS_DC) {
+ int res = -1;
+ switch (barbershop_sock->status) {
+ case BARBERSHOP_SOCK_STATUS_DISCONNECTED:
+ return barbershop_sock_connect(barbershop_sock TSRMLS_CC);
+ case BARBERSHOP_SOCK_STATUS_CONNECTED:
+ res = 0;
+ break;
+ case BARBERSHOP_SOCK_STATUS_UNKNOWN:
+ if (force_connect > 0 && barbershop_sock_connect(barbershop_sock TSRMLS_CC) < 0) {
+ res = -1;
+ } else {
+ res = 0;
+ barbershop_sock->status = BARBERSHOP_SOCK_STATUS_CONNECTED;
+ }
+ break;
+ }
+ return res;
+}
+
+PHPAPI int barbershop_sock_disconnect(BarbershopSock *barbershop_sock TSRMLS_DC) {
+ int res = 0;
+ if (barbershop_sock->stream != NULL) {
+ barbershop_sock_write(barbershop_sock, "QUIT", sizeof("QUIT") - 1);
+ barbershop_sock->status = BARBERSHOP_SOCK_STATUS_DISCONNECTED;
+ php_stream_close(barbershop_sock->stream);
+ barbershop_sock->stream = NULL;
+ res = 1;
+ }
+ return res;
+}
+
+PHPAPI char *barbershop_sock_read(BarbershopSock *barbershop_sock, int *buf_len TSRMLS_DC) {
+ char inbuf[1024];
+ char *resp = NULL;
+
+ barbershop_check_eof(barbershop_sock TSRMLS_CC);
+ php_stream_gets(barbershop_sock->stream, inbuf, 1024);
+
+ switch (inbuf[0]) {
+ case '-':
+ return NULL;
+ case '+':
+ case ':':
+ *buf_len = strlen(inbuf) - 2;
+ if (*buf_len >= 2) {
+ resp = emalloc(1+*buf_len);
+ memcpy(resp, inbuf, *buf_len);
+ resp[*buf_len] = 0;
+ return resp;
+ } else {
+ printf("protocol error \n");
+ return NULL;
+ }
+ default:
+ printf("protocol error, got '%c' as reply type byte\n", inbuf[0]);
+ }
+ return NULL;
+}
+
+PHPAPI int barbershop_sock_write(BarbershopSock *barbershop_sock, char *cmd, size_t sz) {
+ barbershop_check_eof(barbershop_sock TSRMLS_CC);
+ return php_stream_write(barbershop_sock->stream, cmd, sz);
+ return 0;
+}
+
+PHPAPI void barbershop_check_eof(BarbershopSock *barbershop_sock TSRMLS_DC) {
+ int eof = php_stream_eof(barbershop_sock->stream);
+ while (eof) {
+ barbershop_sock->stream = NULL;
+ barbershop_sock_connect(barbershop_sock TSRMLS_CC);
+ eof = php_stream_eof(barbershop_sock->stream);
+ }
+}
+
+PHPAPI int barbershop_sock_get(zval *id, BarbershopSock **barbershop_sock TSRMLS_DC) {
+ zval **socket;
+ int resource_type;
+
+ if (Z_TYPE_P(id) != IS_OBJECT || zend_hash_find(Z_OBJPROP_P(id), "socket", sizeof("socket"), (void **) &socket) == FAILURE) {
+ return -1;
+ }
+
+ *barbershop_sock = (BarbershopSock *) zend_list_find(Z_LVAL_PP(socket), &resource_type);
+ if (!*barbershop_sock || resource_type != le_barbershop_sock) {
+ return -1;
+ }
+
+ return Z_LVAL_PP(socket);
+}
+
+/**
+ * barbershop_free_socket
+ */
+PHPAPI void barbershop_free_socket(BarbershopSock *barbershop_sock) {
+ efree(barbershop_sock->host);
+ efree(barbershop_sock);
+}
+
+PHPAPI zend_class_entry *barbershop_get_exception_base(int root TSRMLS_DC) {
+#if HAVE_SPL
+ if (!root) {
+ if (!spl_ce_RuntimeException) {
+ zend_class_entry **pce;
+ if (zend_hash_find(CG(class_table), "runtimeexception", sizeof("RuntimeException"), (void **) &pce) == SUCCESS) {
+ spl_ce_RuntimeException = *pce;
+ return *pce;
+ }
+ } else {
+ return spl_ce_RuntimeException;
+ }
+ }
+#endif
+#if (PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION < 2)
+ return zend_exception_get_default();
+#else
+ return zend_exception_get_default(TSRMLS_C);
+#endif
+}
+
+static void barbershop_destructor_barbershop_sock(zend_rsrc_list_entry * rsrc TSRMLS_DC) {
+ BarbershopSock *barbershop_sock = (BarbershopSock *) rsrc->ptr;
+ barbershop_sock_disconnect(barbershop_sock TSRMLS_CC);
+ barbershop_free_socket(barbershop_sock);
+}
+
+PHP_MINIT_FUNCTION(barbershop) {
+ zend_class_entry barbershop_class_entry;
+ INIT_CLASS_ENTRY(barbershop_class_entry, "Barbershop", barbershop_functions);
+ barbershop_ce = zend_register_internal_class(&barbershop_class_entry TSRMLS_CC);
+
+ zend_class_entry barbershop_exception_class_entry;
+ INIT_CLASS_ENTRY(barbershop_exception_class_entry, "BarbershopException", NULL);
+ barbershop_exception_ce = zend_register_internal_class_ex(
+ &barbershop_exception_class_entry,
+ barbershop_get_exception_base(0 TSRMLS_CC),
+ NULL TSRMLS_CC
+ );
+
+ le_barbershop_sock = zend_register_list_destructors_ex(
+ barbershop_destructor_barbershop_sock,
+ NULL,
+ barbershop_sock_name, module_number
+ );
+ // XXX: Scrub these
+ add_constant_long(barbershop_ce, "BARBERSHOP_NOT_FOUND", BARBERSHOP_NOT_FOUND);
+ add_constant_long(barbershop_ce, "BARBERSHOP_STRING", BARBERSHOP_STRING);
+ add_constant_long(barbershop_ce, "BARBERSHOP_SET", BARBERSHOP_SET);
+ add_constant_long(barbershop_ce, "BARBERSHOP_LIST", BARBERSHOP_LIST);
+ return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(barbershop) {
+ return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(barbershop) {
+ return SUCCESS;
+}
+
+PHP_RSHUTDOWN_FUNCTION(barbershop) {
+ return SUCCESS;
+}
+
+PHP_MINFO_FUNCTION(barbershop) {
+ php_info_print_table_start();
+ php_info_print_table_header(2, "Barberbshop Support", "enabled");
+ php_info_print_table_row(2, "Version", PHP_BARBERSHOP_VERSION);
+ php_info_print_table_end();
+}
+
+PHP_METHOD(Barbershop, __construct) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
+ RETURN_FALSE;
+ }
+}
+
+PHP_METHOD(Barbershop, connect) {
+ zval *object;
+ int host_len, id;
+ char *host = NULL;
+ long port;
+
+ struct timeval timeout = {0L, 0L};
+ BarbershopSock *barbershop_sock = NULL;
+
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl|l", &object, barbershop_ce, &host, &host_len, &port, &timeout.tv_sec) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (timeout.tv_sec < 0L || timeout.tv_sec > INT_MAX) {
+ zend_throw_exception(barbershop_exception_ce, "Invalid timeout", 0 TSRMLS_CC);
+ RETURN_FALSE;
+ }
+
+ barbershop_sock = barbershop_sock_create(host, host_len, port, timeout.tv_sec);
+
+ if (barbershop_sock_server_open(barbershop_sock, 1 TSRMLS_CC) < 0) {
+ barbershop_free_socket(barbershop_sock);
+ zend_throw_exception_ex(
+ barbershop_exception_ce,
+ 0 TSRMLS_CC,
+ "Can't connect to %s:%d",
+ host,
+ port
+ );
+ RETURN_FALSE;
+ }
+
+ id = zend_list_insert(barbershop_sock, le_barbershop_sock);
+ add_property_resource(object, "socket", id);
+
+ RETURN_TRUE;
+}
+
+PHP_METHOD(Barbershop, close) {
+ zval *object;
+ BarbershopSock *barbershop_sock = NULL;
+
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
+ &object, barbershop_ce) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (barbershop_sock_get(object, &barbershop_sock TSRMLS_CC) < 0) {
+ RETURN_FALSE;
+ }
+
+ if (barbershop_sock_disconnect(barbershop_sock TSRMLS_CC)) {
+ RETURN_TRUE;
+ }
+
+ RETURN_FALSE;
+}
+
+PHPAPI void barbershop_boolean_response(INTERNAL_FUNCTION_PARAMETERS, BarbershopSock *barbershop_sock TSRMLS_DC) {
+ char *response;
+ int response_len;
+ char ret;
+
+ if ((response = barbershop_sock_read(barbershop_sock, &response_len TSRMLS_CC)) == NULL) {
+ RETURN_FALSE;
+ }
+ ret = response[0];
+ efree(response);
+
+ if (ret == '+') {
+ RETURN_TRUE;
+ } else {
+ RETURN_FALSE;
+ }
+}
+
+PHPAPI void barbershop_long_response(INTERNAL_FUNCTION_PARAMETERS, BarbershopSock *barbershop_sock TSRMLS_DC) {
+ char *response;
+ int response_len;
+
+ if ((response = barbershop_sock_read(barbershop_sock, &response_len TSRMLS_CC)) == NULL) {
+ RETURN_FALSE;
+ }
+
+ if(response[0] == ':') {
+ long ret = atol(response + 1);
+ efree(response);
+ RETURN_LONG(ret);
+ } else {
+ efree(response);
+ RETURN_FALSE;
+ }
+}
+
+PHPAPI void barbershop_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, BarbershopSock *barbershop_sock TSRMLS_DC) {
+
+ char *response;
+ int response_len;
+
+ if ((response = barbershop_sock_read(barbershop_sock, &response_len TSRMLS_CC)) == NULL) {
+ RETURN_FALSE;
+ }
+
+ double ret = atof(response);
+ efree(response);
+ RETURN_DOUBLE(ret);
+}
+
+PHPAPI void barbershop_1_response(INTERNAL_FUNCTION_PARAMETERS, BarbershopSock *barbershop_sock TSRMLS_DC) {
+
+ char *response;
+ int response_len;
+ char ret;
+
+ if ((response = barbershop_sock_read(barbershop_sock, &response_len TSRMLS_CC)) == NULL) {
+ RETURN_FALSE;
+ }
+
+ ret = response[1];
+ efree(response);
+
+ if (ret == '1') {
+ RETURN_TRUE;
+ } else {
+ RETURN_FALSE;
+ }
+}
+
+PHP_METHOD(Barbershop, update) {
+ zval *object;
+ BarbershopSock *barbershop_sock;
+ char *key = NULL, *val = NULL, *cmd, *response;
+ int key_len, val_len, cmd_len, response_len;
+
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, barbershop_ce, &key, &key_len, &val, &val_len) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (barbershop_sock_get(object, &barbershop_sock TSRMLS_CC) < 0) {
+ RETURN_FALSE;
+ }
+
+ cmd_len = barbershop_cmd_format(&cmd, "UPDATE %s %s\r\n", key, key_len, val, val_len);
+ if (barbershop_sock_write(barbershop_sock, cmd, cmd_len) < 0) {
+ efree(cmd);
+ RETURN_FALSE;
+ }
+ efree(cmd);
+
+ if ((response = barbershop_sock_read(barbershop_sock, &response_len TSRMLS_CC)) == NULL) {
+ RETURN_FALSE;
+ }
+ RETURN_STRINGL(response, response_len, 0);
+}
+
+PHP_METHOD(Barbershop, next) {
+ zval *object;
+ BarbershopSock *barbershop_sock;
+ char *key = NULL, *cmd, *response;
+ int key_len, cmd_len, response_len;
+
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, barbershop_ce) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (barbershop_sock_get(object, &barbershop_sock TSRMLS_CC) < 0) {
+ RETURN_FALSE;
+ }
+
+ cmd_len = spprintf(&cmd, 0, "NEXT\r\n");
+ if (barbershop_sock_write(barbershop_sock, cmd, cmd_len) < 0) {
+ efree(cmd);
+ RETURN_FALSE;
+ }
+ efree(cmd);
+
+ if ((response = barbershop_sock_read(barbershop_sock, &response_len TSRMLS_CC)) == NULL) {
+ RETURN_FALSE;
+ }
+ RETURN_STRINGL(response, response_len, 0);
+}
+
+PHP_METHOD(Barbershop, peak) {
+ zval *object;
+ BarbershopSock *barbershop_sock;
+ char *key = NULL, *cmd, *response;
+ int key_len, cmd_len, response_len;
+
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, barbershop_ce) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (barbershop_sock_get(object, &barbershop_sock TSRMLS_CC) < 0) {
+ RETURN_FALSE;
+ }
+
+ cmd_len = spprintf(&cmd, 0, "PEAK\r\n");
+ if (barbershop_sock_write(barbershop_sock, cmd, cmd_len) < 0) {
+ efree(cmd);
+ RETURN_FALSE;
+ }
+ efree(cmd);
+
+ if ((response = barbershop_sock_read(barbershop_sock, &response_len TSRMLS_CC)) == NULL) {
+ RETURN_FALSE;
+ }
+ RETURN_STRINGL(response, response_len, 0);
+}
+
+PHP_METHOD(Barbershop, info) {
+
+ zval *object;
+ BarbershopSock *barbershop_sock;
+
+ char cmd[] = "INFO\r\n", *response, *key;
+ int cmd_len = sizeof(cmd)-1, response_len;
+ long ttl;
+ char *cur, *pos, *value;
+ int is_numeric;
+ char *p;
+
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, barbershop_ce) == FAILURE) {
+ RETURN_FALSE;
+ }
+
+ if (barbershop_sock_get(object, &barbershop_sock TSRMLS_CC) < 0) {
+ RETURN_FALSE;
+ }
+
+ if (barbershop_sock_write(barbershop_sock, cmd, cmd_len) < 0) {
+ RETURN_FALSE;
+ }
+
+ if ((response = barbershop_sock_read(barbershop_sock, &response_len TSRMLS_CC)) == NULL) {
+ RETURN_FALSE;
+ }
+
+ array_init(return_value);
+
+ cur = response;
+ while(1) {
+ /* key */
+ pos = strchr(cur, ':');
+ if(pos == NULL) {
+ break;
+ }
+ key = emalloc(pos - cur + 1);
+ memcpy(key, cur, pos-cur);
+ key[pos-cur] = 0;
+
+ /* value */
+ cur = pos + 1;
+ pos = strchr(cur, '\r');
+ if(pos == NULL) {
+ break;
+ }
+ value = emalloc(pos - cur + 1);
+ memcpy(value, cur, pos-cur);
+ value[pos-cur] = 0;
+ pos += 2; /* \r, \n */
+ cur = pos;
+
+ is_numeric = 1;
+ for(p = value; *p; ++p) {
+ if(*p < '0' || *p > '9') {
+ is_numeric = 0;
+ break;
+ }
+ }
+
+ if(is_numeric == 1) {
+ add_assoc_long(return_value, key, atol(value));
+ efree(value);
+ } else {
+ add_assoc_string(return_value, key, value, 0);
+ }
+ efree(key);
+ }
+}
+
+/* vim: set tabstop=4 expandtab: */
View
50 clients/php/config.m4
@@ -0,0 +1,50 @@
+dnl $Id$
+dnl config.m4 for extension barbershop
+
+PHP_ARG_ENABLE(barbershop, whether to enable barbershop support,
+dnl Make sure that the comment is aligned:
+[ --enable-barbershop Enable barbershop support])
+
+if test "$PHP_BARBERSHOP" != "no"; then
+
+ dnl # --with-barbershop -> check with-path
+ dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
+ dnl SEARCH_FOR="/include/barbershop.h" # you most likely want to change this
+ dnl if test -r $PHP_BARBERSHOP/$SEARCH_FOR; then # path given as parameter
+ dnl BARBERSHOP_DIR=$PHP_BARBERSHOP
+ dnl else # search default path list
+ dnl AC_MSG_CHECKING([for barbershop files in default path])
+ dnl for i in $SEARCH_PATH ; do
+ dnl if test -r $i/$SEARCH_FOR; then
+ dnl BARBERSHOP_DIR=$i
+ dnl AC_MSG_RESULT(found in $i)
+ dnl fi
+ dnl done
+ dnl fi
+ dnl
+ dnl if test -z "$BARBERSHOP_DIR"; then
+ dnl AC_MSG_RESULT([not found])
+ dnl AC_MSG_ERROR([Please reinstall the barbershop distribution])
+ dnl fi
+
+ dnl # --with-barbershop -> add include path
+ dnl PHP_ADD_INCLUDE($BARBERSHOP_DIR/include)
+
+ dnl # --with-barbershop -> check for lib and symbol presence
+ dnl LIBNAME=barbershop # you may want to change this
+ dnl LIBSYMBOL=barbershop # you most likely want to change this
+
+ dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
+ dnl [
+ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $BARBERSHOP_DIR/lib, BARBERSHOP_SHARED_LIBADD)
+ dnl AC_DEFINE(HAVE_BARBERSHOPLIB,1,[ ])
+ dnl ],[
+ dnl AC_MSG_ERROR([wrong barbershop lib version or lib not found])
+ dnl ],[
+ dnl -L$BARBERSHOP_DIR/lib -lm -ldl
+ dnl ])
+ dnl
+ dnl PHP_SUBST(BARBERSHOP_SHARED_LIBADD)
+
+ PHP_NEW_EXTENSION(barbershop, barbershop.c, $ext_shared)
+fi
View
96 clients/php/php_barbershop.h
@@ -0,0 +1,96 @@
+/*
+Copyright (c) 2010 Nick Gerakines <nick at gerakines dot net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifndef PHP_BARBERSHOP_H
+#define PHP_BARBERSHOP_H
+
+PHP_METHOD(Barbershop, __construct);
+PHP_METHOD(Barbershop, connect);
+PHP_METHOD(Barbershop, close);
+PHP_METHOD(Barbershop, get);
+PHP_METHOD(Barbershop, set);
+PHP_METHOD(Barbershop, info);
+PHP_METHOD(Barbershop, update);
+PHP_METHOD(Barbershop, next);
+PHP_METHOD(Barbershop, peak);
+
+#ifdef PHP_WIN32
+#define PHP_BARBERSHOP_API __declspec(dllexport)
+#else
+#define PHP_BARBERSHOP_API
+#endif
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+PHP_MINIT_FUNCTION(barbershop);
+PHP_MSHUTDOWN_FUNCTION(barbershop);
+PHP_RINIT_FUNCTION(barbershop);
+PHP_RSHUTDOWN_FUNCTION(barbershop);
+PHP_MINFO_FUNCTION(barbershop);
+
+/* {{{ struct BarbershopSock */
+typedef struct BarbershopSock_ {
+ php_stream *stream;
+ char *host;
+ unsigned short port;
+ long timeout;
+ int failed;
+ int status;
+} BarbershopSock;
+/* }}} */
+
+#define barbershop_sock_name "Barbershop Socket Buffer"
+
+#define BARBERSHOP_SOCK_STATUS_FAILED 0
+#define BARBERSHOP_SOCK_STATUS_DISCONNECTED 1
+#define BARBERSHOP_SOCK_STATUS_UNKNOWN 2
+#define BARBERSHOP_SOCK_STATUS_CONNECTED 3
+
+/* properties */
+#define BARBERSHOP_NOT_FOUND 0
+#define BARBERSHOP_STRING 1
+#define BARBERSHOP_SET 2
+#define BARBERSHOP_LIST 3
+
+
+/* {{{ internal function protos */
+void add_constant_long(zend_class_entry *ce, char *name, int value);
+
+PHPAPI void barbershop_check_eof(BarbershopSock *barbershop_sock TSRMLS_DC);
+PHPAPI BarbershopSock* barbershop_sock_create(char *host, int host_len, unsigned short port, long timeout);
+PHPAPI int barbershop_sock_connect(BarbershopSock *barbershop_sock TSRMLS_DC);
+PHPAPI int barbershop_sock_disconnect(BarbershopSock *barbershop_sock TSRMLS_DC);
+PHPAPI int barbershop_sock_server_open(BarbershopSock *barbershop_sock, int TSRMLS_DC);
+PHPAPI char * barbershop_sock_read(BarbershopSock *barbershop_sock, int *buf_len TSRMLS_DC);
+PHPAPI int barbershop_sock_write(BarbershopSock *barbershop_sock, char *cmd, size_t sz);
+PHPAPI void barbershop_free_socket(BarbershopSock *barbershop_sock);
+
+PHPAPI void barbershop_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, char *keyword TSRMLS_DC);
+
+ZEND_BEGIN_MODULE_GLOBALS(barbershop)
+ZEND_END_MODULE_GLOBALS(barbershop)
+
+#define PHP_BARBERSHOP_VERSION "0.1"
+
+#endif
View
20 clients/php/tests/basic.phpt
@@ -0,0 +1,20 @@
+--TEST--
+simple test
+--FILE--
+<?php
+$bs = new Barbershop();
+var_dump($bs);
+var_dump($bs->connect('127.0.0.1', 8002));
+var_dump($bs->update('5000', 1));
+var_dump($bs->peak());
+var_dump($bs->next());
+var_dump($bs->next());
+?>
+--EXPECT--
+object(Barbershop)#1 (0) {
+}
+bool(true)
+string(3) "+OK"
+string(5) "+5000"
+string(5) "+5000"
+string(3) "+-1"
View
33 src/barbershop.c
@@ -97,14 +97,14 @@ void on_read(int fd, short ev, void *arg) {
token_t tokens[MAX_TOKENS];
size_t ntokens = tokenize_command((char*)buf, tokens, MAX_TOKENS);
// TODO: Add support for the 'quit' command.
- if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN].value, "update") == 0) {
+ if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN].value, "UPDATE") == 0) {
int item_id = atoi(tokens[KEY_TOKEN].value);
int score = atoi(tokens[VALUE_TOKEN].value);
// Score should probably be type-checked to assert that it really is
// an unsigned 32bit integer.
if (score < 1) {
- reply(fd, "ERROR INVALID SCORE\r\n");
+ reply(fd, "-ERROR INVALID SCORE\r\n");
return;
}
@@ -128,16 +128,18 @@ void on_read(int fd, short ev, void *arg) {
pthread_mutex_unlock(&scores_mutex);
}
app_stats.updates += 1;
- reply(fd, "OK\r\n");
- } else if (ntokens == 2 && strcmp(tokens[COMMAND_TOKEN].value, "peak") == 0) {
+ reply(fd, "+OK\r\n");
+ } else if (ntokens == 2 && strcmp(tokens[COMMAND_TOKEN].value, "PEAK") == 0) {
+ printf("received a 'PEAK' command\n");
int next;
pthread_mutex_lock(&scores_mutex);
- scores = PeakNext(scores, &next);
+ PeakNext(scores, &next);
pthread_mutex_unlock(&scores_mutex);
char msg[32];
- sprintf(msg, "%d\r\n", next);
+ sprintf(msg, "+%d\r\n", next);
reply(fd, msg);
- } else if (ntokens == 2 && strcmp(tokens[COMMAND_TOKEN].value, "next") == 0) {
+ } else if (ntokens == 2 && strcmp(tokens[COMMAND_TOKEN].value, "NEXT") == 0) {
+ printf("received a 'NEXT' command\n");
int next;
pthread_mutex_lock(&scores_mutex);
scores = NextItem(scores, &next);
@@ -150,21 +152,20 @@ void on_read(int fd, short ev, void *arg) {
app_stats.items -= 1;
}
char msg[32];
- sprintf(msg, "%d\r\n", next);
+ sprintf(msg, "+%d\r\n", next);
reply(fd, msg);
- } else if (ntokens == 2 && strcmp(tokens[COMMAND_TOKEN].value, "stats") == 0) {
+ } else if (ntokens == 2 && strcmp(tokens[COMMAND_TOKEN].value, "INFO") == 0) {
char out[128];
time_t current_time;
time(&current_time);
- sprintf(out, "STAT uptime %d\r\n", (int)(current_time - app_stats.started_at)); reply(fd, out);
- sprintf(out, "STAT version %s\r\n", app_stats.version); reply(fd, out);
- sprintf(out, "STAT updates %d\r\n", app_stats.updates); reply(fd, out);
- sprintf(out, "STAT items %d\r\n", app_stats.items); reply(fd, out);
- sprintf(out, "STAT pools %d\r\n", app_stats.pools); reply(fd, out);
- reply(fd, "END\r\n");
+ sprintf(out, "uptime:%d\r\n", (int)(current_time - app_stats.started_at)); reply(fd, out);
+ sprintf(out, "version:%s\r\n", app_stats.version); reply(fd, out);
+ sprintf(out, "updates:%d\r\n", app_stats.updates); reply(fd, out);
+ sprintf(out, "items:%d\r\n", app_stats.items); reply(fd, out);
+ sprintf(out, "pools:%d\r\n", app_stats.pools); reply(fd, out);
// pool_foreach(scores, pool_print);
} else {
- reply(fd, "ERROR\r\n");
+ reply(fd, "-ERROR\r\n");
}
}
View
10 src/scores.c
@@ -259,20 +259,18 @@ PoolNode *promoteItem(PoolNode *list, int score, int item, int old_score) {
return NULL;
}
-PoolNode *PeakNext(PoolNode *head, int *next_item) {
+void PeakNext(PoolNode *head, int *next_item) {
if (head == NULL) {
*next_item = -1;
- return NULL;
+ return;
}
if (head->count == 1) {
*next_item = head->members->item;
- return head->next;
+ return;
} else {
MemberNode *last = member_last(head->members);
*next_item = last->item;
- assert(member_remove(head->members, last) == 0);
- head->count -= 1;
- return head;
+ return;
}
}
View
2 src/scores.h
@@ -52,7 +52,7 @@ int find_item(int item, int query);
PoolNode *preparePromotion(PoolNode *head, int item, int score);
PoolNode *promoteItem(PoolNode *list, int score, int item, int old_score);
-PoolNode *PeakNext(PoolNode *head, int *next_item);
+void PeakNext(PoolNode *head, int *next_item);
PoolNode *NextItem(PoolNode *list, int *next_item);
#ifdef DEBUG
View
2 tests/check_barbershop.c
@@ -75,7 +75,7 @@ START_TEST (test_scattered_adds) {
e = promoteItem(e, 7, 5002, -1);
e = promoteItem(e, 8, 5001, 5);
e = promoteItem(e, 1, 5003, -1);
- sync_to_disk(e, "barbershop.dump");
+ // sync_to_disk(e, "barbershop.dump");
int next = -1;
e = NextItem(e, &next);
fail_unless(next == 5000);

0 comments on commit 3872374

Please sign in to comment.