From f966b37d7ade78cb2e500805112be64271ffea5c Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Dec 2010 16:56:42 -0500 Subject: [PATCH 001/122] Initial commit --- .gitignore | 3 ++ Makefile | 18 +++++++ cmsg_private.h | 24 ++++++++++ harness.c | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ harness.h | 23 +++++++++ main.c | 24 ++++++++++ net.c | 97 ++++++++++++++++++++++++++++++++++++++ net.h | 9 ++++ node.h | 14 ++++++ relay.c | 45 ++++++++++++++++++ relay.h | 6 +++ util.c | 62 ++++++++++++++++++++++++ 12 files changed, 450 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 cmsg_private.h create mode 100644 harness.c create mode 100644 harness.h create mode 100644 main.c create mode 100644 net.c create mode 100644 net.h create mode 100644 node.h create mode 100644 relay.c create mode 100644 relay.h create mode 100644 util.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f0df94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +scratch/ +*.o +cmsg diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b84df12 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +TARGET = cmsg +OBJECTS = main.o harness.o net.o util.o relay.o + +CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g +#CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) $(CFLAGS) -o $@ $(OBJECTS) -levent + +%.o: %.c + $(CC) $(CFLAGS) -c $< + +clean: + rm -f $(TARGET) + rm -f $(OBJECTS) + rm -rf *.dSYM diff --git a/cmsg_private.h b/cmsg_private.h new file mode 100644 index 0000000..37fc507 --- /dev/null +++ b/cmsg_private.h @@ -0,0 +1,24 @@ +#ifndef cmsg_private_h +#define cmsg_private_h + +typedef struct cmsg_bytes_t { + size_t len; + void *bytes; +} cmsg_bytes_t; + +#define EMPTY_BYTES ((cmsg_bytes_t) { NULL, 0 }) + +extern cmsg_bytes_t cmsg_cstring_bytes(char const *cstr); +extern cmsg_bytes_t cmsg_bytes_malloc_dup(cmsg_bytes_t src); +extern cmsg_bytes_t cmsg_bytes_malloc(size_t amount); +extern void cmsg_bytes_free(cmsg_bytes_t bytes); + +#define ICHECK(result, message) do { if ((result) == -1) { perror(message); exit(2); } } while (0) +#define BCHECK(result, message) do { if (!(result)) { perror(message); exit(2); } } while (0) +#define PCHECK(result, message) do { if (!(result)) { perror(message); exit(2); } } while (0) + +extern void die(char const *format, ...); +extern void warn(char const *format, ...); +extern void info(char const *format, ...); + +#endif diff --git a/harness.c b/harness.c new file mode 100644 index 0000000..0c9b840 --- /dev/null +++ b/harness.c @@ -0,0 +1,125 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include + +#include + +#include + +typedef unsigned char u_char; +#include + +#include "cmsg_private.h" +#include "harness.h" + +#ifdef __APPLE__ +/* Bollocks. Looks like OS X chokes unless STACK_SIZE is a multiple of 32k. */ +#define STACK_SIZE 32768 +#elif linux +#define STACK_SIZE 4096 +#else +#error Define STACK_SIZE for your platform. +#endif + +Process *current_process = NULL; + +static ucontext_t scheduler; +static ProcessQueue runlist = { 0, NULL, NULL }; +static ProcessQueue deadlist = { 0, NULL, NULL }; + +static void zero_queue(ProcessQueue *pq) { + pq->count = 0; + pq->head = NULL; + pq->tail = NULL; +} + +static void enqueue(ProcessQueue *pq, Process *p) { + p->link = NULL; + if (pq->head == NULL) { + pq->head = p; + } else { + pq->tail->link = p; + } + pq->tail = p; + pq->count++; +} + +static Process *dequeue(ProcessQueue *pq) { + if (pq->head == NULL) { + return NULL; + } else { + Process *p = pq->head; + pq->head = p->link; + if (pq->head == NULL) { + pq->tail = NULL; + } + pq->count--; + return p; + } +} + +void yield(void) { + if (current_process == NULL) { + ICHECK(setcontext(&scheduler), "yield setcontext"); + } else { + enqueue(&runlist, current_process); + ICHECK(swapcontext(¤t_process->context, &scheduler), "yield swapcontext"); + } +} + +void killproc(void) { + enqueue(&deadlist, current_process); + current_process = NULL; + yield(); +} + +static void driver(void (*f)(void *), void *arg) { + f(arg); + killproc(); +} + +void spawn(void (*f)(void *), void *arg) { + Process *p = calloc(1, sizeof(*p)); + PCHECK(p, "spawn calloc"); + + p->stack_base = malloc(STACK_SIZE); /* what is a sane value here? 32k for mac... */ + PCHECK(p->stack_base, "stack pointer malloc"); + + ICHECK(getcontext(&p->context), "spawn getcontext"); + p->context.uc_link = NULL; + p->context.uc_stack.ss_sp = p->stack_base; + p->context.uc_stack.ss_size = STACK_SIZE; + p->context.uc_stack.ss_flags = 0; + makecontext(&p->context, (void (*)(void)) driver, 2, f, arg); + + enqueue(&runlist, p); +} + +static void clean_dead_processes(void) { + Process *deadp; + while ((deadp = dequeue(&deadlist)) != NULL) { + free(deadp->stack_base); + free(deadp); + } +} + +void boot_harness(void) { + ICHECK(getcontext(&scheduler), "boot_harness getcontext"); + while (1) { + while (runlist.count) { + ProcessQueue work = runlist; + zero_queue(&runlist); + info("Processing %d jobs\n", work.count); + while ((current_process = dequeue(&work)) != NULL) { + ICHECK(swapcontext(&scheduler, ¤t_process->context), "boot_harness swapcontext"); + clean_dead_processes(); + } + info("Polling for events\n"); + event_loop(EVLOOP_NONBLOCK); + } + info("Blocking for events\n"); + event_loop(EVLOOP_ONCE); + } +} diff --git a/harness.h b/harness.h new file mode 100644 index 0000000..217069a --- /dev/null +++ b/harness.h @@ -0,0 +1,23 @@ +#ifndef cmsg_harness_h +#define cmsg_harness_h + +typedef struct Process { + ucontext_t context; + struct Process *link; + void *stack_base; +} Process; + +typedef struct ProcessQueue { + int count; + Process *head; + Process *tail; +} ProcessQueue; + +extern Process *current_process; + +extern void yield(void); +extern void spawn(void (*f)(void *), void *arg); + +extern void boot_harness(void); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..ef7b606 --- /dev/null +++ b/main.c @@ -0,0 +1,24 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include + +#include +#include + +typedef unsigned char u_char; +#include + +#include "cmsg_private.h" +#include "harness.h" +#include "net.h" + +int main(int argc, char *argv[]) { + info("cmsg, Copyright (C) 2010 Tony Garnock-Jones. All rights reserved.\n"); + event_init(); + info("Using libevent version %s\n", event_get_version()); + start_net(5671); + boot_harness(); + return 0; +} diff --git a/net.c b/net.c new file mode 100644 index 0000000..90b532f --- /dev/null +++ b/net.c @@ -0,0 +1,97 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +typedef unsigned char u_char; +#include + +#include "cmsg_private.h" +#include "relay.h" + +static struct event accept_event; + +void get_addr_name(char *namebuf, size_t buflen, struct sockaddr_in const *sin) { + unsigned char *addr = (unsigned char *) &sin->sin_addr.s_addr; + struct hostent *h = gethostbyaddr(addr, 4, AF_INET); + + if (h == NULL) { + snprintf(namebuf, buflen, "%u.%u.%u.%u", addr[0], addr[1], addr[2], addr[3]); + } else { + snprintf(namebuf, buflen, "%s", h->h_name); + } +} + +char const *endpoint_name(struct sockaddr_in const *peername) { + char name[256]; + static char result[256]; + get_addr_name(name, sizeof(name), peername); + snprintf(result, sizeof(result), "%s:%d", name, ntohs(peername->sin_port)); + return result; +} + +static void accept_connection(int servfd, short what, void *arg) { + struct sockaddr_in s; + socklen_t addrlen = sizeof(s); + int fd = accept(servfd, (struct sockaddr *) &s, &addrlen); + + if (fd == -1) { + if (errno != EAGAIN && errno != EINTR) { + warn("accept: errno %d (%s)\n", errno, strerror(errno)); + } + return; + } + + start_relay(&s, fd); +} + +void start_net(int listen_port) { + int servfd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in s; + + if (servfd < 0) { + die("Could not open listen socket.\n"); + } + + s.sin_family = AF_INET; + s.sin_addr.s_addr = htonl(INADDR_ANY); + s.sin_port = htons(listen_port); + + { + int i = 1; + setsockopt(servfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); // don't care if this fails + } + + if (bind(servfd, (struct sockaddr *) &s, sizeof(s)) < 0) { + die("Could not bind listen socket.\n"); + } + + if (listen(servfd, 5) < 0) { + int savedErrno = errno; + die("Could not listen on listen socket (errno %d: %s).\n", + savedErrno, strerror(savedErrno)); + } + + event_set(&accept_event, servfd, EV_READ | EV_PERSIST, accept_connection, NULL); + if (event_add(&accept_event, NULL) == -1) { + die("Could not add accept_event."); + } + + info("Accepting connections on port %d.\n", listen_port); +} diff --git a/net.h b/net.h new file mode 100644 index 0000000..351bf2f --- /dev/null +++ b/net.h @@ -0,0 +1,9 @@ +#ifndef cmsg_net_h +#define cmsg_net_h + +extern void get_addr_name(char *namebuf, size_t buflen, struct sockaddr_in const *sin); +extern char const *endpoint_name(struct sockaddr_in const *peername); + +extern void start_net(int listen_port); + +#endif diff --git a/node.h b/node.h new file mode 100644 index 0000000..f917c4a --- /dev/null +++ b/node.h @@ -0,0 +1,14 @@ +#ifndef cmsg_node_h +#define cmsg_node_h + +typedef struct Node { + struct NodeClass *node_class; + cmsg_bytes_t name; /* used as (partial) routing key for metamessages */ +} Node; + +typedef struct NodeClass { + void (*destroy)(Node *n); + void (*handle_message)(Node *n, void *buffer, size_t len); +} NodeClass; + +#endif diff --git a/relay.c b/relay.c new file mode 100644 index 0000000..6f37e77 --- /dev/null +++ b/relay.c @@ -0,0 +1,45 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef unsigned char u_char; +#include + +#include "cmsg_private.h" +#include "harness.h" +#include "relay.h" +#include "net.h" + +void start_relay(struct sockaddr_in const *peername, int fd) { + /* + connstate_t *conn = calloc(1, sizeof(connstate_t)); + conn->peername = *peername; + conn->fd = fd; + conn->amqp_conn = amqp_new_connection(); + amqp_set_sockfd(conn->amqp_conn, fd); + conn->io = bufferevent_new(fd, + (evbuffercb) read_callback, + NULL, + (everrorcb) error_callback, + conn); + bufferevent_settimeout(conn->io, PROTOCOL_HEADER_TIMEOUT, 0); + bufferevent_enable(conn->io, EV_READ | EV_WRITE); + conn->state = CONNECTION_STATE_INITIAL; + conn->vhost = NULL; + */ + + info("Accepted connection from %s on fd %d\n", endpoint_name(peername), fd); +} diff --git a/relay.h b/relay.h new file mode 100644 index 0000000..dc7d92e --- /dev/null +++ b/relay.h @@ -0,0 +1,6 @@ +#ifndef cmsg_relay_h +#define cmsg_relay_h + +extern void start_relay(struct sockaddr_in const *peername, int fd); + +#endif diff --git a/util.c b/util.c new file mode 100644 index 0000000..f2d280b --- /dev/null +++ b/util.c @@ -0,0 +1,62 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include + +#include + +#include "cmsg_private.h" + +cmsg_bytes_t cmsg_cstring_bytes(char const *cstr) { + cmsg_bytes_t result; + result.len = strlen(cstr); + result.bytes = (void *) cstr; + return result; +} + +cmsg_bytes_t cmsg_bytes_malloc_dup(cmsg_bytes_t src) { + cmsg_bytes_t result; + result.len = src.len; + result.bytes = malloc(src.len); + if (result.bytes != NULL) { + memcpy(result.bytes, src.bytes, src.len); + } + return result; +} + +cmsg_bytes_t cmsg_bytes_malloc(size_t amount) { + cmsg_bytes_t result; + result.len = amount; + result.bytes = malloc(amount); + return result; +} + +void cmsg_bytes_free(cmsg_bytes_t bytes) { + free(bytes.bytes); +} + +void die(char const *format, ...) { + va_list vl; + va_start(vl, format); + fprintf(stderr, "ERROR: "); + vfprintf(stderr, format, vl); + va_end(vl); + exit(1); +} + +void warn(char const *format, ...) { + va_list vl; + va_start(vl, format); + fprintf(stderr, "WARNING: "); + vfprintf(stderr, format, vl); + va_end(vl); +} + +void info(char const *format, ...) { + va_list vl; + va_start(vl, format); + fprintf(stderr, "INFO: "); + vfprintf(stderr, format, vl); + va_end(vl); +} From 0c97b76064ea4ce48c69aded113ad2879dd0989d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Dec 2010 18:49:37 -0500 Subject: [PATCH 002/122] Fix EMPTY_BYTES definition --- cmsg_private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmsg_private.h b/cmsg_private.h index 37fc507..48e3412 100644 --- a/cmsg_private.h +++ b/cmsg_private.h @@ -6,7 +6,7 @@ typedef struct cmsg_bytes_t { void *bytes; } cmsg_bytes_t; -#define EMPTY_BYTES ((cmsg_bytes_t) { NULL, 0 }) +#define EMPTY_BYTES ((cmsg_bytes_t) { .len = 0, .bytes = NULL }) extern cmsg_bytes_t cmsg_cstring_bytes(char const *cstr); extern cmsg_bytes_t cmsg_bytes_malloc_dup(cmsg_bytes_t src); From 920d5aaaf9429b247565759fcaa3eb1466ab550f Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Dec 2010 18:49:58 -0500 Subject: [PATCH 003/122] Improve BCHECK and PCHECK macros --- cmsg_private.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmsg_private.h b/cmsg_private.h index 48e3412..f33dd1f 100644 --- a/cmsg_private.h +++ b/cmsg_private.h @@ -14,8 +14,8 @@ extern cmsg_bytes_t cmsg_bytes_malloc(size_t amount); extern void cmsg_bytes_free(cmsg_bytes_t bytes); #define ICHECK(result, message) do { if ((result) == -1) { perror(message); exit(2); } } while (0) -#define BCHECK(result, message) do { if (!(result)) { perror(message); exit(2); } } while (0) -#define PCHECK(result, message) do { if (!(result)) { perror(message); exit(2); } } while (0) +#define BCHECK(result, message) do { if ((result) == 0) { perror(message); exit(2); } } while (0) +#define PCHECK(result, message) do { if ((result) == NULL) { perror(message); exit(2); } } while (0) extern void die(char const *format, ...); extern void warn(char const *format, ...); From e20c8d6dd115943fa3fdf39b4bd133a80323f28e Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Dec 2010 18:50:12 -0500 Subject: [PATCH 004/122] Turns out stack size on linux was too small. --- harness.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/harness.c b/harness.c index 0c9b840..b50f32d 100644 --- a/harness.c +++ b/harness.c @@ -14,13 +14,15 @@ typedef unsigned char u_char; #include "cmsg_private.h" #include "harness.h" +#include + #ifdef __APPLE__ /* Bollocks. Looks like OS X chokes unless STACK_SIZE is a multiple of 32k. */ #define STACK_SIZE 32768 #elif linux -#define STACK_SIZE 4096 +#define STACK_SIZE 32768 #else -#error Define STACK_SIZE for your platform. +#error Define STACK_SIZE for your platform. It should probably not be less than 32k? #endif Process *current_process = NULL; From 0a895ac5c0e1e34beddfb26f86660d8366b9540f Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Dec 2010 18:50:42 -0500 Subject: [PATCH 005/122] IO framework in harness --- harness.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++--- harness.h | 30 +++++++++++++- 2 files changed, 137 insertions(+), 8 deletions(-) diff --git a/harness.c b/harness.c index b50f32d..e21f11b 100644 --- a/harness.c +++ b/harness.c @@ -62,19 +62,30 @@ static Process *dequeue(ProcessQueue *pq) { } } -void yield(void) { +static void enqueue_runlist(Process *p) { + p->state = PROCESS_RUNNING; + enqueue(&runlist, p); +} + +static void schedule(void) { if (current_process == NULL) { - ICHECK(setcontext(&scheduler), "yield setcontext"); + ICHECK(setcontext(&scheduler), "schedule setcontext"); } else { - enqueue(&runlist, current_process); - ICHECK(swapcontext(¤t_process->context, &scheduler), "yield swapcontext"); + ICHECK(swapcontext(¤t_process->context, &scheduler), "schedule swapcontext"); } } +void yield(void) { + enqueue_runlist(current_process); + schedule(); +} + void killproc(void) { + assert(current_process->state == PROCESS_RUNNING); + current_process->state = PROCESS_DEAD; enqueue(&deadlist, current_process); current_process = NULL; - yield(); + schedule(); } static void driver(void (*f)(void *), void *arg) { @@ -86,6 +97,8 @@ void spawn(void (*f)(void *), void *arg) { Process *p = calloc(1, sizeof(*p)); PCHECK(p, "spawn calloc"); + p->state = PROCESS_DEAD; + p->stack_base = malloc(STACK_SIZE); /* what is a sane value here? 32k for mac... */ PCHECK(p->stack_base, "stack pointer malloc"); @@ -96,7 +109,97 @@ void spawn(void (*f)(void *), void *arg) { p->context.uc_stack.ss_flags = 0; makecontext(&p->context, (void (*)(void)) driver, 2, f, arg); - enqueue(&runlist, p); + p->link = NULL; + + enqueue_runlist(p); +} + +static void io_isr(struct bufferevent *bufev, IOHandle *h) { + assert(h->p->state == PROCESS_WAITING); + enqueue_runlist(h->p); +} + +static void error_isr(struct bufferevent *bufev, short what, IOHandle *h) { + h->error_direction = what & (EVBUFFER_READ | EVBUFFER_WRITE); + h->error_kind = what & ~(EVBUFFER_READ | EVBUFFER_WRITE); + if (h->p->state == PROCESS_WAITING) { + enqueue_runlist(h->p); + } else { + warn("Um, not sure what to do here. Error what %d, fd %d, ioh %p process %p\n", + what, + bufev->ev_read.ev_fd, + h, + h->p); + } +} + +IOHandle *new_iohandle(int fd) { + IOHandle *h = malloc(sizeof(*h)); + assert(current_process != NULL); + h->p = current_process; + h->fd = fd; + h->io = bufferevent_new(fd, + (evbuffercb) io_isr, + (evbuffercb) io_isr, + (everrorcb) error_isr, + h); + PCHECK(h->io, "bufferevent_new"); + h->error_direction = 0; + h->error_kind = 0; + return h; +} + +void delete_iohandle(IOHandle *h) { + bufferevent_free(h->io); + free(h); +} + +void iohandle_clear_error(IOHandle *h) { + h->error_direction = 0; + h->error_kind = 0; +} + +static void block_on_io(IOHandle *h, short event) { + ICHECK(bufferevent_enable(h->io, event), "bufferevent_enable"); + assert(current_process == h->p); + h->p->state = PROCESS_WAITING; + schedule(); + ICHECK(bufferevent_disable(h->io, event), "bufferevent_disable"); +} + +cmsg_bytes_t iohandle_readwait(IOHandle *h, size_t at_least) { + while (EVBUFFER_LENGTH(h->io->input) < at_least) { + if (h->error_kind) { + return EMPTY_BYTES; + } + block_on_io(h, EV_READ); + } + return (cmsg_bytes_t) { + .len = EVBUFFER_LENGTH(h->io->input), + .bytes = EVBUFFER_DATA(h->io->input) + }; +} + +void iohandle_drain(IOHandle *h, size_t count) { + evbuffer_drain(h->io->input, count); +} + +void iohandle_write(IOHandle *h, cmsg_bytes_t buf) { + ICHECK(bufferevent_write(h->io, buf.bytes, buf.len), "bufferevent_write"); +} + +int iohandle_flush(IOHandle *h) { + while (EVBUFFER_LENGTH(h->io->output) > 0) { + if (h->error_kind) { + return -1; + } + block_on_io(h, EV_WRITE); + } + return 0; +} + +void iohandle_settimeout(IOHandle *h, int timeout_read, int timeout_write) { + bufferevent_settimeout(h->io, timeout_read, timeout_write); } static void clean_dead_processes(void) { diff --git a/harness.h b/harness.h index 217069a..69b3369 100644 --- a/harness.h +++ b/harness.h @@ -1,12 +1,29 @@ #ifndef cmsg_harness_h #define cmsg_harness_h +typedef void (*process_main_t)(void *); + +typedef enum process_state_t_ { + PROCESS_DEAD = 0, + PROCESS_RUNNING, + PROCESS_WAITING +} process_state_t; + typedef struct Process { + process_state_t state; + void *stack_base; ucontext_t context; struct Process *link; - void *stack_base; } Process; +typedef struct IOHandle { + Process *p; + int fd; + struct bufferevent *io; + short error_direction; + short error_kind; +} IOHandle; + typedef struct ProcessQueue { int count; Process *head; @@ -16,7 +33,16 @@ typedef struct ProcessQueue { extern Process *current_process; extern void yield(void); -extern void spawn(void (*f)(void *), void *arg); +extern void spawn(process_main_t f, void *arg); + +extern IOHandle *new_iohandle(int fd); +extern void delete_iohandle(IOHandle *h); +extern void iohandle_clear_error(IOHandle *h); +extern cmsg_bytes_t iohandle_readwait(IOHandle *h, size_t at_least); +extern void iohandle_drain(IOHandle *h, size_t count); +extern void iohandle_write(IOHandle *h, cmsg_bytes_t buf); +extern int iohandle_flush(IOHandle *h); +extern void iohandle_settimeout(IOHandle *h, int timeout_read, int timeout_write); extern void boot_harness(void); From 63ee4ecea3217fd9d62c1482b84fa458b5fe3eb7 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Dec 2010 18:50:58 -0500 Subject: [PATCH 006/122] endpoint_name needs to be reentrant and stack-safe --- net.c | 6 ++---- net.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/net.c b/net.c index 90b532f..8a7ab6a 100644 --- a/net.c +++ b/net.c @@ -38,12 +38,10 @@ void get_addr_name(char *namebuf, size_t buflen, struct sockaddr_in const *sin) } } -char const *endpoint_name(struct sockaddr_in const *peername) { +void endpoint_name(struct sockaddr_in const *peername, cmsg_bytes_t result) { char name[256]; - static char result[256]; get_addr_name(name, sizeof(name), peername); - snprintf(result, sizeof(result), "%s:%d", name, ntohs(peername->sin_port)); - return result; + snprintf(result.bytes, result.len, "%s:%d", name, ntohs(peername->sin_port)); } static void accept_connection(int servfd, short what, void *arg) { diff --git a/net.h b/net.h index 351bf2f..b63307c 100644 --- a/net.h +++ b/net.h @@ -2,7 +2,7 @@ #define cmsg_net_h extern void get_addr_name(char *namebuf, size_t buflen, struct sockaddr_in const *sin); -extern char const *endpoint_name(struct sockaddr_in const *peername); +extern void endpoint_name(struct sockaddr_in const *peername, cmsg_bytes_t result); extern void start_net(int listen_port); From e37e81599b6f345f6c8024668ed3ff2188d94036 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Dec 2010 18:51:22 -0500 Subject: [PATCH 007/122] Stub connection handler --- relay.c | 65 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/relay.c b/relay.c index 6f37e77..ed6c3ae 100644 --- a/relay.c +++ b/relay.c @@ -23,23 +23,52 @@ typedef unsigned char u_char; #include "relay.h" #include "net.h" -void start_relay(struct sockaddr_in const *peername, int fd) { - /* - connstate_t *conn = calloc(1, sizeof(connstate_t)); - conn->peername = *peername; - conn->fd = fd; - conn->amqp_conn = amqp_new_connection(); - amqp_set_sockfd(conn->amqp_conn, fd); - conn->io = bufferevent_new(fd, - (evbuffercb) read_callback, - NULL, - (everrorcb) error_callback, - conn); - bufferevent_settimeout(conn->io, PROTOCOL_HEADER_TIMEOUT, 0); - bufferevent_enable(conn->io, EV_READ | EV_WRITE); - conn->state = CONNECTION_STATE_INITIAL; - conn->vhost = NULL; - */ +struct boot_args { + struct sockaddr_in peername; + int fd; +}; - info("Accepted connection from %s on fd %d\n", endpoint_name(peername), fd); +static void relay_main(struct boot_args *args) { + IOHandle *h = new_iohandle(args->fd); + + { + char name[256]; + endpoint_name(&args->peername, (cmsg_bytes_t) { .bytes = name, .len = sizeof(name) }); + info("Accepted connection from %s on fd %d\n", name, args->fd); + } + + free(args); + + iohandle_settimeout(h, 3, 0); + while (1) { + cmsg_bytes_t buf = iohandle_readwait(h, 1); + if (buf.len == 0) { + switch (h->error_kind) { + case EVBUFFER_TIMEOUT: + info("Timeout\n"); + iohandle_clear_error(h); + iohandle_write(h, cmsg_cstring_bytes("Timed out\n")); + break; + default: + info("Error! 0x%04X\n", h->error_kind); + break; + } + break; + } else { + info("Read %d: %.*s\n", buf.len, buf.len, buf.bytes); + iohandle_drain(h, buf.len); + } + iohandle_write(h, cmsg_cstring_bytes("OK, proceed\n")); + } + + ICHECK(iohandle_flush(h), "iohandle_flush"); + ICHECK(close(h->fd), "close"); + delete_iohandle(h); +} + +void start_relay(struct sockaddr_in const *peername, int fd) { + struct boot_args *args = malloc(sizeof(*args)); + args->peername = *peername; + args->fd = fd; + spawn((process_main_t) relay_main, args); } From 2e43b12616f78cc7135768749ddffb656edbff13 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Dec 2010 22:49:28 -0500 Subject: [PATCH 008/122] Add nap(); fix blocking bugs on write flush --- harness.c | 48 +++++++++++++++++++++++++++++++++++++++++------- harness.h | 2 ++ relay.c | 6 +++++- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/harness.c b/harness.c index e21f11b..a43d7d3 100644 --- a/harness.c +++ b/harness.c @@ -68,6 +68,7 @@ static void enqueue_runlist(Process *p) { } static void schedule(void) { + //info("schedule %p\n", current_process); if (current_process == NULL) { ICHECK(setcontext(&scheduler), "schedule setcontext"); } else { @@ -114,15 +115,45 @@ void spawn(void (*f)(void *), void *arg) { enqueue_runlist(p); } -static void io_isr(struct bufferevent *bufev, IOHandle *h) { - assert(h->p->state == PROCESS_WAITING); +void nap_isr(int fd, short what, void *arg) { + Process *p = (Process *) arg; + //info("nap_isr %p\n", p); + assert((p->state == PROCESS_WAITING) && (p->wait_flags & EV_TIMEOUT)); + p->wait_flags &= ~EV_TIMEOUT; + enqueue_runlist(p); +} + +void nap(long millis) { + struct timeval tv; + assert(current_process != NULL); + assert(current_process->state == PROCESS_RUNNING); + tv.tv_sec = millis / 1000; + tv.tv_usec = (millis % 1000) * 1000; + ICHECK(event_once(-1, EV_TIMEOUT, nap_isr, current_process, &tv), "event_once"); + current_process->state = PROCESS_WAITING; + current_process->wait_flags |= EV_TIMEOUT; + schedule(); +} + +static void input_isr(struct bufferevent *bufev, IOHandle *h) { + //info("input_isr %p r %d w %d\n", h->p, EVBUFFER_LENGTH(bufev->input), EVBUFFER_LENGTH(bufev->output)); + assert((h->p->state == PROCESS_WAITING) && (h->p->wait_flags & EV_READ)); enqueue_runlist(h->p); } +static void output_isr(struct bufferevent *bufev, IOHandle *h) { + //info("output_isr %p r %d w %d\n", h->p, EVBUFFER_LENGTH(bufev->input), EVBUFFER_LENGTH(bufev->output)); + if ((h->p->state == PROCESS_WAITING) && (h->p->wait_flags & EV_WRITE)) { + enqueue_runlist(h->p); + } +} + static void error_isr(struct bufferevent *bufev, short what, IOHandle *h) { + info("error_isr 0x%04X %p\n", what, h->p); h->error_direction = what & (EVBUFFER_READ | EVBUFFER_WRITE); h->error_kind = what & ~(EVBUFFER_READ | EVBUFFER_WRITE); - if (h->p->state == PROCESS_WAITING) { + if ((h->p->state == PROCESS_WAITING) + && (h->p->wait_flags & (EV_READ | EV_WRITE))) { enqueue_runlist(h->p); } else { warn("Um, not sure what to do here. Error what %d, fd %d, ioh %p process %p\n", @@ -139,8 +170,8 @@ IOHandle *new_iohandle(int fd) { h->p = current_process; h->fd = fd; h->io = bufferevent_new(fd, - (evbuffercb) io_isr, - (evbuffercb) io_isr, + (evbuffercb) input_isr, + (evbuffercb) output_isr, (everrorcb) error_isr, h); PCHECK(h->io, "bufferevent_new"); @@ -160,11 +191,11 @@ void iohandle_clear_error(IOHandle *h) { } static void block_on_io(IOHandle *h, short event) { - ICHECK(bufferevent_enable(h->io, event), "bufferevent_enable"); assert(current_process == h->p); h->p->state = PROCESS_WAITING; + h->p->wait_flags |= event; schedule(); - ICHECK(bufferevent_disable(h->io, event), "bufferevent_disable"); + h->p->wait_flags &= ~event; } cmsg_bytes_t iohandle_readwait(IOHandle *h, size_t at_least) { @@ -172,7 +203,9 @@ cmsg_bytes_t iohandle_readwait(IOHandle *h, size_t at_least) { if (h->error_kind) { return EMPTY_BYTES; } + ICHECK(bufferevent_enable(h->io, EV_READ), "bufferevent_enable"); block_on_io(h, EV_READ); + ICHECK(bufferevent_disable(h->io, EV_READ), "bufferevent_disable"); } return (cmsg_bytes_t) { .len = EVBUFFER_LENGTH(h->io->input), @@ -218,6 +251,7 @@ void boot_harness(void) { zero_queue(&runlist); info("Processing %d jobs\n", work.count); while ((current_process = dequeue(&work)) != NULL) { + //info("entering %p\n", current_process); ICHECK(swapcontext(&scheduler, ¤t_process->context), "boot_harness swapcontext"); clean_dead_processes(); } diff --git a/harness.h b/harness.h index 69b3369..5d5cda6 100644 --- a/harness.h +++ b/harness.h @@ -11,6 +11,7 @@ typedef enum process_state_t_ { typedef struct Process { process_state_t state; + int wait_flags; void *stack_base; ucontext_t context; struct Process *link; @@ -34,6 +35,7 @@ extern Process *current_process; extern void yield(void); extern void spawn(process_main_t f, void *arg); +extern void nap(long millis); extern IOHandle *new_iohandle(int fd); extern void delete_iohandle(IOHandle *h); diff --git a/relay.c b/relay.c index ed6c3ae..984e272 100644 --- a/relay.c +++ b/relay.c @@ -39,6 +39,10 @@ static void relay_main(struct boot_args *args) { free(args); + iohandle_write(h, cmsg_cstring_bytes("Hi\n")); + ICHECK(iohandle_flush(h), "iohandle_flush 1"); + nap(1000); + iohandle_write(h, cmsg_cstring_bytes("Proceed\n")); iohandle_settimeout(h, 3, 0); while (1) { cmsg_bytes_t buf = iohandle_readwait(h, 1); @@ -61,7 +65,7 @@ static void relay_main(struct boot_args *args) { iohandle_write(h, cmsg_cstring_bytes("OK, proceed\n")); } - ICHECK(iohandle_flush(h), "iohandle_flush"); + ICHECK(iohandle_flush(h), "iohandle_flush 2"); ICHECK(close(h->fd), "close"); delete_iohandle(h); } From 615dce02b10474799a27f2bb99924d73b2c11a72 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 09:11:53 -0500 Subject: [PATCH 009/122] Looks like 10.5 has more aggressive stack requirements than 10.6. --- harness.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/harness.c b/harness.c index a43d7d3..449521f 100644 --- a/harness.c +++ b/harness.c @@ -18,11 +18,17 @@ typedef unsigned char u_char; #ifdef __APPLE__ /* Bollocks. Looks like OS X chokes unless STACK_SIZE is a multiple of 32k. */ -#define STACK_SIZE 32768 +# include +# if !defined(MAC_OS_X_VERSION_10_6) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 +/* Hmm, and looks like 10.5 has more aggressive stack requirements than 10.6. */ +# define STACK_SIZE 65536 +# else +# define STACK_SIZE 32768 +# endif #elif linux -#define STACK_SIZE 32768 +# define STACK_SIZE 32768 #else -#error Define STACK_SIZE for your platform. It should probably not be less than 32k? +# error Define STACK_SIZE for your platform. It should probably not be less than 32k? #endif Process *current_process = NULL; From 78fc0a40a03892f65765c2f8fb957e9ae199d0ea Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 09:21:58 -0500 Subject: [PATCH 010/122] Remove some info() scheduling noise --- harness.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/harness.c b/harness.c index 449521f..1f15119 100644 --- a/harness.c +++ b/harness.c @@ -261,10 +261,10 @@ void boot_harness(void) { ICHECK(swapcontext(&scheduler, ¤t_process->context), "boot_harness swapcontext"); clean_dead_processes(); } - info("Polling for events\n"); + //info("Polling for events\n"); event_loop(EVLOOP_NONBLOCK); } - info("Blocking for events\n"); + //info("Blocking for events\n"); event_loop(EVLOOP_ONCE); } } From f395f95fd3325b62d909ac5772395c41ec449d7f Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 11:35:02 -0500 Subject: [PATCH 011/122] Split out queue data structure --- Makefile | 2 +- dataq.c | 32 ++++++++++++++++++++++++++++++++ dataq.h | 14 ++++++++++++++ harness.c | 43 ++++++++----------------------------------- harness.h | 6 ------ 5 files changed, 55 insertions(+), 42 deletions(-) create mode 100644 dataq.c create mode 100644 dataq.h diff --git a/Makefile b/Makefile index b84df12..52b5979 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TARGET = cmsg -OBJECTS = main.o harness.o net.o util.o relay.o +OBJECTS = main.o harness.o net.o util.o relay.o dataq.o CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g #CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 diff --git a/dataq.c b/dataq.c new file mode 100644 index 0000000..c90eb32 --- /dev/null +++ b/dataq.c @@ -0,0 +1,32 @@ +#include +#include +#include + +#include "dataq.h" + +#define QLINK(q,x) (*((void **)(((char *) x) + (q)->link_offset))) + +void enqueue(queue_t *q, void *x) { + QLINK(q, x) = NULL; + if (q->head == NULL) { + q->head = x; + } else { + QLINK(q, q->tail) = x; + } + q->tail = x; + q->count++; +} + +void *dequeue(queue_t *q) { + if (q->head == NULL) { + return NULL; + } else { + void *x = q->head; + q->head = QLINK(q, x); + if (q->head == NULL) { + q->tail = NULL; + } + q->count--; + return x; + } +} diff --git a/dataq.h b/dataq.h new file mode 100644 index 0000000..a193b01 --- /dev/null +++ b/dataq.h @@ -0,0 +1,14 @@ +#ifndef cmsg_dataq_h +#define cmsg_dataq_h + +typedef struct queue_t_ { + size_t link_offset; + int count; + void *head; + void *tail; +} queue_t; + +extern void enqueue(queue_t *q, void *x); +extern void *dequeue(queue_t *q); + +#endif diff --git a/harness.c b/harness.c index 1f15119..efa44df 100644 --- a/harness.c +++ b/harness.c @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -13,6 +14,7 @@ typedef unsigned char u_char; #include "cmsg_private.h" #include "harness.h" +#include "dataq.h" #include @@ -33,40 +35,11 @@ typedef unsigned char u_char; Process *current_process = NULL; +#define EMPTY_PROCESS_QUEUE ((queue_t) { offsetof(Process, link), 0, NULL, NULL }) + static ucontext_t scheduler; -static ProcessQueue runlist = { 0, NULL, NULL }; -static ProcessQueue deadlist = { 0, NULL, NULL }; - -static void zero_queue(ProcessQueue *pq) { - pq->count = 0; - pq->head = NULL; - pq->tail = NULL; -} - -static void enqueue(ProcessQueue *pq, Process *p) { - p->link = NULL; - if (pq->head == NULL) { - pq->head = p; - } else { - pq->tail->link = p; - } - pq->tail = p; - pq->count++; -} - -static Process *dequeue(ProcessQueue *pq) { - if (pq->head == NULL) { - return NULL; - } else { - Process *p = pq->head; - pq->head = p->link; - if (pq->head == NULL) { - pq->tail = NULL; - } - pq->count--; - return p; - } -} +static queue_t runlist = EMPTY_PROCESS_QUEUE; +static queue_t deadlist = EMPTY_PROCESS_QUEUE; static void enqueue_runlist(Process *p) { p->state = PROCESS_RUNNING; @@ -253,8 +226,8 @@ void boot_harness(void) { ICHECK(getcontext(&scheduler), "boot_harness getcontext"); while (1) { while (runlist.count) { - ProcessQueue work = runlist; - zero_queue(&runlist); + queue_t work = runlist; + runlist = EMPTY_PROCESS_QUEUE; info("Processing %d jobs\n", work.count); while ((current_process = dequeue(&work)) != NULL) { //info("entering %p\n", current_process); diff --git a/harness.h b/harness.h index 5d5cda6..f03359c 100644 --- a/harness.h +++ b/harness.h @@ -25,12 +25,6 @@ typedef struct IOHandle { short error_kind; } IOHandle; -typedef struct ProcessQueue { - int count; - Process *head; - Process *tail; -} ProcessQueue; - extern Process *current_process; extern void yield(void); From 84219ff9dc7fdd1572981a8daa77c7c27ea86388 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 18:12:38 -0500 Subject: [PATCH 012/122] Hashtable impl; make cmsg_bytes_t hold chars; SPKI SEXP impl --- Makefile | 2 +- Sexp.txt | 699 +++++++++++++++++++++++++++++++++++++++++++++++++ cmsg_private.h | 10 +- harness.c | 5 +- harness.h | 4 +- hashtable.c | 136 ++++++++++ hashtable.h | 35 +++ net.c | 2 +- node.h | 16 +- ref.h | 38 +++ relay.c | 52 ++-- sexp.c | 121 +++++++++ sexp.h | 99 +++++++ sexpio.c | 205 +++++++++++++++ sexpio.h | 10 + 15 files changed, 1395 insertions(+), 39 deletions(-) create mode 100644 Sexp.txt create mode 100644 hashtable.c create mode 100644 hashtable.h create mode 100644 ref.h create mode 100644 sexp.c create mode 100644 sexp.h create mode 100644 sexpio.c create mode 100644 sexpio.h diff --git a/Makefile b/Makefile index 52b5979..0175cde 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TARGET = cmsg -OBJECTS = main.o harness.o net.o util.o relay.o dataq.o +OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g #CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 diff --git a/Sexp.txt b/Sexp.txt new file mode 100644 index 0000000..caf4542 --- /dev/null +++ b/Sexp.txt @@ -0,0 +1,699 @@ +Network Working Group R. Rivest +Internet Draft May 4, 1997 +Expires November 4, 1997 + + + S-Expressions + draft-rivest-sexp-00.txt + + +Status of this Memo + + Distribution of this memo is unlimited. + + This document is an Internet-Draft. Internet Drafts are working + documents of the Internet Engineering Task Force (IETF), its Areas, + and its Working Groups. Note that other groups may also distribute + working documents as Internet Drafts. + + Internet Drafts are draft documents valid for a maximum of six + months, and may be updated, replaced, or obsoleted by other documents + at any time. It is not appropriate to use Internet Drafts as + reference material, or to cite them other than as a ``working draft'' + or ``work in progress.'' + + To learn the current status of any Internet-Draft, please check the + ``1id-abstracts.txt'' listing contained in the internet-drafts Shadow + Directories on: ftp.is.co.za (Africa), nic.nordu.net (Europe), + ds.internic.net (US East Coast), ftp.isi.edu (US West Coast), + or munnari.oz.au (Pacific Rim) + + +Abstract + +This memo describes a data structure called "S-expressions" that are +suitable for representing arbitrary complex data structures. We make +precise the encodings of S-expressions: we give a "canonical form" for +S-expressions, described two "transport" representations, and also +describe an "advanced" format for display to people. + + + +1. Introduction + +S-expressions are data structures for representing complex data. They +are either byte-strings ("octet-strings") or lists of simpler +S-expressions. Here is a sample S-expression: + + (snicker "abc" (#03# |YWJj|)) + +It is a list of length three: + + -- the octet-string "snicker" + + -- the octet-string "abc" + + -- a sub-list containing two elements: + - the hexadecimal constant #03# + - the base-64 constant |YWJj| (which is the same as "abc") + +This note gives a specific proposal for constructing and utilizing +S-expressions. The proposal is independent of any particular application. + +Here are the design goals for S-expressions: + + -- generality: S-expressions should be good at representing arbitrary + data. + + -- readability: it should be easy for someone to examine and + understand the structure of an S-expression. + + -- economy: S-expressions should represent data compactly. + + -- tranportability: S-expressions should be easy to transport + over communication media (such as email) that are known to be + less than perfect. + + -- flexibility: S-expressions should make it relatively simple to + modify and extend data structures. + + -- canonicalization: it should be easy to produce a unique + "canonical" form of an S-expression, for digital signature purposes. + + -- efficiency: S-expressions should admit in-memory representations + that allow efficient processing. + + +Section 2 gives an introduction to S-expressions. +Section 3 discusses the character sets used. +Section 4 presents the various representations of octet-strings. +Section 5 describes how to represent lists. +Section 6 discusses how S-expressions are represented for various uses. +Section 7 gives a BNF syntax for S-expressions. +Section 8 talks about how S-expressions might be represented in memory. +Section 9 briefly describes implementations for handling S-expressions. +Section 10 discusses how applications might utilize S-expressions. +Section 11 gives historical notes on S-expressions. +Section 12 gives references. + +2. S-expressions -- informal introduction + +Informally, an S-expression is either: + -- an octet-string, or + -- a finite list of simpler S-expressions. + +An octet-string is a finite sequence of eight-bit octets. There may be +many different but equivalent ways of representing an octet-string + + abc -- as a token + + "abc" -- as a quoted string + + #616263# -- as a hexadecimal string + + 3:abc -- as a length-prefixed "verbatim" encoding + + {MzphYmM=} -- as a base-64 encoding of the verbatim encoding + (that is, an encoding of "3:abc") + + |YWJj| -- as a base-64 encoding of the octet-string "abc" + +These encodings are all equivalent; they all denote the same octet string. + +We will give details of these encodings later on, and also describe how to +give a "display type" to a byte string. + +A list is a finite sequence of zero or more simpler S-expressions. A list +may be represented by using parentheses to surround the sequence of encodings +of its elements, as in: + + (abc (de #6667#) "ghi jkl") + +As we see, there is variability possible in the encoding of an +S-expression. In some cases, it is desirable to standardize or +restrict the encodings; in other cases it is desirable to have no +restrictions. The following are the target cases we aim to handle: + + -- a "transport" encoding for transporting the S-expression between + computers. + + -- a "canonical" encoding, used when signing the S-expression. + + -- an "advanced" encoding used for input/output to people. + + -- an "in-memory" encoding used for processing the S-expression in + the computer. + +These need not be different; in this proposal the canonical encoding +is the same as the transport encoding, for example. In this note we +propose (related) encoding techniques for each of these uses. + +3. Character set + +We will be describing encodings of S-expressions. Except when giving +"verbatim" encodings, the character set used is limited to the following +characters in US-ASCII: + Alphabetic: A B ... Z a b ... z + numeric: 0 1 ... 9 + whitespace: space, horizontal tab, vertical tab, form-feed + carriage-return, line-feed + The following graphics characters, which we call "pseudo-alphabetic": + - hyphen or minus + . period + / slash + _ underscore + : colon + * asterisk + + plus + = equal + The following graphics characters, which are "reserved punctuation": + ( left parenthesis + ) right parenthesis + [ left bracket + ] right bracket + { left brace + } right brace + | vertical bar + # number sign + " double quote + & ampersand + \ backslash + The following characters are unused and unavailable, except in + "verbatim" encodings: + ! exclamation point + % percent + ^ circumflex + ~ tilde + ; semicolon + ' apostrophe + , comma + < less than + > greater than + ? question mark + + +4. Octet string representations + +This section describes in detail the ways in which an octet-string may +be represented. + +We recall that an octet-string is any finite sequence of octets, and +that the octet-string may have length zero. + + +4.1 Verbatim representation + +A verbatim encoding of an octet string consists of four parts: + + -- the length (number of octets) of the octet-string, + given in decimal most significant digit first, with + no leading zeros. + + -- a colon ":" + + -- the octet string itself, verbatim. + +There are no blanks or whitespace separating the parts. No "escape +sequences" are interpreted in the octet string. This encoding is also +called a "binary" or "raw" encoding. + +Here are some sample verbatim encodings: + + 3:abc + 7:subject + 4::::: + 12:hello world! + 10:abcdefghij + 0: + +4.2 Quoted-string representation + +The quoted-string representation of an octet-string consists of: + + -- an optional decimal length field + + -- an initial double-quote (") + + -- the octet string with "C" escape conventions (\n,etc) + + -- a final double-quote (") + +The specified length is the length of the resulting string after any +escape sequences have been handled. The string does not have any +"terminating NULL" that C includes, and the length does not count such +a character. + +The length is optional. + +The escape conventions within the quoted string are as follows (these follow +the "C" programming language conventions, with an extension for +ignoring line terminators of just LF or CRLF): + \b -- backspace + \t -- horizontal tab + \v -- vertical tab + \n -- new-line + \f -- form-feed + \r -- carriage-return + \" -- double-quote + \' -- single-quote + \\ -- back-slash + \ooo -- character with octal value ooo (all three digits + must be present) + \xhh -- character with hexadecimal value hh (both digits + must be present) + \ -- causes carriage-return to be ignored. + \ -- causes linefeed to be ignored + \ -- causes CRLF to be ignored. + \ -- causes LFCR to be ignored. + +Here are some examples of quoted-string encodings: + + "subject" + "hi there" + 7"subject" + 3"\n\n\n" + "This has\n two lines." + "This has\ + one." + "" + +4.3 Token representation + +An octet string that meets the following conditions may be given +directly as a "token". + + -- it does not begin with a digit + + -- it contains only characters that are + -- alphabetic (upper or lower case), + -- numeric, or + -- one of the eight "pseudo-alphabetic" punctuation marks: + - . / _ : * + = + (Note: upper and lower case are not equivalent.) + (Note: A token may begin with punctuation, including ":"). + +Here are some examples of token representations: + + subject + not-before + class-of-1997 + //microsoft.com/names/smith + * + + +4.4 Hexadecimal representation + +An octet-string may be represented with a hexadecimal encoding consisting of: + + -- an (optional) decimal length of the octet string + + -- a sharp-sign "#" + + -- a hexadecimal encoding of the octet string, with each octet + represented with two hexadecimal digits, most significant + digit first. + + -- a sharp-sign "#" + +There may be whitespace inserted in the midst of the hexadecimal +encoding arbitrarily; it is ignored. It is an error to have +characters other than whitespace and hexadecimal digits. + +Here are some examples of hexadecimal encodings: + + #616263# -- represents "abc" + 3#616263# -- also represents "abc" + # 616 + 263 # -- also represents "abc" + + +4.5 Base-64 representation + +An octet-string may be represented in a base-64 coding consisting of: + + -- an (optional) decimal length of the octet string + + -- a vertical bar "|" + + -- the rfc 1521 base-64 encoding of the octet string. + + -- a final vertical bar "|" + +The base-64 encoding uses only the characters + A-Z a-z 0-9 + / = +It produces four characters of output for each three octets of input. +If the input has one or two left-over octets of input, it produces an +output block of length four ending in two or one equals signs, respectively. +Output routines compliant with this standard MUST output the equals signs +as specified. Input routines MAY accept inputs where the equals signs are +dropped. + +There may be whitespace inserted in the midst of the base-64 encoding +arbitrarily; it is ignored. It is an error to have characters other +than whitespace and base-64 characters. + +Here are some examples of base-64 encodings: + + |YWJj| -- represents "abc" + | Y W + J j | -- also represents "abc" + 3|YWJj| -- also represents "abc" + |YWJjZA==| -- represents "abcd" + |YWJjZA| -- also represents "abcd" + + +4.6 Display hint + +Any octet string may be preceded by a single "display hint". + +The purposes of the display hint is to provide information on how +to display the octet string to a user. It has no other function. +Many of the MIME types work here. + +A display-hint is an octet string surrounded by square brackets. +There may be whitespace separating the octet string from the +surrounding brackets. Any of the legal formats may be used for the +octet string. + +Here are some examples of display-hints: + + [image/gif] + [URI] + [charset=unicode-1-1] + [text/richtext] + [application/postscript] + [audio/basic] + ["http://abc.com/display-types/funky.html"] + +In applications an octet-string that is untyped may be considered to have +a pre-specified "default" mime type. The mime type + "text/plain; charset=iso-8859-1" +is the standard default. + + +4.7 Equality of octet-strings + +Two octet strings are considered to be "equal" if and only if they +have the same display hint and the same data octet strings. + +Note that octet-strings are "case-sensitive"; the octet-string "abc" +is not equal to the octet-string "ABC". + +An untyped octet-string can be compared to another octet-string (typed +or not) by considering it as a typed octet-string with the default +mime-type. + + +5. Lists + +Just as with octet-strings, there are several ways to represent an +S-expression. Whitespace may be used to separate list elements, but +they are only required to separate two octet strings when otherwise +the two octet strings might be interpreted as one, as when one token +follows another. Also, whitespace may follow the initial left +parenthesis, or precede the final right parenthesis. + +Here are some examples of encodings of lists: + + (a b c) + + ( a ( b c ) ( ( d e ) ( e f ) ) ) + + (11:certificate(6:issuer3:bob)(7:subject5:alice)) + + ({3Rt=} "1997" murphy 3:{XC++}) + + +6. Representation types + +There are three "types" of representations: + + -- canonical + + -- basic transport + + -- advanced transport + +The first two MUST be supported by any implementation; the last is +optional. + + +6.1 Canonical representation + +This canonical representation is used for digital signature purposes, +transmission, etc. It is uniquely defined for each S-expression. It +is not particularly readable, but that is not the point. It is +intended to be very easy to parse, to be reasonably economical, and to +be unique for any S-expression. + +The "canonical" form of an S-expression represents each octet-string +in verbatim mode, and represents each list with no blanks separating +elements from each other or from the surrounding parentheses. + +Here are some examples of canonical representations of S-expressions: + + (6:issuer3:bob) + + (4:icon[12:image/bitmap]9:xxxxxxxxx) + + (7:subject(3:ref5:alice6:mother)) + + +6.2 Basic transport representation + +There are two forms of the "basic transport" representation: + + -- the canonical representation + + -- an rfc-2045 base-64 representation of the canonical representation, + surrounded by braces. + +The transport mechanism is intended to provide a universal means of +representing S-expressions for transport from one machine to another. + +Here are some examples of an S-expression represented in basic +transport mode: + + (1:a1:b1:c) + + {KDE6YTE6YjE6YykA} + + (this is the same S-expression encoded in base-64) + +There is a difference between the brace notation for base-64 used here +and the || notation for base-64'd octet-strings described above. Here +the base-64 contents are converted to octets, and then re-scanned as +if they were given originally as octets. With the || notation, the +contents are just turned into an octet-string. + + +6.3 Advanced transport representation + +The "advanced transport" representation is intended to provide more +flexible and readable notations for documentation, design, debugging, +and (in some cases) user interface. + +The advanced transport representation allows all of the representation +forms described above, include quoted strings, base-64 and hexadecimal +representation of strings, tokens, representations of strings with +omitted lengths, and so on. + + +7. BNF for syntax + +We give separate BNF's for canonical and advanced forms of S-expressions. +We use the following notation: + * means 0 or more occurrences of + + means 1 or more occurrences of + ? means 0 or 1 occurrences of + parentheses are used for grouping, as in ( | )* + +For canonical and basic transport: + + :: | + :: ? ; + :: ; + :: "[" "]" ; + :: ":" ; + :: + ; + -- decimal numbers should have no unnecessary leading zeros + -- any string of bytes, of the indicated length + :: "(" * ")" ; + :: "0" | ... | "9" ; + +For advanced transport: + + :: | + :: ? ; + :: | | | | + ; + :: "[" "]" ; + :: ":" ; + :: + ; + -- decimal numbers should have no unnecessary leading zeros + -- any string of bytes, of the indicated length + :: + ; + :: ? "|" ( | )* "|" ; + :: "#" ( | )* "#" ; + :: ? + :: "\"" "\"" + :: "(" ( | )* ")" ; + :: * ; + :: | | ; + :: | | ; + :: "a" | ... | "z" ; + :: "A" | ... | "Z" ; + :: "0" | ... | "9" ; + :: | "A" | ... | "F" | "a" | ... | "f" ; + :: "-" | "." | "/" | "_" | ":" | "*" | "+" | "=" ; + :: " " | "\t" | "\r" | "\n" ; + :: | | "+" | "/" | "=" ; + :: "" ; + +8. In-memory representations + +For processing, the S-expression would typically be parsed and represented +in memory in a more more amenable to efficient processing. We suggest +two alternatives: + + -- "list-structure" + + -- "array-layout" + +We only sketch these here, as they are only suggestive. The code referenced +below illustrates these styles in more detail. + + +8.1. List-structure memory representation + +Here there are separate records for simple-strings, strings, and +lists. An S-expression of the form ("abc" "de") would require two +records for the simple strings, two for the strings, and two for the +list elements. This is a fairly conventional representation, and +details are omitted here. + +8.2 Array-layout memory representation + +Here each S-expression is represented as a contiguous array of bytes. +The first byte codes the "type" of the S-expression: + + 01 octet-string + + 02 octet-string with display-hint + + 03 beginning of list (and 00 is used for "end of list") + +Each of the three types is immediately followed by a k-byte integer +indicating the size (in bytes) of the following representation. Here +k is an integer that depends on the implementation, it might be +anywhere from 2 to 8, but would be fixed for a given implementation; +it determines the size of the objects that can be handled. The transport +and canonical representations are independent of the choice of k made by +the implementation. + +Although the length of lists are not given in the usual S-expression +notations, it is easy to fill them in when parsing; when you reach a +right-parenthesis you know how long the list representation was, and +where to go back to fill in the missing length. + + +8.2.1 Octet string + +This is represented as follows: + + 01 + +For example (here k = 2) + + 01 0003 a b c + +8.2.2 Octet-string with display-hint + +This is represented as follows: + + 02 + 01 /* for display-type */ + 01 /* for octet-string */ + +For example, the S-expression + + [gif] #61626364# + +would be represented as (with k = 2) + + 02 000d + 01 0003 g i f + 01 0004 61 62 63 64 + +8.2.3 List + +This is represented as + + 03 ... 00 + +For example, the list (abc [d]ef (g)) is represented in memory as (with k=2) + + 03 001b + 01 0003 a b c + 02 0009 + 01 0001 d + 01 0002 e f + 03 0005 + 01 0001 g + 00 + 00 + +9. Code + +There is code available for reading and parsing the various +S-expression formats proposed here. + +See http://theory.lcs.mit.edu/~rivest/sexp.html + + +10. Utilization of S-expressions + +This note has described S-expressions in general form. Application writers +may wish to restrict their use of S-expressions in various ways. Here are +some possible restrictions that might be considered: + + -- no display-hints + -- no lengths on hexadecimal, quoted-strings, or base-64 encodings + -- no empty lists + -- no empty octet-strings + -- no lists having another list as its first element + -- no base-64 or hexadecimal encodings + -- fixed limits on the size of octet-strings + +11. Historical note + +The S-expression technology described here was originally developed +for ``SDSI'' (the Simple Distributed Security Infrastructure by +Lampson and Rivest [SDSI]) in 1996, although the origins clearly date +back to McCarthy's LISP programming language. It was further refined +and improved during the merger of SDSI and SPKI [SPKI] during the +first half of 1997. S-expressions are similar to, but more readable +and flexible than, Bernstein's "net-strings" [BERN]. + +12. References + +[SDSI] "A Simple Distributed Security Architecture", by + Butler Lampson, and Ronald L. Rivest + http://theory.lcs.mit.edu/~cis/sdsi.html + +[SPKI] SPKI--A + Simple Public Key Infrastructure + +[BERN] Dan Bernstein's "net-strings"; Internet Draft + draft-bernstein-netstrings-02.txt + +Author's Address + + Ronald L. Rivest + Room 324, 545 Technology Square + MIT Laboratory for Computer Science + Cambridge, MA 02139 + + rivest@theory.lcs.mit.edu + + diff --git a/cmsg_private.h b/cmsg_private.h index f33dd1f..7add267 100644 --- a/cmsg_private.h +++ b/cmsg_private.h @@ -3,10 +3,14 @@ typedef struct cmsg_bytes_t { size_t len; - void *bytes; + unsigned char *bytes; } cmsg_bytes_t; -#define EMPTY_BYTES ((cmsg_bytes_t) { .len = 0, .bytes = NULL }) +#define CMSG_BYTES(length, bytes_ptr) ((cmsg_bytes_t) { \ + .len = (length), \ + .bytes = (unsigned char *) (bytes_ptr) \ + }) +#define EMPTY_BYTES CMSG_BYTES(0, NULL) extern cmsg_bytes_t cmsg_cstring_bytes(char const *cstr); extern cmsg_bytes_t cmsg_bytes_malloc_dup(cmsg_bytes_t src); @@ -17,7 +21,7 @@ extern void cmsg_bytes_free(cmsg_bytes_t bytes); #define BCHECK(result, message) do { if ((result) == 0) { perror(message); exit(2); } } while (0) #define PCHECK(result, message) do { if ((result) == NULL) { perror(message); exit(2); } } while (0) -extern void die(char const *format, ...); +extern __attribute__((noreturn)) void die(char const *format, ...); extern void warn(char const *format, ...); extern void info(char const *format, ...); diff --git a/harness.c b/harness.c index efa44df..0f9b691 100644 --- a/harness.c +++ b/harness.c @@ -186,10 +186,7 @@ cmsg_bytes_t iohandle_readwait(IOHandle *h, size_t at_least) { block_on_io(h, EV_READ); ICHECK(bufferevent_disable(h->io, EV_READ), "bufferevent_disable"); } - return (cmsg_bytes_t) { - .len = EVBUFFER_LENGTH(h->io->input), - .bytes = EVBUFFER_DATA(h->io->input) - }; + return CMSG_BYTES(EVBUFFER_LENGTH(h->io->input), EVBUFFER_DATA(h->io->input)); } void iohandle_drain(IOHandle *h, size_t count) { diff --git a/harness.h b/harness.h index f03359c..7f30309 100644 --- a/harness.h +++ b/harness.h @@ -21,8 +21,8 @@ typedef struct IOHandle { Process *p; int fd; struct bufferevent *io; - short error_direction; - short error_kind; + unsigned short error_direction; + unsigned short error_kind; } IOHandle; extern Process *current_process; diff --git a/hashtable.c b/hashtable.c new file mode 100644 index 0000000..813024c --- /dev/null +++ b/hashtable.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include + +#include + +#include "cmsg_private.h" +#include "hashtable.h" + +uint32_t hash_bytes(cmsg_bytes_t bytes) { + /* http://en.wikipedia.org/wiki/Jenkins_hash_function */ + uint32_t hash = 0; + size_t i; + + for (i = 0; i < bytes.len; i++) { + hash += bytes.bytes[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; +} + +void init_hashtable(hashtable_t *table, + size_t initial_bucket_count, + void *(*dup_value)(void *), + void (*free_value)(void *)) +{ + table->bucket_count = initial_bucket_count; + table->entry_count = 0; + table->buckets = NULL; + table->dup_value = dup_value; + table->free_value = free_value; + + if (initial_bucket_count > 0) { + table->buckets = realloc(table->buckets, + initial_bucket_count * sizeof(hashtable_entry_t *)); + } +} + +static void destroy_entry(hashtable_t *table, hashtable_entry_t *entry) { + cmsg_bytes_free(entry->key); + if (table->free_value != NULL) { + table->free_value(entry->value); + } + free(entry); +} + +void destroy_hashtable(hashtable_t *table) { + if (table->buckets != NULL) { + int i; + for (i = 0; i < table->bucket_count; i++) { + hashtable_entry_t *chain = table->buckets[i]; + table->buckets[i] = NULL; + while (chain != NULL) { + hashtable_entry_t *next = chain->next; + destroy_entry(table, chain); + chain = next; + } + } + free(table->buckets); + } +} + +static hashtable_entry_t **hashtable_find(hashtable_t *table, cmsg_bytes_t key) { + uint32_t h = hash_bytes(key) % table->bucket_count; + hashtable_entry_t **entryptr = &(table->buckets[h]); + hashtable_entry_t *entry = *entryptr; + while (entry != NULL) { + if ((entry->key.len == key.len) && !memcmp(entry->key.bytes, key.bytes, key.len)) { + break; + } + entryptr = &entry->next; + entry = *entryptr; + } + return entryptr; +} + +int hashtable_get(hashtable_t *table, cmsg_bytes_t key, void **valueptr) { + hashtable_entry_t **entryptr = hashtable_find(table, key); + if (*entryptr == NULL) { + return 0; + } else { + *valueptr = (*entryptr)->value; + return 1; + } +} + +int hashtable_put(hashtable_t *table, cmsg_bytes_t key, void *value) { + /* TODO: grow and rehash */ + hashtable_entry_t **entryptr = hashtable_find(table, key); + if (*entryptr == NULL) { + hashtable_entry_t *entry = malloc(sizeof(hashtable_entry_t)); + entry->next = NULL; + entry->key = cmsg_bytes_malloc_dup(key); + entry->value = (table->dup_value == NULL) ? value : table->dup_value(value); + *entryptr = entry; + table->entry_count++; + return 1; + } else { + if (table->free_value != NULL) { + table->free_value((*entryptr)->value); + } + (*entryptr)->value = (table->dup_value == NULL) ? value : table->dup_value(value); + return 0; + } +} + +int hashtable_erase(hashtable_t *table, cmsg_bytes_t key) { + hashtable_entry_t **entryptr = hashtable_find(table, key); + if (*entryptr == NULL) { + return 0; + } else { + hashtable_entry_t *entry = *entryptr; + *entryptr = entry->next; + destroy_entry(table, entry); + table->entry_count--; + return 1; + } +} + +void hashtable_foreach(hashtable_t *table, + hashtable_iterator_t iterator, + void *context) +{ + int i; + for (i = 0; i < table->bucket_count; i++) { + hashtable_entry_t *chain; + for (chain = table->buckets[i]; chain != NULL; chain = chain->next) { + iterator(context, chain->key, chain->value); + } + } +} diff --git a/hashtable.h b/hashtable.h new file mode 100644 index 0000000..8d36774 --- /dev/null +++ b/hashtable.h @@ -0,0 +1,35 @@ +#ifndef cmsg_hashtable_h +#define cmsg_hashtable_h + +typedef struct hashtable_entry_t_ { + struct hashtable_entry_t_ *next; + cmsg_bytes_t key; + void *value; +} hashtable_entry_t; + +typedef struct hashtable_t_ { + size_t bucket_count; + size_t entry_count; + hashtable_entry_t **buckets; + void *(*dup_value)(void *); + void (*free_value)(void *); +} hashtable_t; + +typedef void (*hashtable_iterator_t)(void *context, cmsg_bytes_t key, void *value); + +extern uint32_t hash_bytes(cmsg_bytes_t bytes); + +extern void init_hashtable(hashtable_t *table, + size_t initial_bucket_count, + void *(*dup_value)(void *), + void (*free_value)(void *)); +extern void destroy_hashtable(hashtable_t *table); + +extern int hashtable_get(hashtable_t *table, cmsg_bytes_t key, void **valueptr); +extern int hashtable_put(hashtable_t *table, cmsg_bytes_t key, void *value); +extern int hashtable_erase(hashtable_t *table, cmsg_bytes_t key); +extern void hashtable_foreach(hashtable_t *table, + hashtable_iterator_t iterator, + void *context); + +#endif diff --git a/net.c b/net.c index 8a7ab6a..245c2ca 100644 --- a/net.c +++ b/net.c @@ -41,7 +41,7 @@ void get_addr_name(char *namebuf, size_t buflen, struct sockaddr_in const *sin) void endpoint_name(struct sockaddr_in const *peername, cmsg_bytes_t result) { char name[256]; get_addr_name(name, sizeof(name), peername); - snprintf(result.bytes, result.len, "%s:%d", name, ntohs(peername->sin_port)); + snprintf((char *) result.bytes, result.len, "%s:%d", name, ntohs(peername->sin_port)); } static void accept_connection(int servfd, short what, void *arg) { diff --git a/node.h b/node.h index f917c4a..6c42456 100644 --- a/node.h +++ b/node.h @@ -1,14 +1,16 @@ #ifndef cmsg_node_h #define cmsg_node_h -typedef struct Node { - struct NodeClass *node_class; +typedef struct node_t_ { + struct node_class_t_ *node_class; cmsg_bytes_t name; /* used as (partial) routing key for metamessages */ -} Node; +} node_t; -typedef struct NodeClass { - void (*destroy)(Node *n); - void (*handle_message)(Node *n, void *buffer, size_t len); -} NodeClass; +typedef struct node_class_t_ { + void (*destroy)(node_t *n); + void (*handle_message)(node_t *n, msg_t *m); +} node_class_t; + +extern node_t *new_node( #endif diff --git a/ref.h b/ref.h new file mode 100644 index 0000000..d84c811 --- /dev/null +++ b/ref.h @@ -0,0 +1,38 @@ +#ifndef cmsg_ref_h +#define cmsg_ref_h + +typedef struct refcount_t_ { + unsigned int count; +} refcount_t; + +#define ZERO_REFCOUNT() ((refcount_t) { .count = 0 }) + +#define INCREF(x) ({ \ + typeof(x) __x = (x); \ + if (__x != NULL) { \ + __x->refcount.count++; \ + } \ + __x; \ + }) + +#define UNGRAB(x) ({ \ + typeof(x) __x = (x); \ + if (__x != NULL) { \ + assert(__x->refcount.count); \ + __x->refcount.count--; \ + } \ + __x; \ + }) + +#define DECREF(x, dtor) ({ \ + typeof(x) __x = (x); \ + if (__x != NULL) { \ + (__x->refcount.count)--; \ + if (__x->refcount.count == 0) { \ + (dtor)(__x); \ + } \ + } \ + (typeof(__x)) 0; \ + }) + +#endif diff --git a/relay.c b/relay.c index 984e272..4ac3de0 100644 --- a/relay.c +++ b/relay.c @@ -22,6 +22,9 @@ typedef unsigned char u_char; #include "harness.h" #include "relay.h" #include "net.h" +#include "ref.h" +#include "sexp.h" +#include "sexpio.h" struct boot_args { struct sockaddr_in peername; @@ -30,10 +33,11 @@ struct boot_args { static void relay_main(struct boot_args *args) { IOHandle *h = new_iohandle(args->fd); + IOHandle *out = new_iohandle(1); { char name[256]; - endpoint_name(&args->peername, (cmsg_bytes_t) { .bytes = name, .len = sizeof(name) }); + endpoint_name(&args->peername, CMSG_BYTES(sizeof(name), name)); info("Accepted connection from %s on fd %d\n", name, args->fd); } @@ -43,31 +47,37 @@ static void relay_main(struct boot_args *args) { ICHECK(iohandle_flush(h), "iohandle_flush 1"); nap(1000); iohandle_write(h, cmsg_cstring_bytes("Proceed\n")); - iohandle_settimeout(h, 3, 0); - while (1) { - cmsg_bytes_t buf = iohandle_readwait(h, 1); - if (buf.len == 0) { - switch (h->error_kind) { - case EVBUFFER_TIMEOUT: - info("Timeout\n"); - iohandle_clear_error(h); - iohandle_write(h, cmsg_cstring_bytes("Timed out\n")); - break; - default: - info("Error! 0x%04X\n", h->error_kind); - break; - } - break; - } else { - info("Read %d: %.*s\n", buf.len, buf.len, buf.bytes); - iohandle_drain(h, buf.len); + //iohandle_settimeout(h, 3, 0); + + loop: + { + sexp_t *x = sexp_read(h); + switch (h->error_kind) { + case 0: + fflush(NULL); + sexp_write(out, x); + iohandle_write(out, cmsg_cstring_bytes("\n")); + ICHECK(iohandle_flush(out), "iohandle_flush out"); + DECREF(x, sexp_destructor); + iohandle_write(h, cmsg_cstring_bytes("OK, proceed\n")); + goto loop; + + case EVBUFFER_TIMEOUT: + info("Timeout\n"); + iohandle_clear_error(h); + iohandle_write(h, cmsg_cstring_bytes("Timed out\n")); + ICHECK(iohandle_flush(h), "iohandle_flush 2"); + break; + + default: + info("Error! 0x%04X\n", h->error_kind); + break; } - iohandle_write(h, cmsg_cstring_bytes("OK, proceed\n")); } - ICHECK(iohandle_flush(h), "iohandle_flush 2"); ICHECK(close(h->fd), "close"); delete_iohandle(h); + delete_iohandle(out); } void start_relay(struct sockaddr_in const *peername, int fd) { diff --git a/sexp.c b/sexp.c new file mode 100644 index 0000000..bc28820 --- /dev/null +++ b/sexp.c @@ -0,0 +1,121 @@ +#include +#include +#include + +#include + +#include "cmsg_private.h" +#include "ref.h" +#include "sexp.h" + +static sexp_t *freelist = NULL; + +static inline sexp_t *alloc_shell(sexp_type_t kind) { + sexp_t *x = freelist; + if (x == NULL) { + x = malloc(sizeof(*x)); + } else { + freelist = x->data.pair.tail; + } + x->refcount = ZERO_REFCOUNT(); + x->kind = kind; + return x; +} + +static inline void release_shell(sexp_t *x) { + x->data.pair.tail = freelist; + freelist = x; +} + +void sexp_data_destructor(sexp_data_t *data) { + cmsg_bytes_free(data->data); + free(data); +} + +void sexp_destructor(sexp_t *x) { + tail_recursion: + switch (x->kind) { + case SEXP_BYTES: + cmsg_bytes_free(x->data.bytes); + break; + case SEXP_SLICE: + DECREF(x->data.slice.data, sexp_data_destructor); + break; + case SEXP_DISPLAY_HINT: + case SEXP_PAIR: { + sexp_t *next = x->data.pair.tail; + DECREF(x->data.pair.head, sexp_destructor); + if (next != NULL) { + if (next->refcount.count == 1) { + release_shell(x); + x = next; + goto tail_recursion; + } else { + DECREF(next, sexp_destructor); + } + } + break; + } + default: + die("Unknown sexp kind %d in dtor\n", x->kind); + } + release_shell(x); +} + +sexp_data_t *sexp_data_copy(cmsg_bytes_t body, size_t offset, size_t length) { + assert(offset + length <= body.len); + return sexp_data_alias(cmsg_bytes_malloc_dup(CMSG_BYTES(length, body.bytes + offset))); +} + +sexp_data_t *sexp_data_alias(cmsg_bytes_t body) { + sexp_data_t *data = malloc(sizeof(*data)); + data->refcount = ZERO_REFCOUNT(); + data->data = body; + return data; +} + +sexp_t *sexp_bytes(cmsg_bytes_t bytes) { + sexp_t *x = alloc_shell(SEXP_BYTES); + x->data.bytes = cmsg_bytes_malloc_dup(bytes); + return x; +} + +sexp_t *sexp_slice(sexp_data_t *data, size_t offset, size_t length) { + sexp_t *x = alloc_shell(SEXP_SLICE); + x->data.slice.data = INCREF(data); + x->data.slice.offset = offset; + x->data.slice.length = length; + return x; +} + +sexp_t *sexp_display_hint(sexp_t *hint, sexp_t *body) { + sexp_t *x = alloc_shell(SEXP_DISPLAY_HINT); + assert(sexp_simple_stringp(hint)); + assert(sexp_simple_stringp(body)); + x->data.pair.head = INCREF(hint); + x->data.pair.tail = INCREF(body); + return x; +} + +sexp_t *sexp_cons(sexp_t *head, sexp_t *tail) { + sexp_t *x = alloc_shell(SEXP_PAIR); + x->data.pair.head = INCREF(head); + x->data.pair.tail = INCREF(tail); + return x; +} + +cmsg_bytes_t sexp_data(sexp_t *x) { + restart: + switch (x->kind) { + case SEXP_BYTES: + return x->data.bytes; + case SEXP_SLICE: + return CMSG_BYTES(x->data.slice.length, + x->data.slice.data->data.bytes + x->data.slice.offset); + case SEXP_DISPLAY_HINT: + x = x->data.pair.tail; + goto restart; + default: + die("Unknown sexp kind %d in data accessor\n", x->kind); + } +} diff --git a/sexp.h b/sexp.h new file mode 100644 index 0000000..76905d5 --- /dev/null +++ b/sexp.h @@ -0,0 +1,99 @@ +#ifndef cmsg_sexp_h +#define cmsg_sexp_h + +typedef struct sexp_data_t_ { + refcount_t refcount; + cmsg_bytes_t data; +} sexp_data_t; + +typedef enum sexp_type_t_ { + SEXP_BYTES, + SEXP_SLICE, + SEXP_DISPLAY_HINT, + SEXP_PAIR +} sexp_type_t; + +typedef struct sexp_t_ { + refcount_t refcount; + sexp_type_t kind; + union { + cmsg_bytes_t bytes; + struct { + sexp_data_t *data; + size_t offset; + size_t length; + } slice; + struct { + struct sexp_t_ *head; + struct sexp_t_ *tail; + } pair; /* and display-hint */ + } data; +} sexp_t; + +extern void sexp_data_destructor(sexp_data_t *data); +extern void sexp_destructor(sexp_t *x); + +extern sexp_data_t *sexp_data_copy(cmsg_bytes_t body, size_t offset, size_t length); +extern sexp_data_t *sexp_data_alias(cmsg_bytes_t body); + +extern sexp_t *sexp_bytes(cmsg_bytes_t bytes); +extern sexp_t *sexp_slice(sexp_data_t *data, size_t offset, size_t length); +extern sexp_t *sexp_display_hint(sexp_t *hint, sexp_t *body); +extern sexp_t *sexp_cons(sexp_t *head, sexp_t *tail); + +#define sexp_simple_stringp(x) ({ \ + sexp_t *__x = (x); \ + (__x != NULL) && ((__x->kind == SEXP_BYTES) || (__x->kind == SEXP_SLICE)); \ + }) + +#define sexp_stringp(x) ({ \ + sexp_t *__x = (x); \ + sexp_simple_stringp(__x) || ((__x != NULL) && (__x->kind == SEXP_DISPLAY_HINT)); \ + } + +#define sexp_pairp(x) ({ \ + sexp_t *__x = (x); \ + (__x != NULL) && (__x->kind == SEXP_PAIR); \ + }) + +extern cmsg_bytes_t sexp_data(sexp_t *x); +#define sexp_head(x) ({sexp_t *__x = (x); assert(__x->kind == SEXP_PAIR); __x->data.pair.head;}) +#define sexp_tail(x) ({sexp_t *__x = (x); assert(__x->kind == SEXP_PAIR); __x->data.pair.tail;}) +#define sexp_hint(x) ({sexp_t *__x = (x); assert(__x->kind == SEXP_DISPLAY_HINT); __x->data.pair.head;}) +#define sexp_body(x) ({sexp_t *__x = (x); assert(__x->kind == SEXP_DISPLAY_HINT); __x->data.pair.tail;}) + +#define sexp_setter_(x,y,fieldname) \ + ({ \ + sexp_t *__x = (x); \ + sexp_t *__y = (y); \ + sexp_t *__old; \ + assert(__x->kind == SEXP_PAIR); \ + INCREF(__y); \ + __old = __x->data.pair.fieldname; \ + __x->data.pair.fieldname = __y; \ + DECREF(__old, sexp_destructor); \ + __x; \ + }) + +#define sexp_sethead(x,y) sexp_setter_(x,y,head) +#define sexp_settail(x,y) sexp_setter_(x,y,tail) + +#define sexp_push(stackvar,val) \ + ({ \ + sexp_t *__oldstack = stackvar; \ + stackvar = INCREF(sexp_cons((val), stackvar)); \ + DECREF(__oldstack, sexp_destructor); \ + stackvar; \ + }) + +#define sexp_pop(stackvar) \ + ({ \ + sexp_t *__nextstack = INCREF(sexp_tail(stackvar)); \ + sexp_t *__val = INCREF(sexp_head(stackvar)); \ + DECREF(stackvar, sexp_destructor); \ + stackvar = __nextstack; \ + UNGRAB(__val); \ + __val; \ + }) + +#endif diff --git a/sexpio.c b/sexpio.c new file mode 100644 index 0000000..c06d21b --- /dev/null +++ b/sexpio.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include + +#include + +#include + +#include "cmsg_private.h" +#include "ref.h" +#include "sexp.h" +#include "harness.h" +#include "sexpio.h" + +/* TODO: limit size of individual simple strings */ +/* TODO: limit nesting of sexps */ + +static sexp_t *read_simple_string(IOHandle *h, cmsg_bytes_t buf) { + int i = 0; + sexp_t *result; + + while (1) { + buf = iohandle_readwait(h, buf.len + 1); + if (h->error_kind) return NULL; + /* Don't reset i to zero: avoids scanning the beginning of the + number repeatedly */ + + while (i < buf.len) { + if (i > 10) { + /* More than ten digits of length prefix. We're unlikely to be + able to cope with anything that large. */ + h->error_kind = SEXP_ERROR_OVERFLOW; + return NULL; + } + if (buf.bytes[i] == ':') { + size_t count; + buf.bytes[i] = '\0'; + count = atoi((char *) buf.bytes); + iohandle_drain(h, i + 1); + buf = iohandle_readwait(h, count); + buf.len = count; + result = sexp_bytes(buf); + iohandle_drain(h, count); + return result; + } + + if (!isdigit(buf.bytes[i])) { + h->error_kind = SEXP_ERROR_SYNTAX; + return NULL; + } + + i++; + } + } +} + +#define CHECKH \ + if (h->error_kind) goto error; + +#define READ1 \ + buf = iohandle_readwait(h, 1); \ + CHECKH; + +sexp_t *sexp_read(IOHandle *h) { + cmsg_bytes_t buf; + sexp_t *stack = NULL; /* held */ + sexp_t *hint = NULL; /* held */ + sexp_t *body = NULL; /* held */ + sexp_t *accumulator = NULL; /* not held */ + + while (1) { + READ1; + switch (buf.bytes[0]) { + case '[': { + iohandle_drain(h, 1); + hint = INCREF(read_simple_string(h, EMPTY_BYTES)); + CHECKH; + READ1; + if (buf.bytes[0] != ']') { + h->error_kind = SEXP_ERROR_SYNTAX; + goto error; + } + iohandle_drain(h, 1); + body = INCREF(read_simple_string(h, EMPTY_BYTES)); + CHECKH; + accumulator = sexp_display_hint(hint, body); + DECREF(hint, sexp_destructor); /* these could be UNGRABs */ + DECREF(body, sexp_destructor); + break; + } + + case '(': + iohandle_drain(h, 1); + sexp_push(stack, sexp_cons(NULL, NULL)); + continue; + + case ')': { + sexp_t *current; + if (stack == NULL) { + h->error_kind = SEXP_ERROR_SYNTAX; + goto error; + } + current = sexp_pop(stack); + iohandle_drain(h, 1); + accumulator = INCREF(sexp_head(current)); + DECREF(current, sexp_destructor); + UNGRAB(accumulator); + break; + } + + default: + if (isspace(buf.bytes[0])) { + iohandle_drain(h, 1); + continue; + } + buf.len = 1; /* needed to avoid reading too much in read_simple_string */ + accumulator = read_simple_string(h, buf); + if (h->error_kind) goto error; + break; + } + + if (stack == NULL) { + return accumulator; + } else { + sexp_t *current = sexp_head(stack); /* not held */ + sexp_t *cell = sexp_cons(accumulator, NULL); + if (sexp_tail(current) == NULL) { + sexp_sethead(current, cell); + } else { + sexp_settail(sexp_tail(current), cell); + } + sexp_settail(current, cell); + } + } + + error: + DECREF(stack, sexp_destructor); + DECREF(hint, sexp_destructor); + DECREF(body, sexp_destructor); + return NULL; +} + +void write_simple_string(IOHandle *h, sexp_t *x) { + cmsg_bytes_t data = sexp_data(x); + char lenstr[16]; + snprintf(lenstr, sizeof(lenstr), "%u:", (unsigned int) data.len); + lenstr[sizeof(lenstr) - 1] = '\0'; + iohandle_write(h, cmsg_cstring_bytes(lenstr)); + iohandle_write(h, data); +} + +unsigned short sexp_write(IOHandle *h, sexp_t *x) { + sexp_t *stack = NULL; /* held */ + sexp_t *current = x; + + write1: + if (current == NULL) { + iohandle_write(h, cmsg_cstring_bytes("()")); + } else { + switch (current->kind) { + case SEXP_BYTES: + case SEXP_SLICE: + write_simple_string(h, current); + break; + + case SEXP_DISPLAY_HINT: + iohandle_write(h, cmsg_cstring_bytes("[")); + write_simple_string(h, sexp_hint(current)); + iohandle_write(h, cmsg_cstring_bytes("]")); + write_simple_string(h, sexp_body(current)); + break; + + case SEXP_PAIR: + iohandle_write(h, cmsg_cstring_bytes("(")); + sexp_push(stack, current); + break; + + default: + die("Unknown sexp kind %d in sexp_write\n", current->kind); + } + } + + check_stack: + if (stack == NULL) { + return 0; + } + + { + sexp_t *cell = sexp_head(stack); + if (cell == NULL) { + iohandle_write(h, cmsg_cstring_bytes(")")); + sexp_pop(stack); /* no need to worry about incref/decref: it's NULL! */ + goto check_stack; + } + + if (sexp_pairp(cell)) { + current = sexp_head(cell); + sexp_sethead(stack, sexp_tail(cell)); + goto write1; + } + + return SEXP_ERROR_SYNTAX; + } +} diff --git a/sexpio.h b/sexpio.h new file mode 100644 index 0000000..256b88a --- /dev/null +++ b/sexpio.h @@ -0,0 +1,10 @@ +#ifndef cmsg_sexpio_h +#define cmsg_sexpio_h + +#define SEXP_ERROR_OVERFLOW 0x8000 +#define SEXP_ERROR_SYNTAX 0x8001 + +extern sexp_t *sexp_read(IOHandle *h); +extern unsigned short sexp_write(IOHandle *h, sexp_t *x); + +#endif From 20ef8dfd46d93d50920ff2c175a0efe213e9a568 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 18:23:01 -0500 Subject: [PATCH 013/122] Permit whitespace between display hint and display body --- sexpio.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sexpio.c b/sexpio.c index c06d21b..150ef6a 100644 --- a/sexpio.c +++ b/sexpio.c @@ -82,6 +82,12 @@ sexp_t *sexp_read(IOHandle *h) { goto error; } iohandle_drain(h, 1); + skip_whitespace_in_display_hint: + READ1; + if (isspace(buf.bytes[0])) { + iohandle_drain(h, 1); + goto skip_whitespace_in_display_hint; + } body = INCREF(read_simple_string(h, EMPTY_BYTES)); CHECKH; accumulator = sexp_display_hint(hint, body); From 23cb773630b85aac31f52184b233b2f6cad30601 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 18:34:09 -0500 Subject: [PATCH 014/122] Add sexp_read_atom() --- sexpio.c | 4 ++++ sexpio.h | 1 + 2 files changed, 5 insertions(+) diff --git a/sexpio.c b/sexpio.c index 150ef6a..6128ca6 100644 --- a/sexpio.c +++ b/sexpio.c @@ -55,6 +55,10 @@ static sexp_t *read_simple_string(IOHandle *h, cmsg_bytes_t buf) { } } +sexp_t *sexp_read_atom(IOHandle *h) { + return read_simple_string(h, EMPTY_BYTES); +} + #define CHECKH \ if (h->error_kind) goto error; diff --git a/sexpio.h b/sexpio.h index 256b88a..28b5955 100644 --- a/sexpio.h +++ b/sexpio.h @@ -4,6 +4,7 @@ #define SEXP_ERROR_OVERFLOW 0x8000 #define SEXP_ERROR_SYNTAX 0x8001 +extern sexp_t *sexp_read_atom(IOHandle *h); extern sexp_t *sexp_read(IOHandle *h); extern unsigned short sexp_write(IOHandle *h, sexp_t *x); From 17edf15cbd453ad01945598ce602e5df92d6d3e9 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 23:55:15 -0500 Subject: [PATCH 015/122] Add hashtable_contains() --- hashtable.c | 5 +++++ hashtable.h | 1 + 2 files changed, 6 insertions(+) diff --git a/hashtable.c b/hashtable.c index 813024c..07a700b 100644 --- a/hashtable.c +++ b/hashtable.c @@ -79,6 +79,11 @@ static hashtable_entry_t **hashtable_find(hashtable_t *table, cmsg_bytes_t key) return entryptr; } +int hashtable_contains(hashtable_t *table, cmsg_bytes_t key) { + hashtable_entry_t **entryptr = hashtable_find(table, key); + return (*entryptr != NULL); +} + int hashtable_get(hashtable_t *table, cmsg_bytes_t key, void **valueptr) { hashtable_entry_t **entryptr = hashtable_find(table, key); if (*entryptr == NULL) { diff --git a/hashtable.h b/hashtable.h index 8d36774..e8ff63f 100644 --- a/hashtable.h +++ b/hashtable.h @@ -25,6 +25,7 @@ extern void init_hashtable(hashtable_t *table, void (*free_value)(void *)); extern void destroy_hashtable(hashtable_t *table); +extern int hashtable_contains(hashtable_t *table, cmsg_bytes_t key); extern int hashtable_get(hashtable_t *table, cmsg_bytes_t key, void **valueptr); extern int hashtable_put(hashtable_t *table, cmsg_bytes_t key, void *value); extern int hashtable_erase(hashtable_t *table, cmsg_bytes_t key); From a3ebf4df378132aca3fd0429f71528e4e6930744 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 23:56:31 -0500 Subject: [PATCH 016/122] Steps toward node, factory and relay functionality. --- Makefile | 2 +- main.c | 25 ++++++++++++ node.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ node.h | 30 ++++++++++++-- relay.c | 87 ++++++++++++++++++++++++++------------- 5 files changed, 232 insertions(+), 33 deletions(-) create mode 100644 node.c diff --git a/Makefile b/Makefile index 0175cde..53012aa 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TARGET = cmsg -OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o +OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g #CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 diff --git a/main.c b/main.c index ef7b606..ce4359f 100644 --- a/main.c +++ b/main.c @@ -13,11 +13,36 @@ typedef unsigned char u_char; #include "cmsg_private.h" #include "harness.h" #include "net.h" +#include "ref.h" +#include "sexp.h" +#include "hashtable.h" +#include "node.h" + +static node_t *factory_construct(node_class_t *nc, sexp_t *args) { + return malloc(sizeof(node_t)); +} + +static void factory_handle_message(node_t *n, sexp_t *m) { + info("factory_handle_message\n"); +} + +static node_class_t factory_class = { + .name = "factory", + .construct = factory_construct, + .destroy = (node_destructor_fn_t) free, + .handle_message = factory_handle_message +}; + +static void init_factory(void) { + bind_node(cmsg_cstring_bytes("factory"), new_node(&factory_class, NULL)); +} int main(int argc, char *argv[]) { info("cmsg, Copyright (C) 2010 Tony Garnock-Jones. All rights reserved.\n"); event_init(); info("Using libevent version %s\n", event_get_version()); + init_node(); + init_factory(); start_net(5671); boot_harness(); return 0; diff --git a/node.c b/node.c new file mode 100644 index 0000000..ad139e3 --- /dev/null +++ b/node.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +#include + +#include "cmsg_private.h" +#include "ref.h" +#include "sexp.h" +#include "hashtable.h" +#include "node.h" + +static hashtable_t node_class_table; +static hashtable_t directory; + +static void *node_incref(void *arg) { + return INCREF((node_t *) arg); +} + +static void node_decref(void *arg) { + DECREF((node_t *) arg, node_destructor); +} + +void init_node(void) { + init_hashtable(&node_class_table, + 31, + NULL, + NULL); + init_hashtable(&directory, + 10007, + node_incref, + node_decref); +} + +void register_node_class(node_class_t *nc) { + cmsg_bytes_t key = cmsg_cstring_bytes(nc->name); + if (hashtable_contains(&node_class_table, key)) { + die("Duplicate node class name %s\n", nc->name); + } + hashtable_put(&node_class_table, key, nc); +} + +node_class_t *lookup_node_class(cmsg_bytes_t name) { + node_class_t *nc = NULL; + hashtable_get(&node_class_table, name, (void **) &nc); + return nc; +} + +node_t *new_node(node_class_t *nc, sexp_t *args) { + node_t *n = nc->construct(nc, args); + n->refcount = ZERO_REFCOUNT(); + n->node_class = nc; + init_hashtable(&n->names, 5, NULL, NULL); + return n; +} + +void unbind_on_destroy(void *context, cmsg_bytes_t key, void *value) { + unbind_node(key); +} + +void node_destructor(node_t *n) { + hashtable_foreach(&n->names, unbind_on_destroy, NULL); + destroy_hashtable(&n->names); + n->node_class->destroy(n); +} + +node_t *lookup_node(cmsg_bytes_t name) { + node_t *n = NULL; + hashtable_get(&directory, name, (void **) &n); + return n; +} + +int bind_node(cmsg_bytes_t name, node_t *n) { + if (hashtable_contains(&directory, name)) { + return 0; + } + hashtable_put(&directory, name, n); + hashtable_put(&n->names, name, NULL); + return 1; +} + +int unbind_node(cmsg_bytes_t name) { + node_t *n = NULL; + hashtable_get(&directory, name, (void **) &n); + if (n == NULL) { + return 0; + } else { + hashtable_erase(&n->names, name); + hashtable_erase(&directory, name); + return 1; + } +} + +int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body) { + static sexp_t *post_atom = NULL; + sexp_t *msg = NULL; + int result; + + if (post_atom == NULL) { + post_atom = INCREF(sexp_bytes(cmsg_cstring_bytes("post"))); + } + + msg = sexp_cons(body, msg); + msg = sexp_cons(sexp_bytes(name), msg); + msg = sexp_cons(post_atom, msg); + INCREF(msg); + result = send_node(node, msg); + DECREF(msg, sexp_destructor); + + return result; +} + +int send_node(cmsg_bytes_t node, sexp_t *message) { + node_t *n = lookup_node(node); + if (n == NULL) { + return 0; + } + n->node_class->handle_message(n, message); + return 1; +} diff --git a/node.h b/node.h index 6c42456..5ed6106 100644 --- a/node.h +++ b/node.h @@ -2,15 +2,37 @@ #define cmsg_node_h typedef struct node_t_ { + refcount_t refcount; struct node_class_t_ *node_class; - cmsg_bytes_t name; /* used as (partial) routing key for metamessages */ + hashtable_t names; } node_t; +typedef node_t *(*node_constructor_fn_t)(struct node_class_t_ *nc, sexp_t *args); +typedef void (*node_destructor_fn_t)(node_t *n); +typedef void (*node_message_handler_fn_t)(node_t *n, sexp_t *m); + typedef struct node_class_t_ { - void (*destroy)(node_t *n); - void (*handle_message)(node_t *n, msg_t *m); + char const *name; + node_constructor_fn_t construct; + node_destructor_fn_t destroy; + node_message_handler_fn_t handle_message; } node_class_t; -extern node_t *new_node( +extern void init_node(void); + +extern void basic_node_destroy(node_t *n); + +extern void register_node_class(node_class_t *nc); +extern node_class_t *lookup_node_class(cmsg_bytes_t name); + +extern node_t *new_node(node_class_t *nc, sexp_t *args); +extern void node_destructor(node_t *n); + +extern node_t *lookup_node(cmsg_bytes_t name); +extern int bind_node(cmsg_bytes_t name, node_t *n); +extern int unbind_node(cmsg_bytes_t name); + +extern int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body); +extern int send_node(cmsg_bytes_t node, sexp_t *message); #endif diff --git a/relay.c b/relay.c index 4ac3de0..5d240cf 100644 --- a/relay.c +++ b/relay.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -31,9 +32,23 @@ struct boot_args { int fd; }; +static void send_sexp_syntax_error(IOHandle *h, char const *message) { + sexp_t *m = NULL; + m = sexp_cons(sexp_bytes(cmsg_cstring_bytes("http://people.csail.mit.edu/rivest/Sexp.txt")), m); + m = sexp_cons(sexp_bytes(cmsg_cstring_bytes(message)), m); + m = sexp_cons(sexp_bytes(cmsg_cstring_bytes("error")), m); + INCREF(m); + + iohandle_clear_error(h); + ICHECK(sexp_write(h, m), "send_sexp_syntax_error sexp_write"); + DECREF(m, sexp_destructor); + iohandle_flush(h); /* ignore result here, there's not much we can do with it */ +} + static void relay_main(struct boot_args *args) { IOHandle *h = new_iohandle(args->fd); - IOHandle *out = new_iohandle(1); + IOHandle *out_handle = new_iohandle(1); + sexp_t *message = NULL; /* held */ { char name[256]; @@ -43,41 +58,57 @@ static void relay_main(struct boot_args *args) { free(args); - iohandle_write(h, cmsg_cstring_bytes("Hi\n")); + iohandle_write(h, cmsg_cstring_bytes("(3:hop1:0)")); ICHECK(iohandle_flush(h), "iohandle_flush 1"); - nap(1000); - iohandle_write(h, cmsg_cstring_bytes("Proceed\n")); + //iohandle_settimeout(h, 3, 0); - loop: - { - sexp_t *x = sexp_read(h); - switch (h->error_kind) { - case 0: - fflush(NULL); - sexp_write(out, x); - iohandle_write(out, cmsg_cstring_bytes("\n")); - ICHECK(iohandle_flush(out), "iohandle_flush out"); - DECREF(x, sexp_destructor); - iohandle_write(h, cmsg_cstring_bytes("OK, proceed\n")); - goto loop; + while (1) { + DECREF(message, sexp_destructor); + message = NULL; + message = INCREF(sexp_read(h)); - case EVBUFFER_TIMEOUT: - info("Timeout\n"); - iohandle_clear_error(h); - iohandle_write(h, cmsg_cstring_bytes("Timed out\n")); - ICHECK(iohandle_flush(h), "iohandle_flush 2"); - break; + if (h->error_kind != 0) goto handle_error; - default: - info("Error! 0x%04X\n", h->error_kind); - break; - } + fflush(NULL); + sexp_write(out_handle, message); + iohandle_write(out_handle, cmsg_cstring_bytes("\n")); + ICHECK(iohandle_flush(out_handle), "iohandle_flush out_handle"); + + iohandle_write(h, cmsg_cstring_bytes("OK, proceed\n")); } - ICHECK(close(h->fd), "close"); + handle_error: + switch (h->error_kind) { + case EVBUFFER_EOF: + info("Disconnecting fd %d normally.\n", h->fd); + break; + + case SEXP_ERROR_OVERFLOW: + send_sexp_syntax_error(h, "sexp too big"); + break; + + case SEXP_ERROR_SYNTAX: + send_sexp_syntax_error(h, "sexp syntax error"); + break; + + default: + warn("Relay handle error on fd %d: 0x%04X\n", h->fd, h->error_kind); + break; + } + goto cleanup; + + cleanup: + goto closedown; + + closedown: + if (close(h->fd) == -1) { + /* log errors as warnings here and keep on trucking */ + warn("Closing file descriptor %d produced errno %d: %s\n", + h->fd, errno, strerror(errno)); + } delete_iohandle(h); - delete_iohandle(out); + delete_iohandle(out_handle); } void start_relay(struct sockaddr_in const *peername, int fd) { From dff260f74ad4bd26dac9b48655421ddab92da7b3 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 29 Dec 2010 23:58:17 -0500 Subject: [PATCH 017/122] Fix check on result of sexp_write() --- relay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay.c b/relay.c index 5d240cf..b2a9838 100644 --- a/relay.c +++ b/relay.c @@ -40,7 +40,7 @@ static void send_sexp_syntax_error(IOHandle *h, char const *message) { INCREF(m); iohandle_clear_error(h); - ICHECK(sexp_write(h, m), "send_sexp_syntax_error sexp_write"); + BCHECK(!sexp_write(h, m), "send_sexp_syntax_error sexp_write"); DECREF(m, sexp_destructor); iohandle_flush(h); /* ignore result here, there's not much we can do with it */ } From 420d53c511f6fcb866d8a7a205113d84960de02a Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 30 Dec 2010 00:30:54 -0500 Subject: [PATCH 018/122] Add interrupt_harness() --- harness.c | 7 +++++++ harness.h | 1 + 2 files changed, 8 insertions(+) diff --git a/harness.c b/harness.c index 0f9b691..3339c18 100644 --- a/harness.c +++ b/harness.c @@ -33,6 +33,7 @@ typedef unsigned char u_char; # error Define STACK_SIZE for your platform. It should probably not be less than 32k? #endif +static volatile int harness_running = 1; Process *current_process = NULL; #define EMPTY_PROCESS_QUEUE ((queue_t) { offsetof(Process, link), 0, NULL, NULL }) @@ -234,7 +235,13 @@ void boot_harness(void) { //info("Polling for events\n"); event_loop(EVLOOP_NONBLOCK); } + if (!harness_running) break; //info("Blocking for events\n"); event_loop(EVLOOP_ONCE); } } + +void interrupt_harness(void) { + info("Interrupting harness\n"); + harness_running = 0; +} diff --git a/harness.h b/harness.h index 7f30309..7926e0b 100644 --- a/harness.h +++ b/harness.h @@ -41,5 +41,6 @@ extern int iohandle_flush(IOHandle *h); extern void iohandle_settimeout(IOHandle *h, int timeout_read, int timeout_write); extern void boot_harness(void); +extern void interrupt_harness(void); #endif From 66c19fea96d25ca0cfae5f307449ce0aeb7f23ff Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 30 Dec 2010 00:31:25 -0500 Subject: [PATCH 019/122] Valgrind found this uninitialized block of memory --- hashtable.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hashtable.c b/hashtable.c index 07a700b..338e5bd 100644 --- a/hashtable.c +++ b/hashtable.c @@ -36,8 +36,7 @@ void init_hashtable(hashtable_t *table, table->free_value = free_value; if (initial_bucket_count > 0) { - table->buckets = realloc(table->buckets, - initial_bucket_count * sizeof(hashtable_entry_t *)); + table->buckets = calloc(initial_bucket_count, sizeof(hashtable_entry_t *)); } } From ef32c01e674d305d8e744f4c93e21e0f89b71e93 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 30 Dec 2010 00:32:29 -0500 Subject: [PATCH 020/122] Add release_sexp_cache() to help valgrind out; call it on EOF --- main.c | 15 +++++++++++++++ sexp.c | 11 +++++++++++ sexp.h | 2 ++ 3 files changed, 28 insertions(+) diff --git a/main.c b/main.c index ce4359f..0a73cab 100644 --- a/main.c +++ b/main.c @@ -37,13 +37,28 @@ static void init_factory(void) { bind_node(cmsg_cstring_bytes("factory"), new_node(&factory_class, NULL)); } +static void console_listener(void *arg) { + IOHandle *in_handle = new_iohandle(0); + while (1) { + cmsg_bytes_t buf = iohandle_readwait(in_handle, 1); + if (in_handle->error_kind) break; + iohandle_drain(in_handle, buf.len); + } + delete_iohandle(in_handle); + interrupt_harness(); +} + int main(int argc, char *argv[]) { info("cmsg, Copyright (C) 2010 Tony Garnock-Jones. All rights reserved.\n"); event_init(); info("Using libevent version %s\n", event_get_version()); init_node(); init_factory(); + spawn(console_listener, NULL); start_net(5671); boot_harness(); +#ifndef NDEBUG + release_sexp_cache(); +#endif return 0; } diff --git a/sexp.c b/sexp.c index bc28820..ca0ad79 100644 --- a/sexp.c +++ b/sexp.c @@ -10,6 +10,17 @@ static sexp_t *freelist = NULL; +void release_sexp_cache(void) { + int count = 0; + while (freelist != NULL) { + sexp_t *x = freelist; + freelist = x->data.pair.tail; + free(x); + count++; + } + info("Released %d cached sexp shells.\n", count); +} + static inline sexp_t *alloc_shell(sexp_type_t kind) { sexp_t *x = freelist; if (x == NULL) { diff --git a/sexp.h b/sexp.h index 76905d5..21527fa 100644 --- a/sexp.h +++ b/sexp.h @@ -30,6 +30,8 @@ typedef struct sexp_t_ { } data; } sexp_t; +extern void release_sexp_cache(void); + extern void sexp_data_destructor(sexp_data_t *data); extern void sexp_destructor(sexp_t *x); From 0f57884762a12866d4343d152e5eefd2c3754be2 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 30 Dec 2010 00:34:18 -0500 Subject: [PATCH 021/122] Fix refcount error --- sexpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sexpio.c b/sexpio.c index 6128ca6..9373e2a 100644 --- a/sexpio.c +++ b/sexpio.c @@ -111,7 +111,7 @@ sexp_t *sexp_read(IOHandle *h) { h->error_kind = SEXP_ERROR_SYNTAX; goto error; } - current = sexp_pop(stack); + current = INCREF(sexp_pop(stack)); iohandle_drain(h, 1); accumulator = INCREF(sexp_head(current)); DECREF(current, sexp_destructor); From e491109d9898915ee6b83cbbd2e9ac0cbee86975 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 30 Dec 2010 13:32:50 -0500 Subject: [PATCH 022/122] Analyze source dependencies in Makefile --- .gitignore | 1 + Makefile | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 3f0df94..2c0831e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ scratch/ *.o cmsg +depend.mk diff --git a/Makefile b/Makefile index 53012aa..58a5067 100644 --- a/Makefile +++ b/Makefile @@ -16,3 +16,9 @@ clean: rm -f $(TARGET) rm -f $(OBJECTS) rm -rf *.dSYM + rm -f depend.mk + +depend.mk: + gcc $(CFLAGS) -M *.c > $@ + echo "depend.mk:" Makefile *.c >> $@ +-include depend.mk From a2fabf160552344bf75c4c3b46da866c53891088 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 31 Dec 2010 17:26:39 -0500 Subject: [PATCH 023/122] Make IOHandles shareable --- harness.c | 55 +++++++++++++++++++++++++++++++------------------------ harness.h | 2 +- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/harness.c b/harness.c index 3339c18..861e756 100644 --- a/harness.c +++ b/harness.c @@ -115,39 +115,44 @@ void nap(long millis) { schedule(); } +static void awaken_waiters(IOHandle *h, short mask) { + Process *prev = NULL; + Process *p; + Process *next; + for (p = h->waiters; p != NULL; p = next) { + next = p->link; + assert(p->state == PROCESS_WAITING); + if ((p->wait_flags & mask) != 0) { + if (prev == NULL) { + h->waiters = next; + } else { + prev->link = next; + } + enqueue_runlist(p); + } else { + prev = p; + } + } +} + static void input_isr(struct bufferevent *bufev, IOHandle *h) { - //info("input_isr %p r %d w %d\n", h->p, EVBUFFER_LENGTH(bufev->input), EVBUFFER_LENGTH(bufev->output)); - assert((h->p->state == PROCESS_WAITING) && (h->p->wait_flags & EV_READ)); - enqueue_runlist(h->p); + awaken_waiters(h, EV_READ); } static void output_isr(struct bufferevent *bufev, IOHandle *h) { - //info("output_isr %p r %d w %d\n", h->p, EVBUFFER_LENGTH(bufev->input), EVBUFFER_LENGTH(bufev->output)); - if ((h->p->state == PROCESS_WAITING) && (h->p->wait_flags & EV_WRITE)) { - enqueue_runlist(h->p); - } + awaken_waiters(h, EV_WRITE); } static void error_isr(struct bufferevent *bufev, short what, IOHandle *h) { - info("error_isr 0x%04X %p\n", what, h->p); + info("error_isr 0x%04X\n", what); h->error_direction = what & (EVBUFFER_READ | EVBUFFER_WRITE); h->error_kind = what & ~(EVBUFFER_READ | EVBUFFER_WRITE); - if ((h->p->state == PROCESS_WAITING) - && (h->p->wait_flags & (EV_READ | EV_WRITE))) { - enqueue_runlist(h->p); - } else { - warn("Um, not sure what to do here. Error what %d, fd %d, ioh %p process %p\n", - what, - bufev->ev_read.ev_fd, - h, - h->p); - } + awaken_waiters(h, EV_READ | EV_WRITE); } IOHandle *new_iohandle(int fd) { IOHandle *h = malloc(sizeof(*h)); - assert(current_process != NULL); - h->p = current_process; + h->waiters = NULL; h->fd = fd; h->io = bufferevent_new(fd, (evbuffercb) input_isr, @@ -171,11 +176,13 @@ void iohandle_clear_error(IOHandle *h) { } static void block_on_io(IOHandle *h, short event) { - assert(current_process == h->p); - h->p->state = PROCESS_WAITING; - h->p->wait_flags |= event; + assert(current_process->link == NULL); + current_process->link = h->waiters; + h->waiters = current_process; + current_process->state = PROCESS_WAITING; + current_process->wait_flags |= event; schedule(); - h->p->wait_flags &= ~event; + current_process->wait_flags &= ~event; } cmsg_bytes_t iohandle_readwait(IOHandle *h, size_t at_least) { diff --git a/harness.h b/harness.h index 7926e0b..f171f52 100644 --- a/harness.h +++ b/harness.h @@ -18,7 +18,7 @@ typedef struct Process { } Process; typedef struct IOHandle { - Process *p; + Process *waiters; int fd; struct bufferevent *io; unsigned short error_direction; From 53609faa820178f15a20afee7232af2d26d626d4 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 31 Dec 2010 17:26:54 -0500 Subject: [PATCH 024/122] Correct syntax error in sexp_string --- sexp.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sexp.h b/sexp.h index 21527fa..c6d41f1 100644 --- a/sexp.h +++ b/sexp.h @@ -49,9 +49,9 @@ extern sexp_t *sexp_cons(sexp_t *head, sexp_t *tail); }) #define sexp_stringp(x) ({ \ - sexp_t *__x = (x); \ - sexp_simple_stringp(__x) || ((__x != NULL) && (__x->kind == SEXP_DISPLAY_HINT)); \ - } + sexp_t *__x = (x); \ + sexp_simple_stringp(__x) || ((__x != NULL) && (__x->kind == SEXP_DISPLAY_HINT)); \ + }) #define sexp_pairp(x) ({ \ sexp_t *__x = (x); \ From ce5de71e11367fda970bb3d3890b77480bbea159 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 10:15:01 -0500 Subject: [PATCH 025/122] sexp_assoc --- sexp.c | 15 +++++++++++++++ sexp.h | 2 ++ 2 files changed, 17 insertions(+) diff --git a/sexp.c b/sexp.c index ca0ad79..46d8635 100644 --- a/sexp.c +++ b/sexp.c @@ -130,3 +130,18 @@ cmsg_bytes_t sexp_data(sexp_t *x) { die("Unknown sexp kind %d in data accessor\n", x->kind); } } + +sexp_t *sexp_assoc(sexp_t *list, cmsg_bytes_t key) { + while (list != NULL) { + sexp_t *candidate = sexp_head(list); + if (sexp_stringp(candidate)) { + cmsg_bytes_t candidate_data = sexp_data(candidate); + if ((candidate_data.len == key.len) + && (!memcmp(candidate_data.bytes, key.bytes, key.len))) { + return candidate; + } + } + list = sexp_tail(list); + } + return NULL; +} diff --git a/sexp.h b/sexp.h index c6d41f1..cfcbc40 100644 --- a/sexp.h +++ b/sexp.h @@ -98,4 +98,6 @@ extern cmsg_bytes_t sexp_data(sexp_t *x); __val; \ }) +extern sexp_t *sexp_assoc(sexp_t *list, cmsg_bytes_t key); + #endif From a28ddbe9aa27c6f9f241875e4e6fe992d5dbead2 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 11:29:29 -0500 Subject: [PATCH 026/122] Configs for static linking --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 58a5067..40f7ba8 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,13 @@ OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpi CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g #CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 +#CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 -static all: $(TARGET) $(TARGET): $(OBJECTS) $(CC) $(CFLAGS) -o $@ $(OBJECTS) -levent +# $(CC) $(CFLAGS) -o $@ $(OBJECTS) -levent -lrt %.o: %.c $(CC) $(CFLAGS) -c $< From 7a85e75b3def6b5ed5cf21bad540299835360635 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 17:29:38 -0500 Subject: [PATCH 027/122] Relay node implementation; inlineize sexp macros --- cmsg_private.h | 1 + main.c | 8 +++ node.c | 14 +++-- node.h | 1 + relay.c | 167 ++++++++++++++++++++++++++++++++++++------------- sexp.c | 9 +++ sexp.h | 103 ++++++++++++++++-------------- sexpio.c | 9 +-- util.c | 6 ++ 9 files changed, 221 insertions(+), 97 deletions(-) diff --git a/cmsg_private.h b/cmsg_private.h index 7add267..e70c4f2 100644 --- a/cmsg_private.h +++ b/cmsg_private.h @@ -16,6 +16,7 @@ extern cmsg_bytes_t cmsg_cstring_bytes(char const *cstr); extern cmsg_bytes_t cmsg_bytes_malloc_dup(cmsg_bytes_t src); extern cmsg_bytes_t cmsg_bytes_malloc(size_t amount); extern void cmsg_bytes_free(cmsg_bytes_t bytes); +extern int cmsg_bytes_cmp(cmsg_bytes_t a, cmsg_bytes_t b); #define ICHECK(result, message) do { if ((result) == -1) { perror(message); exit(2); } } while (0) #define BCHECK(result, message) do { if ((result) == 0) { perror(message); exit(2); } } while (0) diff --git a/main.c b/main.c index 0a73cab..519c7de 100644 --- a/main.c +++ b/main.c @@ -7,6 +7,8 @@ #include #include +#include + typedef unsigned char u_char; #include @@ -18,6 +20,8 @@ typedef unsigned char u_char; #include "hashtable.h" #include "node.h" +#define WANT_CONSOLE_LISTENER 1 + static node_t *factory_construct(node_class_t *nc, sexp_t *args) { return malloc(sizeof(node_t)); } @@ -37,6 +41,7 @@ static void init_factory(void) { bind_node(cmsg_cstring_bytes("factory"), new_node(&factory_class, NULL)); } +#if WANT_CONSOLE_LISTENER static void console_listener(void *arg) { IOHandle *in_handle = new_iohandle(0); while (1) { @@ -47,6 +52,7 @@ static void console_listener(void *arg) { delete_iohandle(in_handle); interrupt_harness(); } +#endif int main(int argc, char *argv[]) { info("cmsg, Copyright (C) 2010 Tony Garnock-Jones. All rights reserved.\n"); @@ -54,7 +60,9 @@ int main(int argc, char *argv[]) { info("Using libevent version %s\n", event_get_version()); init_node(); init_factory(); +#if WANT_CONSOLE_LISTENER spawn(console_listener, NULL); +#endif start_net(5671); boot_harness(); #ifndef NDEBUG diff --git a/node.c b/node.c index ad139e3..14f11fb 100644 --- a/node.c +++ b/node.c @@ -55,12 +55,8 @@ node_t *new_node(node_class_t *nc, sexp_t *args) { return n; } -void unbind_on_destroy(void *context, cmsg_bytes_t key, void *value) { - unbind_node(key); -} - void node_destructor(node_t *n) { - hashtable_foreach(&n->names, unbind_on_destroy, NULL); + unbind_all_names_for_node(n); destroy_hashtable(&n->names); n->node_class->destroy(n); } @@ -92,6 +88,14 @@ int unbind_node(cmsg_bytes_t name) { } } +static void unbind_on_destroy(void *context, cmsg_bytes_t key, void *value) { + unbind_node(key); +} + +void unbind_all_names_for_node(node_t *n) { + hashtable_foreach(&n->names, unbind_on_destroy, NULL); +} + int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body) { static sexp_t *post_atom = NULL; sexp_t *msg = NULL; diff --git a/node.h b/node.h index 5ed6106..ddbf88e 100644 --- a/node.h +++ b/node.h @@ -31,6 +31,7 @@ extern void node_destructor(node_t *n); extern node_t *lookup_node(cmsg_bytes_t name); extern int bind_node(cmsg_bytes_t name, node_t *n); extern int unbind_node(cmsg_bytes_t name); +extern void unbind_all_names_for_node(node_t *n); extern int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body); extern int send_node(cmsg_bytes_t node, sexp_t *message); diff --git a/relay.c b/relay.c index b2a9838..e1601ad 100644 --- a/relay.c +++ b/relay.c @@ -16,6 +16,8 @@ #include #include +#include + typedef unsigned char u_char; #include @@ -26,94 +28,175 @@ typedef unsigned char u_char; #include "ref.h" #include "sexp.h" #include "sexpio.h" +#include "hashtable.h" +#include "node.h" -struct boot_args { +struct relay_node { + node_t node; struct sockaddr_in peername; + char peername_str[256]; int fd; + IOHandle *outh; }; -static void send_sexp_syntax_error(IOHandle *h, char const *message) { - sexp_t *m = NULL; - m = sexp_cons(sexp_bytes(cmsg_cstring_bytes("http://people.csail.mit.edu/rivest/Sexp.txt")), m); +static node_t *relay_construct(node_class_t *nc, sexp_t *args) { + /* TODO: outbound connections; args==NULL -> server relay, nonNULL -> outbound. */ + struct relay_node *r = calloc(1, sizeof(*r)); + return (node_t *) r; +} + +static void relay_destructor(node_t *n) { + struct relay_node *r = (struct relay_node *) n; + delete_iohandle(r->outh); + r->outh = NULL; + if (close(r->fd) == -1) { + /* log errors as warnings here and keep on trucking */ + warn("Closing file descriptor %d produced errno %d: %s\n", + r->fd, errno, strerror(errno)); + } + free(n); +} + +static void relay_handle_message(node_t *n, sexp_t *m) { + struct relay_node *r = (struct relay_node *) n; + BCHECK(!sexp_write(r->outh, m), "relay_handle_message sexp_write"); +} + +static node_class_t relay_class = { + .name = "relay", + .construct = relay_construct, + .destroy = relay_destructor, + .handle_message = relay_handle_message +}; + +static void send_error(IOHandle *h, char const *message, sexp_t *extra) { + sexp_t *m = extra; m = sexp_cons(sexp_bytes(cmsg_cstring_bytes(message)), m); m = sexp_cons(sexp_bytes(cmsg_cstring_bytes("error")), m); INCREF(m); iohandle_clear_error(h); - BCHECK(!sexp_write(h, m), "send_sexp_syntax_error sexp_write"); + BCHECK(!sexp_write(h, m), "send_error sexp_write"); DECREF(m, sexp_destructor); iohandle_flush(h); /* ignore result here, there's not much we can do with it */ } -static void relay_main(struct boot_args *args) { - IOHandle *h = new_iohandle(args->fd); - IOHandle *out_handle = new_iohandle(1); +static void send_sexp_syntax_error(IOHandle *h, char const *message) { + char const *url = "http://people.csail.mit.edu/rivest/Sexp.txt"; + send_error(h, message, sexp_cons(sexp_bytes(cmsg_cstring_bytes(url)), NULL)); +} + +static void relay_main(struct relay_node *r) { + IOHandle *inh = new_iohandle(r->fd); sexp_t *message = NULL; /* held */ - { - char name[256]; - endpoint_name(&args->peername, CMSG_BYTES(sizeof(name), name)); - info("Accepted connection from %s on fd %d\n", name, args->fd); - } + assert((void *) &r->node == (void *) r); - free(args); + INCREF(&r->node); /* because the caller doesn't hold a ref, and we + need to drop ours on our death */ - iohandle_write(h, cmsg_cstring_bytes("(3:hop1:0)")); - ICHECK(iohandle_flush(h), "iohandle_flush 1"); + info("Accepted connection from %s on fd %d\n", r->peername_str, r->fd); - //iohandle_settimeout(h, 3, 0); + iohandle_write(r->outh, cmsg_cstring_bytes("(3:hop1:0)")); + ICHECK(iohandle_flush(r->outh), "iohandle_flush greeting"); + + //iohandle_settimeout(r->inh, 3, 0); while (1) { DECREF(message, sexp_destructor); message = NULL; - message = INCREF(sexp_read(h)); + message = INCREF(sexp_read(inh)); - if (h->error_kind != 0) goto handle_error; + if (inh->error_kind != 0) goto network_error; + /* Log received message to console */ + /* fflush(NULL); sexp_write(out_handle, message); iohandle_write(out_handle, cmsg_cstring_bytes("\n")); ICHECK(iohandle_flush(out_handle), "iohandle_flush out_handle"); + */ - iohandle_write(h, cmsg_cstring_bytes("OK, proceed\n")); + if (!(sexp_pairp(message) && sexp_stringp(sexp_head(message)))) { + send_error(r->outh, "ill-formed message", NULL); + goto protocol_error; + } + + { + cmsg_bytes_t selector = sexp_data(sexp_head(message)); + sexp_t *args = sexp_tail(message); + size_t msglen = sexp_length(message); + + /* TODO: have constant atoms for post, subscribe, unsubscribe etc (see also post_node()) */ + if (!cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post")) && (msglen == 4) + && sexp_stringp(sexp_head(args))) { + cmsg_bytes_t nodename = sexp_data(sexp_head(args)); + if (!send_node(nodename, sexp_head(sexp_tail(args)))) { + warn("Was asked to post to unknown node <<%.*s>>\n", nodename.len, nodename.bytes); + } + } else if (!cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe")) && (msglen == 6) + && sexp_stringp(sexp_head(args)) + && sexp_stringp(sexp_head(sexp_tail(sexp_tail(sexp_tail(args))))) + && sexp_stringp(sexp_head(sexp_tail(sexp_tail(sexp_tail(sexp_tail(args))))))) { + sexp_t *filter_sexp = sexp_head(args); + cmsg_bytes_t filter = sexp_data(filter_sexp); + sexp_t *reply_sink_and_name = sexp_tail(sexp_tail(sexp_tail(args))); + cmsg_bytes_t reply_sink = sexp_data(sexp_head(reply_sink_and_name)); + cmsg_bytes_t reply_name = sexp_data(sexp_head(sexp_tail(reply_sink_and_name))); + if (bind_node(filter, &r->node)) { + sexp_t *subok = sexp_cons(sexp_bytes(cmsg_cstring_bytes("subscribe-ok")), + sexp_cons(filter_sexp, NULL)); + INCREF(subok); + post_node(reply_sink, reply_name, subok); + DECREF(subok, sexp_destructor); + } else { + warn("Bind failed <<%.*s>>\n", filter.len, filter.bytes); + } + } else if (!cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe")) && (msglen == 2) + && sexp_stringp(sexp_head(args))) { + sexp_t *id_sexp = sexp_head(args); + cmsg_bytes_t id = sexp_data(id_sexp); + if (!unbind_node(id)) { + warn("Unbind failed <<%.*s>>\n", id.len, id.bytes); + } + } else { + send_error(r->outh, "message not understood", message); + goto protocol_error; + } + } } - handle_error: - switch (h->error_kind) { + network_error: + switch (inh->error_kind) { case EVBUFFER_EOF: - info("Disconnecting fd %d normally.\n", h->fd); + info("Disconnecting fd %d normally.\n", r->fd); break; case SEXP_ERROR_OVERFLOW: - send_sexp_syntax_error(h, "sexp too big"); + send_sexp_syntax_error(r->outh, "sexp too big"); break; case SEXP_ERROR_SYNTAX: - send_sexp_syntax_error(h, "sexp syntax error"); + send_sexp_syntax_error(r->outh, "sexp syntax error"); break; default: - warn("Relay handle error on fd %d: 0x%04X\n", h->fd, h->error_kind); + warn("Relay handle error on fd %d: 0x%04X\n", r->fd, inh->error_kind); break; } - goto cleanup; - cleanup: - goto closedown; - - closedown: - if (close(h->fd) == -1) { - /* log errors as warnings here and keep on trucking */ - warn("Closing file descriptor %d produced errno %d: %s\n", - h->fd, errno, strerror(errno)); - } - delete_iohandle(h); - delete_iohandle(out_handle); + protocol_error: + DECREF(message, sexp_destructor); + delete_iohandle(inh); + unbind_all_names_for_node(&r->node); + DECREF(&r->node, node_destructor); } void start_relay(struct sockaddr_in const *peername, int fd) { - struct boot_args *args = malloc(sizeof(*args)); - args->peername = *peername; - args->fd = fd; - spawn((process_main_t) relay_main, args); + struct relay_node *n = (struct relay_node *) new_node(&relay_class, NULL); + n->peername = *peername; + endpoint_name(&n->peername, CMSG_BYTES(sizeof(n->peername_str), n->peername_str)); + n->fd = fd; + n->outh = new_iohandle(n->fd); + spawn((process_main_t) relay_main, n); } diff --git a/sexp.c b/sexp.c index 46d8635..34a54df 100644 --- a/sexp.c +++ b/sexp.c @@ -145,3 +145,12 @@ sexp_t *sexp_assoc(sexp_t *list, cmsg_bytes_t key) { } return NULL; } + +size_t sexp_length(sexp_t *list) { + size_t result = 0; + while (sexp_pairp(list)) { + result++; + list = sexp_tail(list); + } + return result; +} diff --git a/sexp.h b/sexp.h index cfcbc40..a57c26d 100644 --- a/sexp.h +++ b/sexp.h @@ -43,61 +43,72 @@ extern sexp_t *sexp_slice(sexp_data_t *data, size_t offset, size_t length); extern sexp_t *sexp_display_hint(sexp_t *hint, sexp_t *body); extern sexp_t *sexp_cons(sexp_t *head, sexp_t *tail); -#define sexp_simple_stringp(x) ({ \ - sexp_t *__x = (x); \ - (__x != NULL) && ((__x->kind == SEXP_BYTES) || (__x->kind == SEXP_SLICE)); \ - }) +static inline int sexp_simple_stringp(sexp_t *x) { + return (x != NULL) && ((x->kind == SEXP_BYTES) || (x->kind == SEXP_SLICE)); +} -#define sexp_stringp(x) ({ \ - sexp_t *__x = (x); \ - sexp_simple_stringp(__x) || ((__x != NULL) && (__x->kind == SEXP_DISPLAY_HINT)); \ - }) +static inline int sexp_stringp(sexp_t *x) { + return sexp_simple_stringp(x) || ((x != NULL) && (x->kind == SEXP_DISPLAY_HINT)); +} -#define sexp_pairp(x) ({ \ - sexp_t *__x = (x); \ - (__x != NULL) && (__x->kind == SEXP_PAIR); \ - }) +static inline int sexp_pairp(sexp_t *x) { + return (x != NULL) && (x->kind == SEXP_PAIR); +} extern cmsg_bytes_t sexp_data(sexp_t *x); -#define sexp_head(x) ({sexp_t *__x = (x); assert(__x->kind == SEXP_PAIR); __x->data.pair.head;}) -#define sexp_tail(x) ({sexp_t *__x = (x); assert(__x->kind == SEXP_PAIR); __x->data.pair.tail;}) -#define sexp_hint(x) ({sexp_t *__x = (x); assert(__x->kind == SEXP_DISPLAY_HINT); __x->data.pair.head;}) -#define sexp_body(x) ({sexp_t *__x = (x); assert(__x->kind == SEXP_DISPLAY_HINT); __x->data.pair.tail;}) -#define sexp_setter_(x,y,fieldname) \ - ({ \ - sexp_t *__x = (x); \ - sexp_t *__y = (y); \ - sexp_t *__old; \ - assert(__x->kind == SEXP_PAIR); \ - INCREF(__y); \ - __old = __x->data.pair.fieldname; \ - __x->data.pair.fieldname = __y; \ - DECREF(__old, sexp_destructor); \ - __x; \ - }) +static inline sexp_t *sexp_head(sexp_t *x) { + assert(x->kind == SEXP_PAIR); + return x->data.pair.head; +} -#define sexp_sethead(x,y) sexp_setter_(x,y,head) -#define sexp_settail(x,y) sexp_setter_(x,y,tail) +static inline sexp_t *sexp_tail(sexp_t *x) { + assert(x->kind == SEXP_PAIR); + return x->data.pair.tail; +} -#define sexp_push(stackvar,val) \ - ({ \ - sexp_t *__oldstack = stackvar; \ - stackvar = INCREF(sexp_cons((val), stackvar)); \ - DECREF(__oldstack, sexp_destructor); \ - stackvar; \ - }) +static inline sexp_t *sexp_hint(sexp_t *x) { + assert(x->kind == SEXP_DISPLAY_HINT); + return x->data.pair.head; +} -#define sexp_pop(stackvar) \ - ({ \ - sexp_t *__nextstack = INCREF(sexp_tail(stackvar)); \ - sexp_t *__val = INCREF(sexp_head(stackvar)); \ - DECREF(stackvar, sexp_destructor); \ - stackvar = __nextstack; \ - UNGRAB(__val); \ - __val; \ - }) +static inline sexp_t *sexp_body(sexp_t *x) { + assert(x->kind == SEXP_DISPLAY_HINT); + return x->data.pair.tail; +} + +#define sexp_setter_(settername,fieldname) \ + static inline sexp_t *settername(sexp_t *x, sexp_t *y) { \ + sexp_t *old; \ + assert(x->kind == SEXP_PAIR); \ + INCREF(y); \ + old = x->data.pair.fieldname; \ + x->data.pair.fieldname = y; \ + DECREF(old, sexp_destructor); \ + return x; \ + } + +sexp_setter_(sexp_sethead, head) +sexp_setter_(sexp_settail, tail) + +static inline sexp_t *sexp_push(sexp_t *oldstack, sexp_t *val) { + sexp_t *newstack = INCREF(sexp_cons(val, oldstack)); + DECREF(oldstack, sexp_destructor); + return newstack; +} + +static inline sexp_t *sexp_pop(sexp_t *oldstack, sexp_t **valp) { + sexp_t *nextstack = INCREF(sexp_tail(oldstack)); + sexp_t *val = INCREF(sexp_head(oldstack)); + DECREF(oldstack, sexp_destructor); + UNGRAB(val); + if (valp != NULL) { + *valp = val; + } + return nextstack; +} extern sexp_t *sexp_assoc(sexp_t *list, cmsg_bytes_t key); +extern size_t sexp_length(sexp_t *list); #endif diff --git a/sexpio.c b/sexpio.c index 9373e2a..3a2fe8c 100644 --- a/sexpio.c +++ b/sexpio.c @@ -102,7 +102,7 @@ sexp_t *sexp_read(IOHandle *h) { case '(': iohandle_drain(h, 1); - sexp_push(stack, sexp_cons(NULL, NULL)); + stack = sexp_push(stack, sexp_cons(NULL, NULL)); continue; case ')': { @@ -111,7 +111,8 @@ sexp_t *sexp_read(IOHandle *h) { h->error_kind = SEXP_ERROR_SYNTAX; goto error; } - current = INCREF(sexp_pop(stack)); + stack = sexp_pop(stack, ¤t); + INCREF(current); iohandle_drain(h, 1); accumulator = INCREF(sexp_head(current)); DECREF(current, sexp_destructor); @@ -183,7 +184,7 @@ unsigned short sexp_write(IOHandle *h, sexp_t *x) { case SEXP_PAIR: iohandle_write(h, cmsg_cstring_bytes("(")); - sexp_push(stack, current); + stack = sexp_push(stack, current); break; default: @@ -200,7 +201,7 @@ unsigned short sexp_write(IOHandle *h, sexp_t *x) { sexp_t *cell = sexp_head(stack); if (cell == NULL) { iohandle_write(h, cmsg_cstring_bytes(")")); - sexp_pop(stack); /* no need to worry about incref/decref: it's NULL! */ + stack = sexp_pop(stack, NULL); /* no need to worry about incref/decref: val is NULL! */ goto check_stack; } diff --git a/util.c b/util.c index f2d280b..726f920 100644 --- a/util.c +++ b/util.c @@ -36,6 +36,12 @@ void cmsg_bytes_free(cmsg_bytes_t bytes) { free(bytes.bytes); } +int cmsg_bytes_cmp(cmsg_bytes_t a, cmsg_bytes_t b) { + if (a.len < b.len) return -1; + if (a.len > b.len) return 1; + return memcmp(a.bytes, b.bytes, a.len); +} + void die(char const *format, ...) { va_list vl; va_start(vl, format); From 006168b54439d797afd1cbdc6c7fe21fb1263ad1 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 17:38:50 -0500 Subject: [PATCH 028/122] Tweak comments --- harness.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/harness.c b/harness.c index 861e756..e4eb8e1 100644 --- a/harness.c +++ b/harness.c @@ -18,6 +18,7 @@ typedef unsigned char u_char; #include +/* What is a sane value for STACK_SIZE? */ #ifdef __APPLE__ /* Bollocks. Looks like OS X chokes unless STACK_SIZE is a multiple of 32k. */ # include @@ -33,6 +34,9 @@ typedef unsigned char u_char; # error Define STACK_SIZE for your platform. It should probably not be less than 32k? #endif +/* TODO: reuse stacks (via a freelist) */ +/* TODO: investigate avoiding syscall in swapcontext, setcontext etc. */ + static volatile int harness_running = 1; Process *current_process = NULL; @@ -80,7 +84,7 @@ void spawn(void (*f)(void *), void *arg) { p->state = PROCESS_DEAD; - p->stack_base = malloc(STACK_SIZE); /* what is a sane value here? 32k for mac... */ + p->stack_base = malloc(STACK_SIZE); PCHECK(p->stack_base, "stack pointer malloc"); ICHECK(getcontext(&p->context), "spawn getcontext"); From e055c7fb82741601ce6b3ca757ae8f372dbc596d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:11:00 -0500 Subject: [PATCH 029/122] Inline cmsg_cstring_bytes; add gen_uuid --- cmsg_private.h | 10 +++++++++- util.c | 28 +++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/cmsg_private.h b/cmsg_private.h index e70c4f2..275649d 100644 --- a/cmsg_private.h +++ b/cmsg_private.h @@ -12,7 +12,15 @@ typedef struct cmsg_bytes_t { }) #define EMPTY_BYTES CMSG_BYTES(0, NULL) -extern cmsg_bytes_t cmsg_cstring_bytes(char const *cstr); +static inline cmsg_bytes_t cmsg_cstring_bytes(char const *cstr) { + cmsg_bytes_t result; + result.len = strlen(cstr); + result.bytes = (void *) cstr; + return result; +} + +extern int gen_uuid(unsigned char *uuid_buf); /* must be 16 bytes long */ + extern cmsg_bytes_t cmsg_bytes_malloc_dup(cmsg_bytes_t src); extern cmsg_bytes_t cmsg_bytes_malloc(size_t amount); extern void cmsg_bytes_free(cmsg_bytes_t bytes); diff --git a/util.c b/util.c index 726f920..4ca3cef 100644 --- a/util.c +++ b/util.c @@ -4,15 +4,33 @@ #include #include +#include #include +/* OSSP UUID */ +#include + #include "cmsg_private.h" -cmsg_bytes_t cmsg_cstring_bytes(char const *cstr) { - cmsg_bytes_t result; - result.len = strlen(cstr); - result.bytes = (void *) cstr; - return result; +int gen_uuid(unsigned char *uuid_buf) { + uuid_rc_t result; + uuid_t *uuid; + size_t uuid_buf_len = UUID_LEN_BIN; + + result = uuid_create(&uuid); + if (result != UUID_RC_OK) return result; + + result = uuid_make(uuid, UUID_MAKE_V4); + if (result != UUID_RC_OK) return result; + + result = uuid_export(uuid, UUID_FMT_BIN, &uuid_buf, &uuid_buf_len); + if (result != UUID_RC_OK) return result; + assert(uuid_buf_len == UUID_LEN_BIN); + + result = uuid_destroy(uuid); + if (result != UUID_RC_OK) return result; + + return UUID_RC_OK; } cmsg_bytes_t cmsg_bytes_malloc_dup(cmsg_bytes_t src) { From 35f42c110aaf1e2483f3240a8aaf75924349cd82 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:11:25 -0500 Subject: [PATCH 030/122] Link against libuuid (OSSP) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 40f7ba8..af11407 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g all: $(TARGET) $(TARGET): $(OBJECTS) - $(CC) $(CFLAGS) -o $@ $(OBJECTS) -levent -# $(CC) $(CFLAGS) -o $@ $(OBJECTS) -levent -lrt + $(CC) $(CFLAGS) -o $@ $(OBJECTS) -luuid -levent +# $(CC) $(CFLAGS) -o $@ $(OBJECTS) -luuid -levent -lrt %.o: %.c $(CC) $(CFLAGS) -c $< From 6c9c026a20409651be9d6cb07091d73dc98d5df3 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:11:45 -0500 Subject: [PATCH 031/122] Add queue_append() --- dataq.c | 16 ++++++++++++++++ dataq.h | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/dataq.c b/dataq.c index c90eb32..753d87f 100644 --- a/dataq.c +++ b/dataq.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "dataq.h" @@ -30,3 +31,18 @@ void *dequeue(queue_t *q) { return x; } } + +void queue_append(queue_t *q1, queue_t *q2) { + assert(q1->link_offset == q2->link_offset); + + if (q2->head != NULL) { + if (q1->head != NULL) { + QLINK(q1, q1->tail) = q2->head; + } else { + q1->head = q2->head; + } + q1->tail = q2->tail; + q2->head = NULL; + q2->tail = NULL; + } +} diff --git a/dataq.h b/dataq.h index a193b01..e65b4d2 100644 --- a/dataq.h +++ b/dataq.h @@ -8,7 +8,12 @@ typedef struct queue_t_ { void *tail; } queue_t; +#define EMPTY_QUEUE(element_t, link_field_name) \ + ((queue_t) { offsetof(element_t, link_field_name), 0, NULL, NULL }) + extern void enqueue(queue_t *q, void *x); extern void *dequeue(queue_t *q); +extern void queue_append(queue_t *q1, queue_t *q2); + #endif From a5c2ec5830b88255a3b962dc6b7b71415314bef4 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:12:35 -0500 Subject: [PATCH 032/122] Comment out scheduler noise --- harness.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harness.c b/harness.c index e4eb8e1..3068d43 100644 --- a/harness.c +++ b/harness.c @@ -237,7 +237,7 @@ void boot_harness(void) { while (runlist.count) { queue_t work = runlist; runlist = EMPTY_PROCESS_QUEUE; - info("Processing %d jobs\n", work.count); + //info("Processing %d jobs\n", work.count); while ((current_process = dequeue(&work)) != NULL) { //info("entering %p\n", current_process); ICHECK(swapcontext(&scheduler, ¤t_process->context), "boot_harness swapcontext"); From 9e0191b2745d4c5aae375794191cb30712b893dc Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:12:54 -0500 Subject: [PATCH 033/122] Add suspend() and resume() to harness --- harness.c | 17 +++++++++++++++-- harness.h | 5 ++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/harness.c b/harness.c index 3068d43..dd1fc8f 100644 --- a/harness.c +++ b/harness.c @@ -40,7 +40,7 @@ typedef unsigned char u_char; static volatile int harness_running = 1; Process *current_process = NULL; -#define EMPTY_PROCESS_QUEUE ((queue_t) { offsetof(Process, link), 0, NULL, NULL }) +#define EMPTY_PROCESS_QUEUE EMPTY_QUEUE(Process, link) static ucontext_t scheduler; static queue_t runlist = EMPTY_PROCESS_QUEUE; @@ -73,12 +73,23 @@ void killproc(void) { schedule(); } +void suspend(void) { + assert(current_process->state == PROCESS_RUNNING); + current_process->state = PROCESS_WAITING; + schedule(); +} + +void resume(Process *p) { + assert(p->state == PROCESS_WAITING); + enqueue_runlist(p); +} + static void driver(void (*f)(void *), void *arg) { f(arg); killproc(); } -void spawn(void (*f)(void *), void *arg) { +Process *spawn(void (*f)(void *), void *arg) { Process *p = calloc(1, sizeof(*p)); PCHECK(p, "spawn calloc"); @@ -97,6 +108,8 @@ void spawn(void (*f)(void *), void *arg) { p->link = NULL; enqueue_runlist(p); + + return p; } void nap_isr(int fd, short what, void *arg) { diff --git a/harness.h b/harness.h index f171f52..41db564 100644 --- a/harness.h +++ b/harness.h @@ -28,9 +28,12 @@ typedef struct IOHandle { extern Process *current_process; extern void yield(void); -extern void spawn(process_main_t f, void *arg); +extern Process *spawn(process_main_t f, void *arg); extern void nap(long millis); +extern void suspend(void); +extern void resume(Process *p); + extern IOHandle *new_iohandle(int fd); extern void delete_iohandle(IOHandle *h); extern void iohandle_clear_error(IOHandle *h); From 54c13c969471d02978a81b643e79e689cebe58d4 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:13:21 -0500 Subject: [PATCH 034/122] Add factory message handler --- main.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index 519c7de..ec9c10a 100644 --- a/main.c +++ b/main.c @@ -27,7 +27,46 @@ static node_t *factory_construct(node_class_t *nc, sexp_t *args) { } static void factory_handle_message(node_t *n, sexp_t *m) { - info("factory_handle_message\n"); + size_t msglen = sexp_length(m); + sexp_t *args; + cmsg_bytes_t selector; + + if (msglen == 0 || !sexp_stringp(sexp_head(m))) { + warn("Invalid message in factory\n"); + return; + } + + selector = sexp_data(sexp_head(m)); + args = sexp_tail(m); + + if ((msglen == 5) + && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("create"))) { + sexp_t *classname = sexp_listref(args, 0); + sexp_t *ctor_arg = sexp_listref(args, 1); + sexp_t *reply_sink = sexp_listref(args, 2); + sexp_t *reply_name = sexp_listref(args, 3); + if (sexp_stringp(classname) && sexp_stringp(reply_sink) && sexp_stringp(reply_name)) { + cmsg_bytes_t classname_bytes = sexp_data(classname); + node_class_t *nc = lookup_node_class(classname_bytes); + if (nc == NULL) { + warn("Node class not found <<%.*s>>\n", classname_bytes.len, classname_bytes.bytes); + } else { + new_node(nc, ctor_arg); + { + sexp_t *createok = sexp_cons(sexp_bytes(cmsg_cstring_bytes("create-ok")), NULL); + INCREF(createok); + post_node(sexp_data(reply_sink), + sexp_data(reply_name), + createok); + DECREF(createok, sexp_destructor); + } + } + } + } else { + warn("Message not understood in factory; selector <<%.*s>>, length %u\n", + selector.len, selector.bytes, + msglen); + } } static node_class_t factory_class = { From 99ab1d60aadd2450395189aaeea60fa5645f409b Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:13:41 -0500 Subject: [PATCH 035/122] Add info() on bind/unbind of nodes --- node.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/node.c b/node.c index 14f11fb..5963743 100644 --- a/node.c +++ b/node.c @@ -73,6 +73,7 @@ int bind_node(cmsg_bytes_t name, node_t *n) { } hashtable_put(&directory, name, n); hashtable_put(&n->names, name, NULL); + info("Binding node <<%.*s>> of class %s\n", name.len, name.bytes, n->node_class->name); return 1; } @@ -82,6 +83,7 @@ int unbind_node(cmsg_bytes_t name) { if (n == NULL) { return 0; } else { + info("Unbinding node <<%.*s>> of class %s\n", name.len, name.bytes, n->node_class->name); hashtable_erase(&n->names, name); hashtable_erase(&directory, name); return 1; From c4999306e1b5b70c4a93ca16b73e8899af6b71f7 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:13:58 -0500 Subject: [PATCH 036/122] Cosmetic --- relay.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/relay.c b/relay.c index e1601ad..8fc43a0 100644 --- a/relay.c +++ b/relay.c @@ -127,7 +127,9 @@ static void relay_main(struct relay_node *r) { sexp_t *args = sexp_tail(message); size_t msglen = sexp_length(message); - /* TODO: have constant atoms for post, subscribe, unsubscribe etc (see also post_node()) */ + /* TODO: have constant atoms for post, subscribe, unsubscribe + etc (see also post_node() and the handler in queue.c etc) */ + if (!cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post")) && (msglen == 4) && sexp_stringp(sexp_head(args))) { cmsg_bytes_t nodename = sexp_data(sexp_head(args)); From 957108f8a29d4f6c15d61a87cfe338b55e9faf10 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:14:17 -0500 Subject: [PATCH 037/122] Sexp utilities --- sexp.c | 41 +++++++++++++++++++++++++++++++++++++++++ sexp.h | 18 ++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/sexp.c b/sexp.c index 34a54df..791bece 100644 --- a/sexp.c +++ b/sexp.c @@ -154,3 +154,44 @@ size_t sexp_length(sexp_t *list) { } return result; } + +sexp_t *sexp_new_queue(void) { + return sexp_cons(NULL, NULL); +} + +int sexp_queue_emptyp(sexp_t *q) { + return sexp_head(q) == NULL; +} + +void sexp_queue_pushback(sexp_t *q, sexp_t *x) { + sexp_t *cell = sexp_cons(x, sexp_head(q)); + if (sexp_head(q) == NULL) { + sexp_settail(q, cell); + } + sexp_sethead(q, cell); +} + +void sexp_enqueue(sexp_t *q, sexp_t *x) { + sexp_t *cell = sexp_cons(x, NULL); + if (sexp_head(q) == NULL) { + sexp_sethead(q, cell); + } else { + sexp_settail(sexp_tail(q), cell); + } + sexp_settail(q, cell); +} + +sexp_t *sexp_dequeue(sexp_t *q) { + if (sexp_head(q) == NULL) { + return NULL; + } else { + sexp_t *x = INCREF(sexp_head(sexp_head(q))); + sexp_t *cell = sexp_tail(sexp_head(q)); + sexp_sethead(q, cell); + if (cell == NULL) { + sexp_settail(q, NULL); + } + UNGRAB(x); + return x; + } +} diff --git a/sexp.h b/sexp.h index a57c26d..ea457c1 100644 --- a/sexp.h +++ b/sexp.h @@ -108,7 +108,25 @@ static inline sexp_t *sexp_pop(sexp_t *oldstack, sexp_t **valp) { return nextstack; } +static inline sexp_t *sexp_listtail(sexp_t *list, size_t dropcount) { + while (dropcount) { + list = sexp_tail(list); + dropcount--; + } + return list; +} + +static inline sexp_t *sexp_listref(sexp_t *list, size_t index) { + return sexp_head(sexp_listtail(list, index)); +} + extern sexp_t *sexp_assoc(sexp_t *list, cmsg_bytes_t key); extern size_t sexp_length(sexp_t *list); +extern sexp_t *sexp_new_queue(void); +extern int sexp_queue_emptyp(sexp_t *q); +extern void sexp_queue_pushback(sexp_t *q, sexp_t *x); +extern void sexp_enqueue(sexp_t *q, sexp_t *x); +extern sexp_t *sexp_dequeue(sexp_t *q); + #endif From fd85e4d24306ef6bc4e519ce766d0e66a4a27ce8 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:14:37 -0500 Subject: [PATCH 038/122] Initial attempt at queue implementation --- Makefile | 2 +- main.c | 2 + queue.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ queue.h | 6 ++ 4 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 queue.c create mode 100644 queue.h diff --git a/Makefile b/Makefile index af11407..a3483a6 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TARGET = cmsg -OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o +OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o queue.o CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g #CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 diff --git a/main.c b/main.c index ec9c10a..217719d 100644 --- a/main.c +++ b/main.c @@ -19,6 +19,7 @@ typedef unsigned char u_char; #include "sexp.h" #include "hashtable.h" #include "node.h" +#include "queue.h" #define WANT_CONSOLE_LISTENER 1 @@ -99,6 +100,7 @@ int main(int argc, char *argv[]) { info("Using libevent version %s\n", event_get_version()); init_node(); init_factory(); + init_queue(); #if WANT_CONSOLE_LISTENER spawn(console_listener, NULL); #endif diff --git a/queue.c b/queue.c new file mode 100644 index 0000000..452e8e6 --- /dev/null +++ b/queue.c @@ -0,0 +1,201 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include +#include +#include + +#include + +/* OSSP UUID */ +#include + +#include "cmsg_private.h" +#include "harness.h" +#include "ref.h" +#include "sexp.h" +#include "hashtable.h" +#include "node.h" +#include "queue.h" +#include "dataq.h" + +typedef struct queue_node_t_ { + node_t node; + sexp_t *backlog_q; + queue_t waiter_q; + hashtable_t subscriptions; + Process *shovel; + int shovel_awake; +} queue_node_t; + +typedef struct subscription_t_ { + sexp_t *uuid; + sexp_t *sink; + sexp_t *name; + struct subscription_t_ *link; +} subscription_t; + +static void free_subscription(subscription_t *sub) { + DECREF(sub->uuid, sexp_destructor); + DECREF(sub->sink, sexp_destructor); + DECREF(sub->name, sexp_destructor); + free(sub); +} + +static node_t *queue_construct(node_class_t *nc, sexp_t *args) { + queue_node_t *q = calloc(1, sizeof(*q)); + q->backlog_q = INCREF(sexp_new_queue()); + q->waiter_q = EMPTY_QUEUE(subscription_t, link); + init_hashtable(&q->subscriptions, 5, NULL, NULL); + q->shovel = NULL; + q->shovel_awake = 0; + return (node_t *) q; +} + +static void queue_destructor(node_t *n) { + queue_node_t *q = (queue_node_t *) n; + DECREF(q->backlog_q, sexp_destructor); + /* q->waiter_q will be automatically destroyed as part of the destruction of q->subscriptions */ + destroy_hashtable(&q->subscriptions); + /* TODO: um, take down the shovel too! */ + free(q); +} + +static int send_to_waiter(subscription_t *sub, sexp_t *body) { + return post_node(sexp_data(sub->sink), sexp_data(sub->name), body); +} + +static void shoveller(void *qv) { + queue_node_t *q = (queue_node_t *) qv; + sexp_t *body = NULL; /* held */ + queue_t examined; + subscription_t *sub = NULL; + + check_for_work: + + if (!sexp_queue_emptyp(q->backlog_q)) { + goto wait_and_shovel; + } + + body = INCREF(sexp_dequeue(q->backlog_q)); /* held */ + examined = EMPTY_QUEUE(subscription_t, link); + + find_valid_waiter: + if (q->waiter_q.count == 0) { + sexp_queue_pushback(q->backlog_q, body); + DECREF(body, sexp_destructor); + q->waiter_q = examined; + goto wait_and_shovel; + } + + sub = dequeue(&q->waiter_q); + + if ((sub->uuid == NULL) /* It has been unsubscribed. */ + || !send_to_waiter(sub, body)) { /* Destination no longer exists. */ + free_subscription(sub); + goto find_valid_waiter; + } + + DECREF(body, sexp_destructor); + queue_append(&q->waiter_q, &examined); + enqueue(&q->waiter_q, sub); + goto check_for_work; + + wait_and_shovel: + q->shovel_awake = 0; + suspend(); + goto check_for_work; +} + +static void throck_shovel(queue_node_t *q) { + if (!q->shovel_awake) { + if (!q->shovel) { + q->shovel = spawn(shoveller, q); + } else { + resume(q->shovel); + } + q->shovel_awake = 1; + } +} + +static void queue_handle_message(node_t *n, sexp_t *m) { + queue_node_t *q = (queue_node_t *) n; + + size_t msglen = sexp_length(m); + sexp_t *args; + cmsg_bytes_t selector; + + if (msglen == 0 || !sexp_stringp(sexp_head(m))) { + warn("Invalid message in factory\n"); + return; + } + + selector = sexp_data(sexp_head(m)); + args = sexp_tail(m); + + if ((msglen == 4) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post"))) { + sexp_enqueue(q->backlog_q, sexp_listref(args, 1)); + throck_shovel(q); + } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { + unsigned char uuid[UUID_LEN_BIN]; + if (gen_uuid(uuid) != 0) { + warn("Could not generate UUID\n"); + } else { + sexp_t *reply_sink = sexp_listref(args, 3); + sexp_t *reply_name = sexp_listref(args, 4); + subscription_t *sub = malloc(sizeof(*sub)); + sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); + sub->sink = sexp_listref(args, 1); + sub->name = sexp_listref(args, 2); + sub->link = NULL; + if (!sexp_stringp(sub->sink) || !sexp_stringp(sub->name) + || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { + DECREF(sub->uuid, sexp_destructor); + warn("Bad sink/name/reply_sink/replay_name in subscribe"); + } else { + INCREF(sub->sink); + INCREF(sub->name); + hashtable_put(&q->subscriptions, sexp_data(sub->uuid), sub); + enqueue(&q->waiter_q, sub); + throck_shovel(q); + { + sexp_t *subok = sexp_cons(sexp_bytes(cmsg_cstring_bytes("subscribe-ok")), + sexp_cons(sub->uuid, NULL)); + INCREF(subok); + post_node(sexp_data(reply_sink), sexp_data(reply_name), subok); + DECREF(subok, sexp_destructor); + } + } + } + } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { + if (sexp_stringp(sexp_head(args))) { + cmsg_bytes_t uuid = sexp_data(sexp_head(args)); + subscription_t *sub; + if (hashtable_get(&q->subscriptions, uuid, (void **) &sub)) { + /* TODO: clean up more eagerly perhaps? */ + hashtable_erase(&q->subscriptions, uuid); + DECREF(sub->uuid, sexp_destructor); + sub->uuid = NULL; + } + } else { + warn("Invalid unsubscription\n"); + } + } else { + warn("Message not understood in queue; selector <<%.*s>>, length %u\n", + selector.len, selector.bytes, + msglen); + } +} + +static node_class_t queue_class = { + .name = "queue", + .construct = queue_construct, + .destroy = queue_destructor, + .handle_message = queue_handle_message +}; + +void init_queue(void) { + register_node_class(&queue_class); +} diff --git a/queue.h b/queue.h new file mode 100644 index 0000000..f7a0e65 --- /dev/null +++ b/queue.h @@ -0,0 +1,6 @@ +#ifndef cmsg_queue_h +#define cmsg_queue_h + +extern void init_queue(void); + +#endif From ca7a7eb4339d817e72325953efab2e9cfd465d04 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:14:43 -0500 Subject: [PATCH 039/122] Little test stub --- t1 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 t1 diff --git a/t1 b/t1 new file mode 100644 index 0000000..35be9b8 --- /dev/null +++ b/t1 @@ -0,0 +1,2 @@ +(9:subscribe5:test10:0:5:test15:login) +(4:post7:factory(6:create5:queue((4:name2:q1))5:test11:k)0:) From f67d46efdbe8027ebbb15af20b5dbbc314d4d057 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:30:03 -0500 Subject: [PATCH 040/122] Link to ossp-uuid on linux --- Makefile | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index a3483a6..80c6543 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,25 @@ TARGET = cmsg OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o queue.o -CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g -#CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 -#CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 -static +UUID_CFLAGS:=$(shell uuid-config --cflags) +UUID_LDFLAGS:=$(shell uuid-config --ldflags) + +# grr +ifeq ($(shell uname -s),Darwin) +UUID_LIB=uuid +else +UUID_LIB=ossp-uuid +endif + +CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g $(UUID_CFLAGS) +#CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 $(UUID_CFLAGS) +#CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 -static $(UUID_CFLAGS) all: $(TARGET) $(TARGET): $(OBJECTS) - $(CC) $(CFLAGS) -o $@ $(OBJECTS) -luuid -levent -# $(CC) $(CFLAGS) -o $@ $(OBJECTS) -luuid -levent -lrt + $(CC) $(CFLAGS) $(UUID_LDFLAGS) -o $@ $(OBJECTS) -l$(UUID_LIB) -levent +# $(CC) $(CFLAGS) $(UUID_LDFLAGS) -o $@ $(OBJECTS) -l$(UUID_LIB) -levent -lrt %.o: %.c $(CC) $(CFLAGS) -c $< From dae85b8b15cdd01809cc8d2395b753b719b7485e Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:30:24 -0500 Subject: [PATCH 041/122] Avoid stompling on n->names during unbind_all --- node.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/node.c b/node.c index 5963743..62c9362 100644 --- a/node.c +++ b/node.c @@ -47,11 +47,15 @@ node_class_t *lookup_node_class(cmsg_bytes_t name) { return nc; } +static void init_node_names(node_t *n) { + init_hashtable(&n->names, 5, NULL, NULL); +} + node_t *new_node(node_class_t *nc, sexp_t *args) { node_t *n = nc->construct(nc, args); n->refcount = ZERO_REFCOUNT(); n->node_class = nc; - init_hashtable(&n->names, 5, NULL, NULL); + init_node_names(n); return n; } @@ -95,7 +99,10 @@ static void unbind_on_destroy(void *context, cmsg_bytes_t key, void *value) { } void unbind_all_names_for_node(node_t *n) { - hashtable_foreach(&n->names, unbind_on_destroy, NULL); + hashtable_t names = n->names; + init_node_names(n); + hashtable_foreach(&names, unbind_on_destroy, NULL); + destroy_hashtable(&names); } int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body) { From 5963d884bc9bcd9aa4b226e2056b7824c9c405fd Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:31:04 -0500 Subject: [PATCH 042/122] An additional assertion in DECREF --- ref.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ref.h b/ref.h index d84c811..e9e16ef 100644 --- a/ref.h +++ b/ref.h @@ -27,6 +27,7 @@ typedef struct refcount_t_ { #define DECREF(x, dtor) ({ \ typeof(x) __x = (x); \ if (__x != NULL) { \ + assert(__x->refcount.count); \ (__x->refcount.count)--; \ if (__x->refcount.count == 0) { \ (dtor)(__x); \ From f044c3b8ff098596d42508a6a54ff64dfbb8235e Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 1 Jan 2011 21:31:11 -0500 Subject: [PATCH 043/122] Compile on linux --- queue.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/queue.c b/queue.c index 452e8e6..f8a9233 100644 --- a/queue.c +++ b/queue.c @@ -9,6 +9,8 @@ #include +#include + /* OSSP UUID */ #include From 5e7a6efeb82720271eb4d614e74027bb49d8a18d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 10:29:47 -0500 Subject: [PATCH 044/122] Warn when deleting an IOHandle with waiting processes --- harness.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/harness.c b/harness.c index dd1fc8f..535a5d1 100644 --- a/harness.c +++ b/harness.c @@ -183,6 +183,11 @@ IOHandle *new_iohandle(int fd) { } void delete_iohandle(IOHandle *h) { + if (h->waiters) { + warn("Deleting IOHandle %p with fd %d: processes are blocked on this handle!\n", + h, + h->fd); + } bufferevent_free(h->io); free(h); } From 1994245ceaa1d30fe99a594cf28366c3d81109aa Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 10:30:17 -0500 Subject: [PATCH 045/122] Add std{in,out,err}_h --- harness.c | 15 +++++++++++++++ harness.h | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/harness.c b/harness.c index 535a5d1..0ac363a 100644 --- a/harness.c +++ b/harness.c @@ -37,6 +37,10 @@ typedef unsigned char u_char; /* TODO: reuse stacks (via a freelist) */ /* TODO: investigate avoiding syscall in swapcontext, setcontext etc. */ +IOHandle *stdin_h = NULL; +IOHandle *stdout_h = NULL; +IOHandle *stderr_h = NULL; + static volatile int harness_running = 1; Process *current_process = NULL; @@ -250,7 +254,12 @@ static void clean_dead_processes(void) { } void boot_harness(void) { + stdin_h = new_iohandle(0); + stdout_h = new_iohandle(1); + stderr_h = new_iohandle(2); + ICHECK(getcontext(&scheduler), "boot_harness getcontext"); + while (1) { while (runlist.count) { queue_t work = runlist; @@ -268,6 +277,12 @@ void boot_harness(void) { //info("Blocking for events\n"); event_loop(EVLOOP_ONCE); } + + info("Shutting down.\n"); + + delete_iohandle(stdin_h); + delete_iohandle(stdout_h); + delete_iohandle(stderr_h); } void interrupt_harness(void) { diff --git a/harness.h b/harness.h index 41db564..b2650c7 100644 --- a/harness.h +++ b/harness.h @@ -25,6 +25,10 @@ typedef struct IOHandle { unsigned short error_kind; } IOHandle; +extern IOHandle *stdin_h; +extern IOHandle *stdout_h; +extern IOHandle *stderr_h; + extern Process *current_process; extern void yield(void); From 6d01ea359a51a6f838a871dac71942a2630423c8 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 10:30:41 -0500 Subject: [PATCH 046/122] Add sexp_write_flush, and use it to trace messages --- relay.c | 15 ++++++++------- sexpio.c | 9 +++++++++ sexpio.h | 1 + 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/relay.c b/relay.c index 8fc43a0..b68dab1 100644 --- a/relay.c +++ b/relay.c @@ -59,6 +59,11 @@ static void relay_destructor(node_t *n) { static void relay_handle_message(node_t *n, sexp_t *m) { struct relay_node *r = (struct relay_node *) n; + + info("fd %d <-- ", r->fd); + sexp_write_flush(stderr_h, m); + fprintf(stderr, "\n"); + BCHECK(!sexp_write(r->outh, m), "relay_handle_message sexp_write"); } @@ -109,13 +114,9 @@ static void relay_main(struct relay_node *r) { if (inh->error_kind != 0) goto network_error; - /* Log received message to console */ - /* - fflush(NULL); - sexp_write(out_handle, message); - iohandle_write(out_handle, cmsg_cstring_bytes("\n")); - ICHECK(iohandle_flush(out_handle), "iohandle_flush out_handle"); - */ + info("fd %d --> ", r->fd); + sexp_write_flush(stderr_h, message); + fprintf(stderr, "\n"); if (!(sexp_pairp(message) && sexp_stringp(sexp_head(message)))) { send_error(r->outh, "ill-formed message", NULL); diff --git a/sexpio.c b/sexpio.c index 3a2fe8c..d56c3e5 100644 --- a/sexpio.c +++ b/sexpio.c @@ -214,3 +214,12 @@ unsigned short sexp_write(IOHandle *h, sexp_t *x) { return SEXP_ERROR_SYNTAX; } } + +unsigned short sexp_write_flush(IOHandle *h, sexp_t *x) { + unsigned short result; + + fflush(NULL); + result = sexp_write(h, x); + ICHECK(iohandle_flush(h), "sexp_write_flush iohandle_flush"); + return result; +} diff --git a/sexpio.h b/sexpio.h index 28b5955..70a5afd 100644 --- a/sexpio.h +++ b/sexpio.h @@ -7,5 +7,6 @@ extern sexp_t *sexp_read_atom(IOHandle *h); extern sexp_t *sexp_read(IOHandle *h); extern unsigned short sexp_write(IOHandle *h, sexp_t *x); +extern unsigned short sexp_write_flush(IOHandle *h, sexp_t *x); #endif From e7fa9b164236db0d7c839c46607b9706dc592397 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 11:44:31 -0500 Subject: [PATCH 047/122] Clear link pointer on dequeue --- dataq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/dataq.c b/dataq.c index 753d87f..6fe99a7 100644 --- a/dataq.c +++ b/dataq.c @@ -24,6 +24,7 @@ void *dequeue(queue_t *q) { } else { void *x = q->head; q->head = QLINK(q, x); + QLINK(q, x) = NULL; if (q->head == NULL) { q->tail = NULL; } From 0a0b4580575fce5f7b7e7f7bfb479acd8a776bf5 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 11:46:59 -0500 Subject: [PATCH 048/122] Switch to hex representation of UUIDs for now --- cmsg_private.h | 3 ++- queue.c | 5 +---- util.c | 26 +++++++++++++++++++------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/cmsg_private.h b/cmsg_private.h index 275649d..0823782 100644 --- a/cmsg_private.h +++ b/cmsg_private.h @@ -19,7 +19,8 @@ static inline cmsg_bytes_t cmsg_cstring_bytes(char const *cstr) { return result; } -extern int gen_uuid(unsigned char *uuid_buf); /* must be 16 bytes long */ +#define CMSG_UUID_BUF_SIZE 36 +extern int gen_uuid(unsigned char *uuid_buf); /* must be exactly CMSG_UUID_BUF_SIZE bytes long */ extern cmsg_bytes_t cmsg_bytes_malloc_dup(cmsg_bytes_t src); extern cmsg_bytes_t cmsg_bytes_malloc(size_t amount); diff --git a/queue.c b/queue.c index f8a9233..fdb62e3 100644 --- a/queue.c +++ b/queue.c @@ -11,9 +11,6 @@ #include -/* OSSP UUID */ -#include - #include "cmsg_private.h" #include "harness.h" #include "ref.h" @@ -141,7 +138,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { sexp_enqueue(q->backlog_q, sexp_listref(args, 1)); throck_shovel(q); } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - unsigned char uuid[UUID_LEN_BIN]; + unsigned char uuid[CMSG_UUID_BUF_SIZE]; if (gen_uuid(uuid) != 0) { warn("Could not generate UUID\n"); } else { diff --git a/util.c b/util.c index 4ca3cef..f0547a3 100644 --- a/util.c +++ b/util.c @@ -12,23 +12,35 @@ #include "cmsg_private.h" +#define UUID_CHECK(context) \ + if (result != UUID_RC_OK) { \ + warn("gen_uuid failed with %d at %s\n", result, context); \ + return result; \ + } + int gen_uuid(unsigned char *uuid_buf) { uuid_rc_t result; uuid_t *uuid; - size_t uuid_buf_len = UUID_LEN_BIN; + unsigned char temp_buf[UUID_LEN_STR + 1]; + unsigned char *temp_buf_ptr = &temp_buf[0]; /* odd API */ + size_t uuid_buf_len = UUID_LEN_STR + 1; + + assert(CMSG_UUID_BUF_SIZE == UUID_LEN_STR); result = uuid_create(&uuid); - if (result != UUID_RC_OK) return result; + UUID_CHECK("uuid_create"); result = uuid_make(uuid, UUID_MAKE_V4); - if (result != UUID_RC_OK) return result; + UUID_CHECK("uuid_make"); - result = uuid_export(uuid, UUID_FMT_BIN, &uuid_buf, &uuid_buf_len); - if (result != UUID_RC_OK) return result; - assert(uuid_buf_len == UUID_LEN_BIN); + result = uuid_export(uuid, UUID_FMT_STR, &temp_buf_ptr, &uuid_buf_len); + UUID_CHECK("uuid_export"); + assert(uuid_buf_len == (UUID_LEN_STR + 1)); + + memcpy(uuid_buf, temp_buf, CMSG_UUID_BUF_SIZE); result = uuid_destroy(uuid); - if (result != UUID_RC_OK) return result; + UUID_CHECK("uuid_destroy"); return UUID_RC_OK; } From f1ab541e5737f7c3ec0f9c6d7ce3b23813c5c901 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 11:49:05 -0500 Subject: [PATCH 049/122] Yield between handled messages in relay --- relay.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/relay.c b/relay.c index b68dab1..28e2efb 100644 --- a/relay.c +++ b/relay.c @@ -108,6 +108,8 @@ static void relay_main(struct relay_node *r) { //iohandle_settimeout(r->inh, 3, 0); while (1) { + yield(); + DECREF(message, sexp_destructor); message = NULL; message = INCREF(sexp_read(inh)); From c42deefbef0c49ac58ce410eb08b1124eca4e42e Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 11:49:56 -0500 Subject: [PATCH 050/122] New approach to node representation; sexp utilities; beginnings of test cases --- main.c | 27 ++++++++++------------ node.c | 29 ++++++++++++++++++++---- node.h | 7 +++--- queue.c | 68 ++++++++++++++++++++++++++++++++++++-------------------- relay.c | 62 ++++++++++++++++++++++++--------------------------- sexp.c | 4 ++++ sexp.h | 1 + sexpio.c | 7 ++++-- sexpio.h | 2 +- t1 | 3 ++- t2 | 4 ++++ 11 files changed, 131 insertions(+), 83 deletions(-) create mode 100644 t2 diff --git a/main.c b/main.c index 217719d..b701027 100644 --- a/main.c +++ b/main.c @@ -23,10 +23,6 @@ typedef unsigned char u_char; #define WANT_CONSOLE_LISTENER 1 -static node_t *factory_construct(node_class_t *nc, sexp_t *args) { - return malloc(sizeof(node_t)); -} - static void factory_handle_message(node_t *n, sexp_t *m) { size_t msglen = sexp_length(m); sexp_t *args; @@ -52,15 +48,16 @@ static void factory_handle_message(node_t *n, sexp_t *m) { if (nc == NULL) { warn("Node class not found <<%.*s>>\n", classname_bytes.len, classname_bytes.bytes); } else { - new_node(nc, ctor_arg); - { - sexp_t *createok = sexp_cons(sexp_bytes(cmsg_cstring_bytes("create-ok")), NULL); - INCREF(createok); - post_node(sexp_data(reply_sink), - sexp_data(reply_name), - createok); - DECREF(createok, sexp_destructor); + sexp_t *error = NULL; + sexp_t *reply; + if (new_node(nc, ctor_arg, &error) != NULL) { + reply = sexp_cons(sexp_cstring("create-ok"), NULL); + } else { + reply = sexp_cons(sexp_cstring("create-failed"), sexp_cons(error, NULL)); } + INCREF(reply); + post_node(sexp_data(reply_sink), sexp_data(reply_name), reply); + DECREF(reply, sexp_destructor); } } } else { @@ -72,13 +69,13 @@ static void factory_handle_message(node_t *n, sexp_t *m) { static node_class_t factory_class = { .name = "factory", - .construct = factory_construct, - .destroy = (node_destructor_fn_t) free, + .extend = NULL, + .destroy = NULL, .handle_message = factory_handle_message }; static void init_factory(void) { - bind_node(cmsg_cstring_bytes("factory"), new_node(&factory_class, NULL)); + bind_node(cmsg_cstring_bytes("factory"), new_node(&factory_class, NULL, NULL)); } #if WANT_CONSOLE_LISTENER diff --git a/node.c b/node.c index 62c9362..3d4b141 100644 --- a/node.c +++ b/node.c @@ -5,9 +5,13 @@ #include +#include + #include "cmsg_private.h" #include "ref.h" #include "sexp.h" +#include "harness.h" +#include "sexpio.h" #include "hashtable.h" #include "node.h" @@ -51,18 +55,35 @@ static void init_node_names(node_t *n) { init_hashtable(&n->names, 5, NULL, NULL); } -node_t *new_node(node_class_t *nc, sexp_t *args) { - node_t *n = nc->construct(nc, args); +node_t *new_node(node_class_t *nc, sexp_t *args, sexp_t **error_out) { + node_t *n = malloc(sizeof(*n)); n->refcount = ZERO_REFCOUNT(); n->node_class = nc; + n->extension = NULL; init_node_names(n); + if (nc->extend != NULL) { + sexp_t *error = nc->extend(n, args); + if (error != NULL) { + node_destructor(n); + if (error_out != NULL) { + *error_out = error; + } else { + warn("Creating node of class %s failed with ", nc->name); + sexp_writeln(stderr_h, error); + } + return NULL; + } + } return n; } void node_destructor(node_t *n) { + if (n->node_class->destroy != NULL) { + n->node_class->destroy(n); + } unbind_all_names_for_node(n); destroy_hashtable(&n->names); - n->node_class->destroy(n); + free(n); } node_t *lookup_node(cmsg_bytes_t name) { @@ -111,7 +132,7 @@ int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body) { int result; if (post_atom == NULL) { - post_atom = INCREF(sexp_bytes(cmsg_cstring_bytes("post"))); + post_atom = INCREF(sexp_cstring("post")); } msg = sexp_cons(body, msg); diff --git a/node.h b/node.h index ddbf88e..541d5cc 100644 --- a/node.h +++ b/node.h @@ -5,15 +5,16 @@ typedef struct node_t_ { refcount_t refcount; struct node_class_t_ *node_class; hashtable_t names; + void *extension; /* Each node class puts something different here in its instances */ } node_t; -typedef node_t *(*node_constructor_fn_t)(struct node_class_t_ *nc, sexp_t *args); +typedef sexp_t *(*node_extension_fn_t)(node_t *n, sexp_t *args); typedef void (*node_destructor_fn_t)(node_t *n); typedef void (*node_message_handler_fn_t)(node_t *n, sexp_t *m); typedef struct node_class_t_ { char const *name; - node_constructor_fn_t construct; + node_extension_fn_t extend; node_destructor_fn_t destroy; node_message_handler_fn_t handle_message; } node_class_t; @@ -25,7 +26,7 @@ extern void basic_node_destroy(node_t *n); extern void register_node_class(node_class_t *nc); extern node_class_t *lookup_node_class(cmsg_bytes_t name); -extern node_t *new_node(node_class_t *nc, sexp_t *args); +extern node_t *new_node(node_class_t *nc, sexp_t *args, sexp_t **error_out); extern void node_destructor(node_t *n); extern node_t *lookup_node(cmsg_bytes_t name); diff --git a/queue.c b/queue.c index fdb62e3..0e0e856 100644 --- a/queue.c +++ b/queue.c @@ -15,19 +15,19 @@ #include "harness.h" #include "ref.h" #include "sexp.h" +#include "sexpio.h" #include "hashtable.h" #include "node.h" #include "queue.h" #include "dataq.h" -typedef struct queue_node_t_ { - node_t node; +typedef struct queue_extension_t_ { sexp_t *backlog_q; queue_t waiter_q; hashtable_t subscriptions; Process *shovel; int shovel_awake; -} queue_node_t; +} queue_extension_t; typedef struct subscription_t_ { sexp_t *uuid; @@ -43,23 +43,32 @@ static void free_subscription(subscription_t *sub) { free(sub); } -static node_t *queue_construct(node_class_t *nc, sexp_t *args) { - queue_node_t *q = calloc(1, sizeof(*q)); - q->backlog_q = INCREF(sexp_new_queue()); - q->waiter_q = EMPTY_QUEUE(subscription_t, link); - init_hashtable(&q->subscriptions, 5, NULL, NULL); - q->shovel = NULL; - q->shovel_awake = 0; - return (node_t *) q; +static sexp_t *queue_extend(node_t *n, sexp_t *args) { + if ((sexp_length(args) == 1) && sexp_stringp(sexp_head(args))) { + cmsg_bytes_t name = sexp_data(sexp_head(args)); + queue_extension_t *q = calloc(1, sizeof(*q)); + q->backlog_q = INCREF(sexp_new_queue()); + q->waiter_q = EMPTY_QUEUE(subscription_t, link); + init_hashtable(&q->subscriptions, 5, NULL, NULL); + q->shovel = NULL; + q->shovel_awake = 0; + + n->extension = q; + return bind_node(name, n) ? NULL : sexp_cstring("bind failed"); + } else { + return sexp_cstring("invalid args"); + } } static void queue_destructor(node_t *n) { - queue_node_t *q = (queue_node_t *) n; - DECREF(q->backlog_q, sexp_destructor); - /* q->waiter_q will be automatically destroyed as part of the destruction of q->subscriptions */ - destroy_hashtable(&q->subscriptions); - /* TODO: um, take down the shovel too! */ - free(q); + queue_extension_t *q = n->extension; + if (q != NULL) { /* can be NULL if queue_extend was given invalid args */ + DECREF(q->backlog_q, sexp_destructor); + /* q->waiter_q will be automatically destroyed as part of the destruction of q->subscriptions */ + destroy_hashtable(&q->subscriptions); + warn("TODO: the shovel needs to be taken down as well here\n"); + free(q); + } } static int send_to_waiter(subscription_t *sub, sexp_t *body) { @@ -67,14 +76,17 @@ static int send_to_waiter(subscription_t *sub, sexp_t *body) { } static void shoveller(void *qv) { - queue_node_t *q = (queue_node_t *) qv; + queue_extension_t *q = qv; + sexp_t *body = NULL; /* held */ queue_t examined; subscription_t *sub = NULL; check_for_work: + info("Checking for work\n"); - if (!sexp_queue_emptyp(q->backlog_q)) { + if (sexp_queue_emptyp(q->backlog_q)) { + info("Backlog empty\n"); goto wait_and_shovel; } @@ -83,6 +95,7 @@ static void shoveller(void *qv) { find_valid_waiter: if (q->waiter_q.count == 0) { + info("No waiters\n"); sexp_queue_pushback(q->backlog_q, body); DECREF(body, sexp_destructor); q->waiter_q = examined; @@ -91,24 +104,32 @@ static void shoveller(void *qv) { sub = dequeue(&q->waiter_q); + info("Delivering to <<%.*s>>/<<%.*s>>...\n", + sexp_data(sub->sink).len, sexp_data(sub->sink).bytes, + sexp_data(sub->name).len, sexp_data(sub->name).bytes); + if ((sub->uuid == NULL) /* It has been unsubscribed. */ || !send_to_waiter(sub, body)) { /* Destination no longer exists. */ + info((sub->uuid == NULL) ? "Waiter was unsubscribed\n" : "Destination not found\n"); free_subscription(sub); goto find_valid_waiter; } + info("Delivery successful\n"); DECREF(body, sexp_destructor); queue_append(&q->waiter_q, &examined); enqueue(&q->waiter_q, sub); goto check_for_work; wait_and_shovel: + info("Waiting for throck\n"); q->shovel_awake = 0; suspend(); + info("Throck received!\n"); goto check_for_work; } -static void throck_shovel(queue_node_t *q) { +static void throck_shovel(queue_extension_t *q) { if (!q->shovel_awake) { if (!q->shovel) { q->shovel = spawn(shoveller, q); @@ -120,7 +141,7 @@ static void throck_shovel(queue_node_t *q) { } static void queue_handle_message(node_t *n, sexp_t *m) { - queue_node_t *q = (queue_node_t *) n; + queue_extension_t *q = n->extension; size_t msglen = sexp_length(m); sexp_t *args; @@ -160,8 +181,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { enqueue(&q->waiter_q, sub); throck_shovel(q); { - sexp_t *subok = sexp_cons(sexp_bytes(cmsg_cstring_bytes("subscribe-ok")), - sexp_cons(sub->uuid, NULL)); + sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); INCREF(subok); post_node(sexp_data(reply_sink), sexp_data(reply_name), subok); DECREF(subok, sexp_destructor); @@ -190,7 +210,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { static node_class_t queue_class = { .name = "queue", - .construct = queue_construct, + .extend = queue_extend, .destroy = queue_destructor, .handle_message = queue_handle_message }; diff --git a/relay.c b/relay.c index 28e2efb..5c641bb 100644 --- a/relay.c +++ b/relay.c @@ -31,22 +31,21 @@ typedef unsigned char u_char; #include "hashtable.h" #include "node.h" -struct relay_node { - node_t node; +typedef struct relay_extension_t_ { struct sockaddr_in peername; char peername_str[256]; int fd; IOHandle *outh; -}; +} relay_extension_t; -static node_t *relay_construct(node_class_t *nc, sexp_t *args) { +static sexp_t *relay_extend(node_t *n, sexp_t *args) { /* TODO: outbound connections; args==NULL -> server relay, nonNULL -> outbound. */ - struct relay_node *r = calloc(1, sizeof(*r)); - return (node_t *) r; + n->extension = calloc(1, sizeof(relay_extension_t)); + return NULL; } static void relay_destructor(node_t *n) { - struct relay_node *r = (struct relay_node *) n; + relay_extension_t *r = n->extension; delete_iohandle(r->outh); r->outh = NULL; if (close(r->fd) == -1) { @@ -54,30 +53,29 @@ static void relay_destructor(node_t *n) { warn("Closing file descriptor %d produced errno %d: %s\n", r->fd, errno, strerror(errno)); } - free(n); + free(r); } static void relay_handle_message(node_t *n, sexp_t *m) { - struct relay_node *r = (struct relay_node *) n; + relay_extension_t *r = n->extension; info("fd %d <-- ", r->fd); - sexp_write_flush(stderr_h, m); - fprintf(stderr, "\n"); + sexp_writeln(stderr_h, m); BCHECK(!sexp_write(r->outh, m), "relay_handle_message sexp_write"); } static node_class_t relay_class = { .name = "relay", - .construct = relay_construct, + .extend = relay_extend, .destroy = relay_destructor, .handle_message = relay_handle_message }; static void send_error(IOHandle *h, char const *message, sexp_t *extra) { sexp_t *m = extra; - m = sexp_cons(sexp_bytes(cmsg_cstring_bytes(message)), m); - m = sexp_cons(sexp_bytes(cmsg_cstring_bytes("error")), m); + m = sexp_cons(sexp_cstring(message), m); + m = sexp_cons(sexp_cstring("error"), m); INCREF(m); iohandle_clear_error(h); @@ -88,17 +86,16 @@ static void send_error(IOHandle *h, char const *message, sexp_t *extra) { static void send_sexp_syntax_error(IOHandle *h, char const *message) { char const *url = "http://people.csail.mit.edu/rivest/Sexp.txt"; - send_error(h, message, sexp_cons(sexp_bytes(cmsg_cstring_bytes(url)), NULL)); + send_error(h, message, sexp_cons(sexp_cstring(url), NULL)); } -static void relay_main(struct relay_node *r) { +static void relay_main(node_t *n) { + relay_extension_t *r = n->extension; IOHandle *inh = new_iohandle(r->fd); sexp_t *message = NULL; /* held */ - assert((void *) &r->node == (void *) r); - - INCREF(&r->node); /* because the caller doesn't hold a ref, and we - need to drop ours on our death */ + INCREF(n); /* because the caller doesn't hold a ref, and we need to + drop ours on our death */ info("Accepted connection from %s on fd %d\n", r->peername_str, r->fd); @@ -117,8 +114,7 @@ static void relay_main(struct relay_node *r) { if (inh->error_kind != 0) goto network_error; info("fd %d --> ", r->fd); - sexp_write_flush(stderr_h, message); - fprintf(stderr, "\n"); + sexp_writeln(stderr_h, message); if (!(sexp_pairp(message) && sexp_stringp(sexp_head(message)))) { send_error(r->outh, "ill-formed message", NULL); @@ -148,9 +144,8 @@ static void relay_main(struct relay_node *r) { sexp_t *reply_sink_and_name = sexp_tail(sexp_tail(sexp_tail(args))); cmsg_bytes_t reply_sink = sexp_data(sexp_head(reply_sink_and_name)); cmsg_bytes_t reply_name = sexp_data(sexp_head(sexp_tail(reply_sink_and_name))); - if (bind_node(filter, &r->node)) { - sexp_t *subok = sexp_cons(sexp_bytes(cmsg_cstring_bytes("subscribe-ok")), - sexp_cons(filter_sexp, NULL)); + if (bind_node(filter, n)) { + sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(filter_sexp, NULL)); INCREF(subok); post_node(reply_sink, reply_name, subok); DECREF(subok, sexp_destructor); @@ -165,7 +160,7 @@ static void relay_main(struct relay_node *r) { warn("Unbind failed <<%.*s>>\n", id.len, id.bytes); } } else { - send_error(r->outh, "message not understood", message); + send_error(r->outh, "message not understood", sexp_cons(message, NULL)); goto protocol_error; } } @@ -193,15 +188,16 @@ static void relay_main(struct relay_node *r) { protocol_error: DECREF(message, sexp_destructor); delete_iohandle(inh); - unbind_all_names_for_node(&r->node); - DECREF(&r->node, node_destructor); + unbind_all_names_for_node(n); + DECREF(n, node_destructor); } void start_relay(struct sockaddr_in const *peername, int fd) { - struct relay_node *n = (struct relay_node *) new_node(&relay_class, NULL); - n->peername = *peername; - endpoint_name(&n->peername, CMSG_BYTES(sizeof(n->peername_str), n->peername_str)); - n->fd = fd; - n->outh = new_iohandle(n->fd); + node_t *n = new_node(&relay_class, NULL, NULL); + relay_extension_t *r = n->extension; + r->peername = *peername; + endpoint_name(&r->peername, CMSG_BYTES(sizeof(r->peername_str), r->peername_str)); + r->fd = fd; + r->outh = new_iohandle(r->fd); spawn((process_main_t) relay_main, n); } diff --git a/sexp.c b/sexp.c index 791bece..0bf8a43 100644 --- a/sexp.c +++ b/sexp.c @@ -85,6 +85,10 @@ sexp_data_t *sexp_data_alias(cmsg_bytes_t body) { return data; } +sexp_t *sexp_cstring(char const *str) { + return sexp_bytes(cmsg_cstring_bytes(str)); +} + sexp_t *sexp_bytes(cmsg_bytes_t bytes) { sexp_t *x = alloc_shell(SEXP_BYTES); x->data.bytes = cmsg_bytes_malloc_dup(bytes); diff --git a/sexp.h b/sexp.h index ea457c1..a69830e 100644 --- a/sexp.h +++ b/sexp.h @@ -38,6 +38,7 @@ extern void sexp_destructor(sexp_t *x); extern sexp_data_t *sexp_data_copy(cmsg_bytes_t body, size_t offset, size_t length); extern sexp_data_t *sexp_data_alias(cmsg_bytes_t body); +extern sexp_t *sexp_cstring(char const *str); extern sexp_t *sexp_bytes(cmsg_bytes_t bytes); extern sexp_t *sexp_slice(sexp_data_t *data, size_t offset, size_t length); extern sexp_t *sexp_display_hint(sexp_t *hint, sexp_t *body); diff --git a/sexpio.c b/sexpio.c index d56c3e5..319fb18 100644 --- a/sexpio.c +++ b/sexpio.c @@ -215,11 +215,14 @@ unsigned short sexp_write(IOHandle *h, sexp_t *x) { } } -unsigned short sexp_write_flush(IOHandle *h, sexp_t *x) { +unsigned short sexp_writeln(IOHandle *h, sexp_t *x) { unsigned short result; fflush(NULL); result = sexp_write(h, x); - ICHECK(iohandle_flush(h), "sexp_write_flush iohandle_flush"); + if (result == 0) { + iohandle_write(h, cmsg_cstring_bytes("\n")); + ICHECK(iohandle_flush(h), "sexp_writeln iohandle_flush"); + } return result; } diff --git a/sexpio.h b/sexpio.h index 70a5afd..1b8af06 100644 --- a/sexpio.h +++ b/sexpio.h @@ -7,6 +7,6 @@ extern sexp_t *sexp_read_atom(IOHandle *h); extern sexp_t *sexp_read(IOHandle *h); extern unsigned short sexp_write(IOHandle *h, sexp_t *x); -extern unsigned short sexp_write_flush(IOHandle *h, sexp_t *x); +extern unsigned short sexp_writeln(IOHandle *h, sexp_t *x); #endif diff --git a/t1 b/t1 index 35be9b8..1687325 100644 --- a/t1 +++ b/t1 @@ -1,2 +1,3 @@ (9:subscribe5:test10:0:5:test15:login) -(4:post7:factory(6:create5:queue((4:name2:q1))5:test11:k)0:) +(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:) +(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:) diff --git a/t2 b/t2 new file mode 100644 index 0000000..fc76e16 --- /dev/null +++ b/t2 @@ -0,0 +1,4 @@ +(9:subscribe5:test20:0:5:test25:login) +(4:post2:q1(4:post0:8:message10:)0:) +(4:post2:q1(4:post0:8:message20:)0:) +(11:unsubscribe5:test2) From e52c3df365618c0b3501f71e262cf86c534189f9 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 12:25:13 -0500 Subject: [PATCH 051/122] Silence delivery noise --- queue.c | 14 ++++++++------ relay.c | 4 ++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/queue.c b/queue.c index 0e0e856..30e708d 100644 --- a/queue.c +++ b/queue.c @@ -83,10 +83,10 @@ static void shoveller(void *qv) { subscription_t *sub = NULL; check_for_work: - info("Checking for work\n"); + //info("Checking for work\n"); if (sexp_queue_emptyp(q->backlog_q)) { - info("Backlog empty\n"); + //info("Backlog empty\n"); goto wait_and_shovel; } @@ -95,7 +95,7 @@ static void shoveller(void *qv) { find_valid_waiter: if (q->waiter_q.count == 0) { - info("No waiters\n"); + //info("No waiters\n"); sexp_queue_pushback(q->backlog_q, body); DECREF(body, sexp_destructor); q->waiter_q = examined; @@ -104,9 +104,11 @@ static void shoveller(void *qv) { sub = dequeue(&q->waiter_q); + /* info("Delivering to <<%.*s>>/<<%.*s>>...\n", sexp_data(sub->sink).len, sexp_data(sub->sink).bytes, sexp_data(sub->name).len, sexp_data(sub->name).bytes); + */ if ((sub->uuid == NULL) /* It has been unsubscribed. */ || !send_to_waiter(sub, body)) { /* Destination no longer exists. */ @@ -115,17 +117,17 @@ static void shoveller(void *qv) { goto find_valid_waiter; } - info("Delivery successful\n"); + //info("Delivery successful\n"); DECREF(body, sexp_destructor); queue_append(&q->waiter_q, &examined); enqueue(&q->waiter_q, sub); goto check_for_work; wait_and_shovel: - info("Waiting for throck\n"); + //info("Waiting for throck\n"); q->shovel_awake = 0; suspend(); - info("Throck received!\n"); + //info("Throck received!\n"); goto check_for_work; } diff --git a/relay.c b/relay.c index 5c641bb..412b43f 100644 --- a/relay.c +++ b/relay.c @@ -59,8 +59,10 @@ static void relay_destructor(node_t *n) { static void relay_handle_message(node_t *n, sexp_t *m) { relay_extension_t *r = n->extension; + /* info("fd %d <-- ", r->fd); sexp_writeln(stderr_h, m); + */ BCHECK(!sexp_write(r->outh, m), "relay_handle_message sexp_write"); } @@ -113,8 +115,10 @@ static void relay_main(node_t *n) { if (inh->error_kind != 0) goto network_error; + /* info("fd %d --> ", r->fd); sexp_writeln(stderr_h, message); + */ if (!(sexp_pairp(message) && sexp_stringp(sexp_head(message)))) { send_error(r->outh, "ill-formed message", NULL); From 449be964e30f9eeae92b9b75034e78e00c46f8bf Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 12:27:02 -0500 Subject: [PATCH 052/122] Crude subscriber test --- test1.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 test1.c diff --git a/test1.c b/test1.c new file mode 100644 index 0000000..da1cabd --- /dev/null +++ b/test1.c @@ -0,0 +1,71 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int main(int argc, char *argv[]) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in s; + FILE *f; + struct timeval start_time; + long bytecount = -1; + + s.sin_family = AF_INET; + s.sin_addr.s_addr = htonl(0x7f000001); + s.sin_port = htons(5671); + + if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; + + f = fdopen(fd, "a+"); + + fprintf(f, "(9:subscribe5:test10:0:5:test15:login)(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:)(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:)\n"); + fflush(f); + + while (1) { + char buf[1024]; + size_t n = read(fd, buf, sizeof(buf)); + if (n == 0) break; + if (n >= 7) { + if (!memcmp(buf, "(4:post", 7)) { + if (bytecount == -1) { + printf("Starting.\n"); + bytecount = 0; + gettimeofday(&start_time, NULL); + } + } + } + if (bytecount >= 0) { + bytecount += n; +#define MESSAGESIZE 26 + if ((bytecount % 100000) < MESSAGESIZE) { + struct timeval now; + double delta; + gettimeofday(&now, NULL); + delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; + printf("So far %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", + bytecount, + delta, + bytecount / delta, + bytecount / (delta * MESSAGESIZE)); + fflush(stdout); + } + } + } + + return 0; +} From de804e9bdb265b5d67a23da8abd9fa0354faff70 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 12:27:17 -0500 Subject: [PATCH 053/122] Switch to -O3 temporarily --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 80c6543..f04bf75 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ else UUID_LIB=ossp-uuid endif -CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g $(UUID_CFLAGS) -#CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 $(UUID_CFLAGS) +#CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O0 -g $(UUID_CFLAGS) +CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 $(UUID_CFLAGS) #CFLAGS = -D_XOPEN_SOURCE=600 -Wall -O3 -static $(UUID_CFLAGS) all: $(TARGET) From f98f0c9876f53b3ddeb3d05a3cb657fe95baf9a3 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 13:28:30 -0500 Subject: [PATCH 054/122] Processing in buffer-sized batches gives a speedup of 10x, roughly --- relay.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/relay.c b/relay.c index 412b43f..6d796e3 100644 --- a/relay.c +++ b/relay.c @@ -107,8 +107,6 @@ static void relay_main(node_t *n) { //iohandle_settimeout(r->inh, 3, 0); while (1) { - yield(); - DECREF(message, sexp_destructor); message = NULL; message = INCREF(sexp_read(inh)); From 114d8191df395af5c6ef1ac1be376aa46c866f72 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 13:32:53 -0500 Subject: [PATCH 055/122] Sender test program --- test1.c | 2 +- test3.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test3.c diff --git a/test1.c b/test1.c index da1cabd..ac3080f 100644 --- a/test1.c +++ b/test1.c @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) { double delta; gettimeofday(&now, NULL); delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; - printf("So far %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", + printf("So far received %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", bytecount, delta, bytecount / delta, diff --git a/test3.c b/test3.c new file mode 100644 index 0000000..9911ec5 --- /dev/null +++ b/test3.c @@ -0,0 +1,67 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int main(int argc, char *argv[]) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in s; + FILE *f; + struct timeval start_time; + long bytecount = 0; + char const *msg = "(4:post2:q1(4:post0:6:XXXXXX0:)0:)"; + size_t msglen = strlen(msg); + int i; + + s.sin_family = AF_INET; + s.sin_addr.s_addr = htonl(0x7f000001); + s.sin_port = htons(5671); + + if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; + + f = fdopen(fd, "a+"); + + fprintf(f, "(9:subscribe5:test30:0:5:test35:login)"); + fflush(f); + + gettimeofday(&start_time, NULL); + + for (i = 0; i < 10000000; i++) { + fwrite(msg, msglen, 1, f); + bytecount += msglen; + if ((bytecount % 100000) < msglen) { + struct timeval now; + double delta; + gettimeofday(&now, NULL); + delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; + printf("So far sent %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", + bytecount, + delta, + bytecount / delta, + bytecount / (delta * msglen)); + fflush(stdout); + } + } + + fprintf(f, "(11:unsubscribe5:test3)"); + fflush(f); + + fclose(f); + + return 0; +} From 42864f20075c7a5dc591418e288fc957b85943f5 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 13:40:22 -0500 Subject: [PATCH 056/122] Reuse iohandle_clear_error --- harness.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/harness.c b/harness.c index 0ac363a..55bdcec 100644 --- a/harness.c +++ b/harness.c @@ -181,8 +181,7 @@ IOHandle *new_iohandle(int fd) { (everrorcb) error_isr, h); PCHECK(h->io, "bufferevent_new"); - h->error_direction = 0; - h->error_kind = 0; + iohandle_clear_error(h); return h; } From 334c532a9bceb26f3dc306df7f5dea32268fda9b Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 13:40:36 -0500 Subject: [PATCH 057/122] Ignore SIGPIPE --- main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.c b/main.c index b701027..4d43284 100644 --- a/main.c +++ b/main.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,7 @@ static void console_listener(void *arg) { int main(int argc, char *argv[]) { info("cmsg, Copyright (C) 2010 Tony Garnock-Jones. All rights reserved.\n"); event_init(); + signal(SIGPIPE, SIG_IGN); /* avoid EPIPE when connections drop unexpectedly */ info("Using libevent version %s\n", event_get_version()); init_node(); init_factory(); From 9fcffa8083d3d9d16dbf116680518a9fe607ea29 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 13:58:18 -0500 Subject: [PATCH 058/122] Add forgotten token to delivery posts --- main.c | 7 +++---- node.c | 3 ++- node.h | 2 +- queue.c | 4 ++-- relay.c | 2 +- sexp.c | 12 +++++++++++- sexp.h | 5 ++++- test1.c | 8 +++++--- 8 files changed, 29 insertions(+), 14 deletions(-) diff --git a/main.c b/main.c index 4d43284..e2755c4 100644 --- a/main.c +++ b/main.c @@ -57,7 +57,7 @@ static void factory_handle_message(node_t *n, sexp_t *m) { reply = sexp_cons(sexp_cstring("create-failed"), sexp_cons(error, NULL)); } INCREF(reply); - post_node(sexp_data(reply_sink), sexp_data(reply_name), reply); + post_node(sexp_data(reply_sink), sexp_data(reply_name), reply, sexp_empty_bytes); DECREF(reply, sexp_destructor); } } @@ -97,6 +97,7 @@ int main(int argc, char *argv[]) { event_init(); signal(SIGPIPE, SIG_IGN); /* avoid EPIPE when connections drop unexpectedly */ info("Using libevent version %s\n", event_get_version()); + init_sexp(); init_node(); init_factory(); init_queue(); @@ -105,8 +106,6 @@ int main(int argc, char *argv[]) { #endif start_net(5671); boot_harness(); -#ifndef NDEBUG - release_sexp_cache(); -#endif + done_sexp(); return 0; } diff --git a/node.c b/node.c index 3d4b141..da16273 100644 --- a/node.c +++ b/node.c @@ -126,7 +126,7 @@ void unbind_all_names_for_node(node_t *n) { destroy_hashtable(&names); } -int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body) { +int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body, sexp_t *token) { static sexp_t *post_atom = NULL; sexp_t *msg = NULL; int result; @@ -135,6 +135,7 @@ int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body) { post_atom = INCREF(sexp_cstring("post")); } + msg = sexp_cons(token, msg); msg = sexp_cons(body, msg); msg = sexp_cons(sexp_bytes(name), msg); msg = sexp_cons(post_atom, msg); diff --git a/node.h b/node.h index 541d5cc..6399fd5 100644 --- a/node.h +++ b/node.h @@ -34,7 +34,7 @@ extern int bind_node(cmsg_bytes_t name, node_t *n); extern int unbind_node(cmsg_bytes_t name); extern void unbind_all_names_for_node(node_t *n); -extern int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body); +extern int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body, sexp_t *token); extern int send_node(cmsg_bytes_t node, sexp_t *message); #endif diff --git a/queue.c b/queue.c index 30e708d..8b96597 100644 --- a/queue.c +++ b/queue.c @@ -72,7 +72,7 @@ static void queue_destructor(node_t *n) { } static int send_to_waiter(subscription_t *sub, sexp_t *body) { - return post_node(sexp_data(sub->sink), sexp_data(sub->name), body); + return post_node(sexp_data(sub->sink), sexp_data(sub->name), body, sub->uuid); } static void shoveller(void *qv) { @@ -185,7 +185,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { { sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); INCREF(subok); - post_node(sexp_data(reply_sink), sexp_data(reply_name), subok); + post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); DECREF(subok, sexp_destructor); } } diff --git a/relay.c b/relay.c index 6d796e3..54aa5e7 100644 --- a/relay.c +++ b/relay.c @@ -149,7 +149,7 @@ static void relay_main(node_t *n) { if (bind_node(filter, n)) { sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(filter_sexp, NULL)); INCREF(subok); - post_node(reply_sink, reply_name, subok); + post_node(reply_sink, reply_name, subok, sexp_empty_bytes); DECREF(subok, sexp_destructor); } else { warn("Bind failed <<%.*s>>\n", filter.len, filter.bytes); diff --git a/sexp.c b/sexp.c index 0bf8a43..4e9b20c 100644 --- a/sexp.c +++ b/sexp.c @@ -10,8 +10,18 @@ static sexp_t *freelist = NULL; -void release_sexp_cache(void) { +sexp_t *sexp_empty_bytes = NULL; + +void init_sexp(void) { + sexp_empty_bytes = INCREF(sexp_cstring("")); +} + +void done_sexp(void) { int count = 0; + + DECREF(sexp_empty_bytes, sexp_destructor); + sexp_empty_bytes = NULL; + while (freelist != NULL) { sexp_t *x = freelist; freelist = x->data.pair.tail; diff --git a/sexp.h b/sexp.h index a69830e..664c9fb 100644 --- a/sexp.h +++ b/sexp.h @@ -30,7 +30,10 @@ typedef struct sexp_t_ { } data; } sexp_t; -extern void release_sexp_cache(void); +extern sexp_t *sexp_empty_bytes; + +extern void init_sexp(void); +extern void done_sexp(void); extern void sexp_data_destructor(sexp_data_t *data); extern void sexp_destructor(sexp_t *x); diff --git a/test1.c b/test1.c index ac3080f..919a288 100644 --- a/test1.c +++ b/test1.c @@ -36,13 +36,16 @@ int main(int argc, char *argv[]) { fprintf(f, "(9:subscribe5:test10:0:5:test15:login)(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:)(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:)\n"); fflush(f); +#define MESSAGESIZE 65 + while (1) { char buf[1024]; size_t n = read(fd, buf, sizeof(buf)); if (n == 0) break; - if (n >= 7) { - if (!memcmp(buf, "(4:post", 7)) { + if (n >= 16) { + if (!memcmp(buf, "(4:post8:consumer", 16)) { if (bytecount == -1) { + printf("Buffer at start: <<%.*s>>\n", (int) n, buf); printf("Starting.\n"); bytecount = 0; gettimeofday(&start_time, NULL); @@ -51,7 +54,6 @@ int main(int argc, char *argv[]) { } if (bytecount >= 0) { bytecount += n; -#define MESSAGESIZE 26 if ((bytecount % 100000) < MESSAGESIZE) { struct timeval now; double delta; From 33e304e7f7b3162107171a18ddaf2a5bd6f90f49 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 13:58:36 -0500 Subject: [PATCH 059/122] Remember queue name --- queue.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/queue.c b/queue.c index 8b96597..e74f384 100644 --- a/queue.c +++ b/queue.c @@ -22,6 +22,7 @@ #include "dataq.h" typedef struct queue_extension_t_ { + sexp_t *name; sexp_t *backlog_q; queue_t waiter_q; hashtable_t subscriptions; @@ -47,6 +48,7 @@ static sexp_t *queue_extend(node_t *n, sexp_t *args) { if ((sexp_length(args) == 1) && sexp_stringp(sexp_head(args))) { cmsg_bytes_t name = sexp_data(sexp_head(args)); queue_extension_t *q = calloc(1, sizeof(*q)); + q->name = INCREF(sexp_head(args)); q->backlog_q = INCREF(sexp_new_queue()); q->waiter_q = EMPTY_QUEUE(subscription_t, link); init_hashtable(&q->subscriptions, 5, NULL, NULL); @@ -63,6 +65,7 @@ static sexp_t *queue_extend(node_t *n, sexp_t *args) { static void queue_destructor(node_t *n) { queue_extension_t *q = n->extension; if (q != NULL) { /* can be NULL if queue_extend was given invalid args */ + DECREF(q->name, sexp_destructor); DECREF(q->backlog_q, sexp_destructor); /* q->waiter_q will be automatically destroyed as part of the destruction of q->subscriptions */ destroy_hashtable(&q->subscriptions); From 2c4a64e76fc41083d2264395ba3700ff37ec9c05 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 14:19:32 -0500 Subject: [PATCH 060/122] It's alpha software as yet --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index e2755c4..aecb6de 100644 --- a/main.c +++ b/main.c @@ -93,7 +93,7 @@ static void console_listener(void *arg) { #endif int main(int argc, char *argv[]) { - info("cmsg, Copyright (C) 2010 Tony Garnock-Jones. All rights reserved.\n"); + info("cmsg ALPHA, Copyright (C) 2010 Tony Garnock-Jones. All rights reserved.\n"); event_init(); signal(SIGPIPE, SIG_IGN); /* avoid EPIPE when connections drop unexpectedly */ info("Using libevent version %s\n", event_get_version()); From 0b9c6a3d096ffa26d216359acad31ecc8f9944c2 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 14:19:54 -0500 Subject: [PATCH 061/122] Add crude shovel stats --- queue.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/queue.c b/queue.c index e74f384..a113468 100644 --- a/queue.c +++ b/queue.c @@ -81,6 +81,8 @@ static int send_to_waiter(subscription_t *sub, sexp_t *body) { static void shoveller(void *qv) { queue_extension_t *q = qv; + size_t burst_count = 0; + size_t total_count = 0; sexp_t *body = NULL; /* held */ queue_t examined; subscription_t *sub = NULL; @@ -120,6 +122,9 @@ static void shoveller(void *qv) { goto find_valid_waiter; } + burst_count++; + total_count++; + //info("Delivery successful\n"); DECREF(body, sexp_destructor); queue_append(&q->waiter_q, &examined); @@ -127,10 +132,14 @@ static void shoveller(void *qv) { goto check_for_work; wait_and_shovel: + info("Queue <<%.*s>>: burst count %lu; total %lu\n", + sexp_data(q->name).len, sexp_data(q->name).bytes, + burst_count, total_count); //info("Waiting for throck\n"); q->shovel_awake = 0; suspend(); //info("Throck received!\n"); + burst_count = 0; goto check_for_work; } From 544a719d21b9ab219064841cce5bffd2811367aa Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 14:51:13 -0500 Subject: [PATCH 062/122] Separate socket EOF from socket error conditions --- harness.c | 12 +++++++++--- harness.h | 1 + main.c | 2 +- relay.c | 34 +++++++++++++++++----------------- sexpio.c | 22 ++++++++++++---------- sexpio.h | 2 +- 6 files changed, 41 insertions(+), 32 deletions(-) diff --git a/harness.c b/harness.c index 55bdcec..953f7ce 100644 --- a/harness.c +++ b/harness.c @@ -165,9 +165,14 @@ static void output_isr(struct bufferevent *bufev, IOHandle *h) { } static void error_isr(struct bufferevent *bufev, short what, IOHandle *h) { - info("error_isr 0x%04X\n", what); + unsigned short kind = what & ~(EVBUFFER_READ | EVBUFFER_WRITE); + info("error_isr 0x%04X fd %d\n", what, h->fd); h->error_direction = what & (EVBUFFER_READ | EVBUFFER_WRITE); - h->error_kind = what & ~(EVBUFFER_READ | EVBUFFER_WRITE); + if (kind == EVBUFFER_EOF) { + h->eof = 1; + } else { + h->error_kind = kind; + } awaken_waiters(h, EV_READ | EV_WRITE); } @@ -181,6 +186,7 @@ IOHandle *new_iohandle(int fd) { (everrorcb) error_isr, h); PCHECK(h->io, "bufferevent_new"); + h->eof = 0; iohandle_clear_error(h); return h; } @@ -212,7 +218,7 @@ static void block_on_io(IOHandle *h, short event) { cmsg_bytes_t iohandle_readwait(IOHandle *h, size_t at_least) { while (EVBUFFER_LENGTH(h->io->input) < at_least) { - if (h->error_kind) { + if (h->eof || h->error_kind) { return EMPTY_BYTES; } ICHECK(bufferevent_enable(h->io, EV_READ), "bufferevent_enable"); diff --git a/harness.h b/harness.h index b2650c7..1bff1f6 100644 --- a/harness.h +++ b/harness.h @@ -21,6 +21,7 @@ typedef struct IOHandle { Process *waiters; int fd; struct bufferevent *io; + int eof; unsigned short error_direction; unsigned short error_kind; } IOHandle; diff --git a/main.c b/main.c index aecb6de..d61d715 100644 --- a/main.c +++ b/main.c @@ -84,7 +84,7 @@ static void console_listener(void *arg) { IOHandle *in_handle = new_iohandle(0); while (1) { cmsg_bytes_t buf = iohandle_readwait(in_handle, 1); - if (in_handle->error_kind) break; + if (buf.len == 0) break; iohandle_drain(in_handle, buf.len); } delete_iohandle(in_handle); diff --git a/relay.c b/relay.c index 54aa5e7..888e643 100644 --- a/relay.c +++ b/relay.c @@ -109,9 +109,8 @@ static void relay_main(node_t *n) { while (1) { DECREF(message, sexp_destructor); message = NULL; - message = INCREF(sexp_read(inh)); - - if (inh->error_kind != 0) goto network_error; + if (!sexp_read(inh, &message)) goto network_error; + INCREF(message); /* info("fd %d --> ", r->fd); @@ -119,6 +118,7 @@ static void relay_main(node_t *n) { */ if (!(sexp_pairp(message) && sexp_stringp(sexp_head(message)))) { + info("Ill-formed message\n"); send_error(r->outh, "ill-formed message", NULL); goto protocol_error; } @@ -169,22 +169,22 @@ static void relay_main(node_t *n) { } network_error: - switch (inh->error_kind) { - case EVBUFFER_EOF: - info("Disconnecting fd %d normally.\n", r->fd); - break; + if (inh->eof) { + info("Disconnecting fd %d normally.\n", r->fd); + } else { + switch (inh->error_kind) { + case SEXP_ERROR_OVERFLOW: + send_sexp_syntax_error(r->outh, "sexp too big"); + break; - case SEXP_ERROR_OVERFLOW: - send_sexp_syntax_error(r->outh, "sexp too big"); - break; + case SEXP_ERROR_SYNTAX: + send_sexp_syntax_error(r->outh, "sexp syntax error"); + break; - case SEXP_ERROR_SYNTAX: - send_sexp_syntax_error(r->outh, "sexp syntax error"); - break; - - default: - warn("Relay handle error on fd %d: 0x%04X\n", r->fd, inh->error_kind); - break; + default: + warn("Relay handle error on fd %d: 0x%04X\n", r->fd, inh->error_kind); + break; + } } protocol_error: diff --git a/sexpio.c b/sexpio.c index 319fb18..40755d2 100644 --- a/sexpio.c +++ b/sexpio.c @@ -22,7 +22,7 @@ static sexp_t *read_simple_string(IOHandle *h, cmsg_bytes_t buf) { while (1) { buf = iohandle_readwait(h, buf.len + 1); - if (h->error_kind) return NULL; + if (buf.len == 0) return NULL; /* Don't reset i to zero: avoids scanning the beginning of the number repeatedly */ @@ -39,6 +39,10 @@ static sexp_t *read_simple_string(IOHandle *h, cmsg_bytes_t buf) { count = atoi((char *) buf.bytes); iohandle_drain(h, i + 1); buf = iohandle_readwait(h, count); + if (buf.len < count) { + /* Error or EOF. */ + return NULL; + } buf.len = count; result = sexp_bytes(buf); iohandle_drain(h, count); @@ -59,14 +63,11 @@ sexp_t *sexp_read_atom(IOHandle *h) { return read_simple_string(h, EMPTY_BYTES); } -#define CHECKH \ - if (h->error_kind) goto error; - #define READ1 \ buf = iohandle_readwait(h, 1); \ - CHECKH; + if (buf.len == 0) goto error; -sexp_t *sexp_read(IOHandle *h) { +int sexp_read(IOHandle *h, sexp_t **result_ptr) { cmsg_bytes_t buf; sexp_t *stack = NULL; /* held */ sexp_t *hint = NULL; /* held */ @@ -79,7 +80,7 @@ sexp_t *sexp_read(IOHandle *h) { case '[': { iohandle_drain(h, 1); hint = INCREF(read_simple_string(h, EMPTY_BYTES)); - CHECKH; + if (hint == NULL) goto error; READ1; if (buf.bytes[0] != ']') { h->error_kind = SEXP_ERROR_SYNTAX; @@ -93,7 +94,7 @@ sexp_t *sexp_read(IOHandle *h) { goto skip_whitespace_in_display_hint; } body = INCREF(read_simple_string(h, EMPTY_BYTES)); - CHECKH; + if (body == NULL) goto error; accumulator = sexp_display_hint(hint, body); DECREF(hint, sexp_destructor); /* these could be UNGRABs */ DECREF(body, sexp_destructor); @@ -132,7 +133,8 @@ sexp_t *sexp_read(IOHandle *h) { } if (stack == NULL) { - return accumulator; + *result_ptr = accumulator; + return 1; } else { sexp_t *current = sexp_head(stack); /* not held */ sexp_t *cell = sexp_cons(accumulator, NULL); @@ -149,7 +151,7 @@ sexp_t *sexp_read(IOHandle *h) { DECREF(stack, sexp_destructor); DECREF(hint, sexp_destructor); DECREF(body, sexp_destructor); - return NULL; + return 0; } void write_simple_string(IOHandle *h, sexp_t *x) { diff --git a/sexpio.h b/sexpio.h index 1b8af06..afcef4e 100644 --- a/sexpio.h +++ b/sexpio.h @@ -5,7 +5,7 @@ #define SEXP_ERROR_SYNTAX 0x8001 extern sexp_t *sexp_read_atom(IOHandle *h); -extern sexp_t *sexp_read(IOHandle *h); +extern int sexp_read(IOHandle *h, sexp_t **result_ptr); extern unsigned short sexp_write(IOHandle *h, sexp_t *x); extern unsigned short sexp_writeln(IOHandle *h, sexp_t *x); From 61c45e9a12fac297bcb1a5c980112e922c16cad9 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 14:51:31 -0500 Subject: [PATCH 063/122] Set high watermark at 256k --- harness.c | 1 + 1 file changed, 1 insertion(+) diff --git a/harness.c b/harness.c index 953f7ce..e7d1b80 100644 --- a/harness.c +++ b/harness.c @@ -186,6 +186,7 @@ IOHandle *new_iohandle(int fd) { (everrorcb) error_isr, h); PCHECK(h->io, "bufferevent_new"); + bufferevent_setwatermark(h->io, EV_READ, 0, 256 * 1024); h->eof = 0; iohandle_clear_error(h); return h; From 2b8f39b52f8413ed0b302c6601b0e7422050d8c8 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 14:53:43 -0500 Subject: [PATCH 064/122] Clean out the test programs too --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index f04bf75..ada6574 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ clean: rm -f $(OBJECTS) rm -rf *.dSYM rm -f depend.mk + rm -f test1 test1.o test3 test3.o depend.mk: gcc $(CFLAGS) -M *.c > $@ From 9dd094daeb31368a84fcdca35550392b2442d249 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 15:06:17 -0500 Subject: [PATCH 065/122] Save (and print) errno on socket error --- harness.c | 4 ++++ harness.h | 1 + relay.c | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/harness.c b/harness.c index e7d1b80..9c891b3 100644 --- a/harness.c +++ b/harness.c @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -166,12 +167,14 @@ static void output_isr(struct bufferevent *bufev, IOHandle *h) { static void error_isr(struct bufferevent *bufev, short what, IOHandle *h) { unsigned short kind = what & ~(EVBUFFER_READ | EVBUFFER_WRITE); + int saved_errno = errno; info("error_isr 0x%04X fd %d\n", what, h->fd); h->error_direction = what & (EVBUFFER_READ | EVBUFFER_WRITE); if (kind == EVBUFFER_EOF) { h->eof = 1; } else { h->error_kind = kind; + h->error_errno = saved_errno; } awaken_waiters(h, EV_READ | EV_WRITE); } @@ -205,6 +208,7 @@ void delete_iohandle(IOHandle *h) { void iohandle_clear_error(IOHandle *h) { h->error_direction = 0; h->error_kind = 0; + h->error_errno = 0; } static void block_on_io(IOHandle *h, short event) { diff --git a/harness.h b/harness.h index 1bff1f6..552044c 100644 --- a/harness.h +++ b/harness.h @@ -24,6 +24,7 @@ typedef struct IOHandle { int eof; unsigned short error_direction; unsigned short error_kind; + int error_errno; } IOHandle; extern IOHandle *stdin_h; diff --git a/relay.c b/relay.c index 888e643..c9268e5 100644 --- a/relay.c +++ b/relay.c @@ -182,7 +182,8 @@ static void relay_main(node_t *n) { break; default: - warn("Relay handle error on fd %d: 0x%04X\n", r->fd, inh->error_kind); + warn("Relay handle error 0x%04X on fd %d: %d, %s\n", + inh->error_kind, r->fd, inh->error_errno, strerror(inh->error_errno)); break; } } From 187a17ca6df07b4229b94787283912d576b0f66c Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 15:06:34 -0500 Subject: [PATCH 066/122] Treat read_simple_string result uniformly --- sexpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sexpio.c b/sexpio.c index 40755d2..bada817 100644 --- a/sexpio.c +++ b/sexpio.c @@ -128,7 +128,7 @@ int sexp_read(IOHandle *h, sexp_t **result_ptr) { } buf.len = 1; /* needed to avoid reading too much in read_simple_string */ accumulator = read_simple_string(h, buf); - if (h->error_kind) goto error; + if (accumulator == NULL) goto error; break; } From cc275029f715ed75562dc9eef74d27340ff3b9f5 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 15:21:12 -0500 Subject: [PATCH 067/122] Avoid overly large bursts of shovelling --- queue.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/queue.c b/queue.c index a113468..6d96a35 100644 --- a/queue.c +++ b/queue.c @@ -78,6 +78,15 @@ static int send_to_waiter(subscription_t *sub, sexp_t *body) { return post_node(sexp_data(sub->sink), sexp_data(sub->name), body, sub->uuid); } +static void end_burst(queue_extension_t *q, size_t *burst_count_ptr, size_t total_count) { + if (*burst_count_ptr > 0) { + info("Queue <<%.*s>>: burst count %lu; total %lu\n", + sexp_data(q->name).len, sexp_data(q->name).bytes, + *burst_count_ptr, total_count); + } + *burst_count_ptr = 0; +} + static void shoveller(void *qv) { queue_extension_t *q = qv; @@ -129,17 +138,20 @@ static void shoveller(void *qv) { DECREF(body, sexp_destructor); queue_append(&q->waiter_q, &examined); enqueue(&q->waiter_q, sub); + + if (burst_count > 10000) { + end_burst(q, &burst_count, total_count); + yield(); + } + goto check_for_work; wait_and_shovel: - info("Queue <<%.*s>>: burst count %lu; total %lu\n", - sexp_data(q->name).len, sexp_data(q->name).bytes, - burst_count, total_count); + end_burst(q, &burst_count, total_count); //info("Waiting for throck\n"); q->shovel_awake = 0; suspend(); //info("Throck received!\n"); - burst_count = 0; goto check_for_work; } From ec2461b9135ab5ac80837d47fd7677b4e8f56f11 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 15:58:26 -0500 Subject: [PATCH 068/122] Reading and discarding replies from the server avoids ECONNRESET when we close --- test3.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test3.c b/test3.c index 9911ec5..2ec968f 100644 --- a/test3.c +++ b/test3.c @@ -39,6 +39,13 @@ int main(int argc, char *argv[]) { fprintf(f, "(9:subscribe5:test30:0:5:test35:login)"); fflush(f); + usleep(100000); + { + char buf[4096]; + size_t n = read(fd, buf, sizeof(buf)); + printf("Received: <<%.*s>>\n", (int) n, buf); + } + gettimeofday(&start_time, NULL); for (i = 0; i < 10000000; i++) { From e02b5111ad3b1d52ee28bf2c9e93a31ed7196c3d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 16:04:57 -0500 Subject: [PATCH 069/122] Off-by-one error --- queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue.c b/queue.c index 6d96a35..9003f45 100644 --- a/queue.c +++ b/queue.c @@ -139,7 +139,7 @@ static void shoveller(void *qv) { queue_append(&q->waiter_q, &examined); enqueue(&q->waiter_q, sub); - if (burst_count > 10000) { + if (burst_count >= 10000) { end_burst(q, &burst_count, total_count); yield(); } From 449c59abeda9e10f5c9769a197b8bce70c5f54ab Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 16:14:51 -0500 Subject: [PATCH 070/122] Fix typo --- queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue.c b/queue.c index 9003f45..0486875 100644 --- a/queue.c +++ b/queue.c @@ -199,7 +199,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { if (!sexp_stringp(sub->sink) || !sexp_stringp(sub->name) || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { DECREF(sub->uuid, sexp_destructor); - warn("Bad sink/name/reply_sink/replay_name in subscribe"); + warn("Bad sink/name/reply_sink/reply_name in subscribe"); } else { INCREF(sub->sink); INCREF(sub->name); From 4400f8e5bb793c49c4d977c3ac8f0aaebc00b983 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 17:13:18 -0500 Subject: [PATCH 071/122] Permit specification of hostname in test1, test3 --- test1.c | 18 +++++++++++++++--- test3.c | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/test1.c b/test1.c index 919a288..e0cd37b 100644 --- a/test1.c +++ b/test1.c @@ -25,9 +25,21 @@ int main(int argc, char *argv[]) { struct timeval start_time; long bytecount = -1; - s.sin_family = AF_INET; - s.sin_addr.s_addr = htonl(0x7f000001); - s.sin_port = htons(5671); + if (argc < 2) { + fprintf(stderr, "Usage: test1 \n"); + exit(1); + } + + { + struct hostent *h = gethostbyname(argv[1]); + if (h == NULL) { + fprintf(stderr, "serverhostname lookup: %d\n", h_errno); + exit(1); + } + s.sin_family = AF_INET; + s.sin_addr.s_addr = * (uint32_t *) h->h_addr_list[0]; + s.sin_port = htons(5671); + } if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; diff --git a/test3.c b/test3.c index 2ec968f..055336e 100644 --- a/test3.c +++ b/test3.c @@ -28,9 +28,21 @@ int main(int argc, char *argv[]) { size_t msglen = strlen(msg); int i; - s.sin_family = AF_INET; - s.sin_addr.s_addr = htonl(0x7f000001); - s.sin_port = htons(5671); + if (argc < 2) { + fprintf(stderr, "Usage: test1 \n"); + exit(1); + } + + { + struct hostent *h = gethostbyname(argv[1]); + if (h == NULL) { + fprintf(stderr, "serverhostname lookup: %d\n", h_errno); + exit(1); + } + s.sin_family = AF_INET; + s.sin_addr.s_addr = * (uint32_t *) h->h_addr_list[0]; + s.sin_port = htons(5671); + } if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; From b4392db10975b23ed8aae0cd1977689688f0b0e6 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 17:29:30 -0500 Subject: [PATCH 072/122] Actually clean up waiters --- queue.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/queue.c b/queue.c index 0486875..e0ec2be 100644 --- a/queue.c +++ b/queue.c @@ -67,7 +67,12 @@ static void queue_destructor(node_t *n) { if (q != NULL) { /* can be NULL if queue_extend was given invalid args */ DECREF(q->name, sexp_destructor); DECREF(q->backlog_q, sexp_destructor); - /* q->waiter_q will be automatically destroyed as part of the destruction of q->subscriptions */ + { + subscription_t *sub = NULL; + while ((sub = dequeue(&q->waiter_q)) != NULL) { + free_subscription(sub); + } + } destroy_hashtable(&q->subscriptions); warn("TODO: the shovel needs to be taken down as well here\n"); free(q); From 97b610452f4b617e23a672444f809b71761a3e16 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 17:56:11 -0500 Subject: [PATCH 073/122] Direct exchange --- Makefile | 3 +- direct.c | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ direct.h | 6 ++ main.c | 2 + queue.c | 3 +- sexp.c | 8 +++ sexp.h | 3 + t4 | 4 ++ t5 | 7 +++ t6 | 6 ++ 10 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 direct.c create mode 100644 direct.h create mode 100644 t4 create mode 100644 t5 create mode 100644 t6 diff --git a/Makefile b/Makefile index ada6574..02d14bd 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ TARGET = cmsg -OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o queue.o +OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o \ + queue.o direct.o UUID_CFLAGS:=$(shell uuid-config --cflags) UUID_LDFLAGS:=$(shell uuid-config --ldflags) diff --git a/direct.c b/direct.c new file mode 100644 index 0000000..8428b31 --- /dev/null +++ b/direct.c @@ -0,0 +1,184 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "cmsg_private.h" +#include "harness.h" +#include "ref.h" +#include "sexp.h" +#include "hashtable.h" +#include "node.h" + +typedef struct direct_extension_t_ { + sexp_t *name; + hashtable_t routing_table; + hashtable_t subscriptions; +} direct_extension_t; + +typedef struct subscription_t_ { + sexp_t *uuid; + sexp_t *sink; + sexp_t *name; + struct subscription_t_ *link; +} subscription_t; + +static void free_subscription(subscription_t *sub) { + DECREF(sub->uuid, sexp_destructor); + DECREF(sub->sink, sexp_destructor); + DECREF(sub->name, sexp_destructor); + free(sub); +} + +static void free_subscription_chain(void *context, cmsg_bytes_t key, void *value) { + subscription_t *chain = value; + while (chain != NULL) { + subscription_t *next = chain->link; + free_subscription(chain); + chain = next; + } +} + +static sexp_t *direct_extend(node_t *n, sexp_t *args) { + if ((sexp_length(args) == 1) && sexp_stringp(sexp_head(args))) { + cmsg_bytes_t name = sexp_data(sexp_head(args)); + direct_extension_t *d = calloc(1, sizeof(*d)); + d->name = INCREF(sexp_head(args)); + init_hashtable(&d->routing_table, 5, NULL, NULL); + init_hashtable(&d->subscriptions, 5, NULL, NULL); + + n->extension = d; + return bind_node(name, n) ? NULL : sexp_cstring("bind failed"); + } else { + return sexp_cstring("invalid args"); + } +} + +static void direct_destructor(node_t *n) { + direct_extension_t *d = n->extension; + if (d != NULL) { /* can be NULL if direct_extend was given invalid args */ + DECREF(d->name, sexp_destructor); + hashtable_foreach(&d->routing_table, free_subscription_chain, NULL); + destroy_hashtable(&d->routing_table); + destroy_hashtable(&d->subscriptions); + free(d); + } +} + +static int send_to_sub(subscription_t *sub, sexp_t *body) { + return post_node(sexp_data(sub->sink), sexp_data(sub->name), body, sub->uuid); +} + +static void route_message(direct_extension_t *d, sexp_t *rk, sexp_t *body) { + subscription_t *chain = NULL; + subscription_t *prev = NULL; + hashtable_get(&d->routing_table, sexp_data(rk), (void **) &chain); + while (chain != NULL) { + subscription_t *next = chain->link; + if (!send_to_sub(chain, body)) { /* Destination no longer exists. */ + info("Destination not found\n"); + if (prev == NULL) { + hashtable_put(&d->routing_table, sexp_data(rk), chain->link); + } else { + prev->link = chain->link; + } + chain->link = NULL; + free_subscription(chain); + } + chain = next; + } +} + +static void direct_handle_message(node_t *n, sexp_t *m) { + direct_extension_t *d = n->extension; + + size_t msglen = sexp_length(m); + sexp_t *args; + cmsg_bytes_t selector; + + if (msglen == 0 || !sexp_stringp(sexp_head(m))) { + warn("Invalid message in direct\n"); + return; + } + + selector = sexp_data(sexp_head(m)); + args = sexp_tail(m); + + if ((msglen == 4) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post"))) { + sexp_t *rk = sexp_listref(args, 0); + if (sexp_stringp(rk)) { + route_message(d, rk, sexp_listref(args, 1)); + } else { + warn("Non-string routing key in direct\n"); + } + } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { + unsigned char uuid[CMSG_UUID_BUF_SIZE]; + if (gen_uuid(uuid) != 0) { + warn("Could not generate UUID\n"); + } else { + sexp_t *filter = sexp_listref(args, 0); + sexp_t *reply_sink = sexp_listref(args, 3); + sexp_t *reply_name = sexp_listref(args, 4); + subscription_t *sub = malloc(sizeof(*sub)); + sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); + sub->sink = sexp_listref(args, 1); + sub->name = sexp_listref(args, 2); + sub->link = NULL; + if (!sexp_stringp(filter) + || !sexp_stringp(sub->sink) || !sexp_stringp(sub->name) + || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { + DECREF(sub->uuid, sexp_destructor); + free(sub); + warn("Bad sink/name/reply_sink/reply_name in subscribe"); + } else { + INCREF(sub->sink); + INCREF(sub->name); + hashtable_put(&d->subscriptions, sexp_data(sub->uuid), sub); + hashtable_get(&d->routing_table, sexp_data(filter), (void **) &sub->link); + hashtable_put(&d->routing_table, sexp_data(filter), sub); + { + sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); + INCREF(subok); + post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); + DECREF(subok, sexp_destructor); + } + } + } + } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { + if (sexp_stringp(sexp_head(args))) { + cmsg_bytes_t uuid = sexp_data(sexp_head(args)); + subscription_t *sub; + if (hashtable_get(&d->subscriptions, uuid, (void **) &sub)) { + /* TODO: clean up more eagerly perhaps? */ + hashtable_erase(&d->subscriptions, uuid); + DECREF(sub->uuid, sexp_destructor); + sub->uuid = NULL; + } + } else { + warn("Invalid unsubscription\n"); + } + } else { + warn("Message not understood in direct; selector <<%.*s>>, length %u\n", + selector.len, selector.bytes, + msglen); + } +} + +static node_class_t direct_class = { + .name = "direct", + .extend = direct_extend, + .destroy = direct_destructor, + .handle_message = direct_handle_message +}; + +void init_direct(void) { + register_node_class(&direct_class); +} diff --git a/direct.h b/direct.h new file mode 100644 index 0000000..38b7528 --- /dev/null +++ b/direct.h @@ -0,0 +1,6 @@ +#ifndef cmsg_direct_h +#define cmsg_direct_h + +extern void init_direct(void); + +#endif diff --git a/main.c b/main.c index d61d715..4a5a55f 100644 --- a/main.c +++ b/main.c @@ -21,6 +21,7 @@ typedef unsigned char u_char; #include "hashtable.h" #include "node.h" #include "queue.h" +#include "direct.h" #define WANT_CONSOLE_LISTENER 1 @@ -101,6 +102,7 @@ int main(int argc, char *argv[]) { init_node(); init_factory(); init_queue(); + init_direct(); #if WANT_CONSOLE_LISTENER spawn(console_listener, NULL); #endif diff --git a/queue.c b/queue.c index e0ec2be..18d1a37 100644 --- a/queue.c +++ b/queue.c @@ -179,7 +179,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { cmsg_bytes_t selector; if (msglen == 0 || !sexp_stringp(sexp_head(m))) { - warn("Invalid message in factory\n"); + warn("Invalid message in queue\n"); return; } @@ -204,6 +204,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { if (!sexp_stringp(sub->sink) || !sexp_stringp(sub->name) || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { DECREF(sub->uuid, sexp_destructor); + free(sub); warn("Bad sink/name/reply_sink/reply_name in subscribe"); } else { INCREF(sub->sink); diff --git a/sexp.c b/sexp.c index 4e9b20c..ab7926d 100644 --- a/sexp.c +++ b/sexp.c @@ -48,6 +48,14 @@ static inline void release_shell(sexp_t *x) { freelist = x; } +sexp_t *sexp_incref(sexp_t *x) { + return INCREF(x); +} + +sexp_t *sexp_decref(sexp_t *x) { + return DECREF(x, sexp_destructor); +} + void sexp_data_destructor(sexp_data_t *data) { cmsg_bytes_free(data->data); free(data); diff --git a/sexp.h b/sexp.h index 664c9fb..8632079 100644 --- a/sexp.h +++ b/sexp.h @@ -35,6 +35,9 @@ extern sexp_t *sexp_empty_bytes; extern void init_sexp(void); extern void done_sexp(void); +extern sexp_t *sexp_incref(sexp_t *x); +extern sexp_t *sexp_decref(sexp_t *x); + extern void sexp_data_destructor(sexp_data_t *data); extern void sexp_destructor(sexp_t *x); diff --git a/t4 b/t4 new file mode 100644 index 0000000..a304da2 --- /dev/null +++ b/t4 @@ -0,0 +1,4 @@ +(9:subscribe5:test40:0:5:test45:login) +(4:post7:factory(6:create6:direct(2:dx)5:test41:k)0:) +(4:post2:dx(9:subscribe1:a5:test48:consumer5:test41:k)0:) +(4:post2:dx(9:subscribe1:c5:test48:consumer5:test41:k)0:) diff --git a/t5 b/t5 new file mode 100644 index 0000000..12bedce --- /dev/null +++ b/t5 @@ -0,0 +1,7 @@ +(9:subscribe5:test50:0:5:test55:login) +(4:post7:factory(6:create6:direct(2:dx)5:test51:k)0:) +(4:post7:factory(6:create5:queue(2:q5)5:test51:k)0:) +(4:post2:q5(9:subscribe0:5:test59:consumer15:test51:k)0:) +(4:post2:q5(9:subscribe0:5:test59:consumer25:test51:k)0:) +(4:post2:dx(9:subscribe1:a2:q50:5:test51:k)0:) +(4:post2:dx(9:subscribe1:b2:q50:5:test51:k)0:) diff --git a/t6 b/t6 new file mode 100644 index 0000000..bda111c --- /dev/null +++ b/t6 @@ -0,0 +1,6 @@ +(9:subscribe5:test60:0:5:test65:login) +(4:post2:dx(4:post1:a9:messageA10:)0:) +(4:post2:dx(4:post1:a9:messageA20:)0:) +(4:post2:dx(4:post1:b8:messageB0:)0:) +(4:post2:dx(4:post1:c8:messageC0:)0:) +(11:unsubscribe5:test6) From 0cab9ca4f51f3eef6a17ce899f0a6116b5b5bb6a Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 18:13:51 -0500 Subject: [PATCH 074/122] Permit hashtable_foreach iterator to remove the current entry --- hashtable.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hashtable.c b/hashtable.c index 338e5bd..c0d4e2e 100644 --- a/hashtable.c +++ b/hashtable.c @@ -132,9 +132,11 @@ void hashtable_foreach(hashtable_t *table, { int i; for (i = 0; i < table->bucket_count; i++) { - hashtable_entry_t *chain; - for (chain = table->buckets[i]; chain != NULL; chain = chain->next) { + hashtable_entry_t *chain = table->buckets[i]; + while (chain != NULL) { + hashtable_entry_t *next = chain->next; iterator(context, chain->key, chain->value); + chain = next; } } } From 34baf152e2727029c1627d930c2032802050873c Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 18:14:51 -0500 Subject: [PATCH 075/122] Fanout exchange --- Makefile | 2 +- fanout.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ fanout.h | 6 +++ main.c | 2 + 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 fanout.c create mode 100644 fanout.h diff --git a/Makefile b/Makefile index 02d14bd..5c6a650 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ TARGET = cmsg OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o \ - queue.o direct.o + queue.o direct.o fanout.o UUID_CFLAGS:=$(shell uuid-config --cflags) UUID_LDFLAGS:=$(shell uuid-config --ldflags) diff --git a/fanout.c b/fanout.c new file mode 100644 index 0000000..e60938c --- /dev/null +++ b/fanout.c @@ -0,0 +1,147 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "cmsg_private.h" +#include "harness.h" +#include "ref.h" +#include "sexp.h" +#include "hashtable.h" +#include "node.h" + +typedef struct fanout_extension_t_ { + sexp_t *name; + hashtable_t subscriptions; +} fanout_extension_t; + +typedef struct subscription_t_ { + sexp_t *uuid; + sexp_t *sink; + sexp_t *name; +} subscription_t; + +static void free_subscription(void *value) { + subscription_t *sub = value; + DECREF(sub->uuid, sexp_destructor); + DECREF(sub->sink, sexp_destructor); + DECREF(sub->name, sexp_destructor); + free(sub); +} + +static sexp_t *fanout_extend(node_t *n, sexp_t *args) { + if ((sexp_length(args) == 1) && sexp_stringp(sexp_head(args))) { + cmsg_bytes_t name = sexp_data(sexp_head(args)); + fanout_extension_t *f = calloc(1, sizeof(*f)); + f->name = INCREF(sexp_head(args)); + init_hashtable(&f->subscriptions, 5, NULL, free_subscription); + + n->extension = f; + return bind_node(name, n) ? NULL : sexp_cstring("bind failed"); + } else { + return sexp_cstring("invalid args"); + } +} + +static void fanout_destructor(node_t *n) { + fanout_extension_t *f = n->extension; + if (f != NULL) { /* can be NULL if fanout_extend was given invalid args */ + DECREF(f->name, sexp_destructor); + destroy_hashtable(&f->subscriptions); + free(f); + } +} + +struct delivery_context { + fanout_extension_t *f; + sexp_t *body; +}; + +static void send_to_sub(void *contextv, cmsg_bytes_t key, void *subv) { + struct delivery_context *context = contextv; + subscription_t *sub = subv; + if (!post_node(sexp_data(sub->sink), sexp_data(sub->name), context->body, sub->uuid)) { + hashtable_erase(&context->f->subscriptions, sexp_data(sub->uuid)); + } +} + +static void fanout_handle_message(node_t *n, sexp_t *m) { + fanout_extension_t *f = n->extension; + + size_t msglen = sexp_length(m); + sexp_t *args; + cmsg_bytes_t selector; + + if (msglen == 0 || !sexp_stringp(sexp_head(m))) { + warn("Invalid message in fanout\n"); + return; + } + + selector = sexp_data(sexp_head(m)); + args = sexp_tail(m); + + if ((msglen == 4) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post"))) { + struct delivery_context context; + context.f = f; + context.body = sexp_listref(args, 1); + hashtable_foreach(&f->subscriptions, send_to_sub, &context); + } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { + unsigned char uuid[CMSG_UUID_BUF_SIZE]; + if (gen_uuid(uuid) != 0) { + warn("Could not generate UUID\n"); + } else { + sexp_t *reply_sink = sexp_listref(args, 3); + sexp_t *reply_name = sexp_listref(args, 4); + subscription_t *sub = malloc(sizeof(*sub)); + sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); + sub->sink = sexp_listref(args, 1); + sub->name = sexp_listref(args, 2); + if (!sexp_stringp(sub->sink) || !sexp_stringp(sub->name) + || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { + DECREF(sub->uuid, sexp_destructor); + free(sub); + warn("Bad sink/name/reply_sink/reply_name in subscribe"); + } else { + INCREF(sub->sink); + INCREF(sub->name); + hashtable_put(&f->subscriptions, sexp_data(sub->uuid), sub); + { + sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); + INCREF(subok); + post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); + DECREF(subok, sexp_destructor); + } + } + } + } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { + if (sexp_stringp(sexp_head(args))) { + cmsg_bytes_t uuid = sexp_data(sexp_head(args)); + hashtable_erase(&f->subscriptions, uuid); + } else { + warn("Invalid unsubscription\n"); + } + } else { + warn("Message not understood in fanout; selector <<%.*s>>, length %u\n", + selector.len, selector.bytes, + msglen); + } +} + +static node_class_t fanout_class = { + .name = "fanout", + .extend = fanout_extend, + .destroy = fanout_destructor, + .handle_message = fanout_handle_message +}; + +void init_fanout(void) { + register_node_class(&fanout_class); +} diff --git a/fanout.h b/fanout.h new file mode 100644 index 0000000..0c73a39 --- /dev/null +++ b/fanout.h @@ -0,0 +1,6 @@ +#ifndef cmsg_fanout_h +#define cmsg_fanout_h + +extern void init_fanout(void); + +#endif diff --git a/main.c b/main.c index 4a5a55f..cb55f9c 100644 --- a/main.c +++ b/main.c @@ -22,6 +22,7 @@ typedef unsigned char u_char; #include "node.h" #include "queue.h" #include "direct.h" +#include "fanout.h" #define WANT_CONSOLE_LISTENER 1 @@ -103,6 +104,7 @@ int main(int argc, char *argv[]) { init_factory(); init_queue(); init_direct(); + init_fanout(); #if WANT_CONSOLE_LISTENER spawn(console_listener, NULL); #endif From 6720a6996ffbe23636bf498f0e0f8a91d02df8e3 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 18:15:02 -0500 Subject: [PATCH 076/122] TODO file --- TODO | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000..a4e003a --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ +Cope with possibility of duplicate uuid, in e.g. queue/fanout/direct. +If a subscription token matches an existing subscription, there's a +collision, so choose a new token until there's no collision. + +Factor out commonality in subscription-management from queue/fanout/direct. From 62ff086c7bcacc10f16c7a5e0dcda7cdfe3fc51d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 18:20:08 -0500 Subject: [PATCH 077/122] Note re SAX-style sexp reader --- TODO | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO b/TODO index a4e003a..8d33684 100644 --- a/TODO +++ b/TODO @@ -3,3 +3,6 @@ If a subscription token matches an existing subscription, there's a collision, so choose a new token until there's no collision. Factor out commonality in subscription-management from queue/fanout/direct. + +SAX-style sexp reader/writer, so that we can do something sensible for +enormous (e.g. gigabyte-sized) messages. From 0cdf9f6e686232d60201129b00f87c9242a900a1 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 21:23:43 -0500 Subject: [PATCH 078/122] Factor out commonality in subscription management --- Makefile | 2 +- TODO | 2 - direct.c | 99 ++++++------------------------------- fanout.c | 56 ++------------------- queue.c | 74 +++------------------------ subscription.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ subscription.h | 24 +++++++++ 7 files changed, 184 insertions(+), 205 deletions(-) create mode 100644 subscription.c create mode 100644 subscription.h diff --git a/Makefile b/Makefile index 5c6a650..2e5765d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ TARGET = cmsg OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o \ - queue.o direct.o fanout.o + queue.o direct.o fanout.o subscription.o UUID_CFLAGS:=$(shell uuid-config --cflags) UUID_LDFLAGS:=$(shell uuid-config --ldflags) diff --git a/TODO b/TODO index 8d33684..62f7b68 100644 --- a/TODO +++ b/TODO @@ -2,7 +2,5 @@ Cope with possibility of duplicate uuid, in e.g. queue/fanout/direct. If a subscription token matches an existing subscription, there's a collision, so choose a new token until there's no collision. -Factor out commonality in subscription-management from queue/fanout/direct. - SAX-style sexp reader/writer, so that we can do something sensible for enormous (e.g. gigabyte-sized) messages. diff --git a/direct.c b/direct.c index 8428b31..dfecc85 100644 --- a/direct.c +++ b/direct.c @@ -17,6 +17,7 @@ #include "sexp.h" #include "hashtable.h" #include "node.h" +#include "subscription.h" typedef struct direct_extension_t_ { sexp_t *name; @@ -24,29 +25,6 @@ typedef struct direct_extension_t_ { hashtable_t subscriptions; } direct_extension_t; -typedef struct subscription_t_ { - sexp_t *uuid; - sexp_t *sink; - sexp_t *name; - struct subscription_t_ *link; -} subscription_t; - -static void free_subscription(subscription_t *sub) { - DECREF(sub->uuid, sexp_destructor); - DECREF(sub->sink, sexp_destructor); - DECREF(sub->name, sexp_destructor); - free(sub); -} - -static void free_subscription_chain(void *context, cmsg_bytes_t key, void *value) { - subscription_t *chain = value; - while (chain != NULL) { - subscription_t *next = chain->link; - free_subscription(chain); - chain = next; - } -} - static sexp_t *direct_extend(node_t *n, sexp_t *args) { if ((sexp_length(args) == 1) && sexp_stringp(sexp_head(args))) { cmsg_bytes_t name = sexp_data(sexp_head(args)); @@ -62,38 +40,28 @@ static sexp_t *direct_extend(node_t *n, sexp_t *args) { } } +static void free_direct_chain(void *context, cmsg_bytes_t key, void *value) { + free_subscription_chain(value); +} + static void direct_destructor(node_t *n) { direct_extension_t *d = n->extension; if (d != NULL) { /* can be NULL if direct_extend was given invalid args */ DECREF(d->name, sexp_destructor); - hashtable_foreach(&d->routing_table, free_subscription_chain, NULL); + hashtable_foreach(&d->routing_table, free_direct_chain, NULL); destroy_hashtable(&d->routing_table); destroy_hashtable(&d->subscriptions); free(d); } } -static int send_to_sub(subscription_t *sub, sexp_t *body) { - return post_node(sexp_data(sub->sink), sexp_data(sub->name), body, sub->uuid); -} - static void route_message(direct_extension_t *d, sexp_t *rk, sexp_t *body) { subscription_t *chain = NULL; - subscription_t *prev = NULL; + subscription_t *newchain; hashtable_get(&d->routing_table, sexp_data(rk), (void **) &chain); - while (chain != NULL) { - subscription_t *next = chain->link; - if (!send_to_sub(chain, body)) { /* Destination no longer exists. */ - info("Destination not found\n"); - if (prev == NULL) { - hashtable_put(&d->routing_table, sexp_data(rk), chain->link); - } else { - prev->link = chain->link; - } - chain->link = NULL; - free_subscription(chain); - } - chain = next; + newchain = send_to_subscription_chain(&d->subscriptions, chain, body); + if (newchain != chain) { + hashtable_put(&d->routing_table, sexp_data(rk), newchain); } } @@ -120,51 +88,14 @@ static void direct_handle_message(node_t *n, sexp_t *m) { warn("Non-string routing key in direct\n"); } } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - unsigned char uuid[CMSG_UUID_BUF_SIZE]; - if (gen_uuid(uuid) != 0) { - warn("Could not generate UUID\n"); - } else { + subscription_t *sub = handle_subscribe_message(&d->subscriptions, args); + if (sub != NULL) { sexp_t *filter = sexp_listref(args, 0); - sexp_t *reply_sink = sexp_listref(args, 3); - sexp_t *reply_name = sexp_listref(args, 4); - subscription_t *sub = malloc(sizeof(*sub)); - sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); - sub->sink = sexp_listref(args, 1); - sub->name = sexp_listref(args, 2); - sub->link = NULL; - if (!sexp_stringp(filter) - || !sexp_stringp(sub->sink) || !sexp_stringp(sub->name) - || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { - DECREF(sub->uuid, sexp_destructor); - free(sub); - warn("Bad sink/name/reply_sink/reply_name in subscribe"); - } else { - INCREF(sub->sink); - INCREF(sub->name); - hashtable_put(&d->subscriptions, sexp_data(sub->uuid), sub); - hashtable_get(&d->routing_table, sexp_data(filter), (void **) &sub->link); - hashtable_put(&d->routing_table, sexp_data(filter), sub); - { - sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); - INCREF(subok); - post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); - DECREF(subok, sexp_destructor); - } - } + hashtable_get(&d->routing_table, sexp_data(filter), (void **) &sub->link); + hashtable_put(&d->routing_table, sexp_data(filter), sub); } } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - if (sexp_stringp(sexp_head(args))) { - cmsg_bytes_t uuid = sexp_data(sexp_head(args)); - subscription_t *sub; - if (hashtable_get(&d->subscriptions, uuid, (void **) &sub)) { - /* TODO: clean up more eagerly perhaps? */ - hashtable_erase(&d->subscriptions, uuid); - DECREF(sub->uuid, sexp_destructor); - sub->uuid = NULL; - } - } else { - warn("Invalid unsubscription\n"); - } + handle_unsubscribe_message(&d->subscriptions, args); } else { warn("Message not understood in direct; selector <<%.*s>>, length %u\n", selector.len, selector.bytes, diff --git a/fanout.c b/fanout.c index e60938c..02722d7 100644 --- a/fanout.c +++ b/fanout.c @@ -17,32 +17,19 @@ #include "sexp.h" #include "hashtable.h" #include "node.h" +#include "subscription.h" typedef struct fanout_extension_t_ { sexp_t *name; hashtable_t subscriptions; } fanout_extension_t; -typedef struct subscription_t_ { - sexp_t *uuid; - sexp_t *sink; - sexp_t *name; -} subscription_t; - -static void free_subscription(void *value) { - subscription_t *sub = value; - DECREF(sub->uuid, sexp_destructor); - DECREF(sub->sink, sexp_destructor); - DECREF(sub->name, sexp_destructor); - free(sub); -} - static sexp_t *fanout_extend(node_t *n, sexp_t *args) { if ((sexp_length(args) == 1) && sexp_stringp(sexp_head(args))) { cmsg_bytes_t name = sexp_data(sexp_head(args)); fanout_extension_t *f = calloc(1, sizeof(*f)); f->name = INCREF(sexp_head(args)); - init_hashtable(&f->subscriptions, 5, NULL, free_subscription); + init_hashtable(&f->subscriptions, 5, NULL, (void (*)(void *)) free_subscription); n->extension = f; return bind_node(name, n) ? NULL : sexp_cstring("bind failed"); @@ -68,9 +55,7 @@ struct delivery_context { static void send_to_sub(void *contextv, cmsg_bytes_t key, void *subv) { struct delivery_context *context = contextv; subscription_t *sub = subv; - if (!post_node(sexp_data(sub->sink), sexp_data(sub->name), context->body, sub->uuid)) { - hashtable_erase(&context->f->subscriptions, sexp_data(sub->uuid)); - } + send_to_subscription(&context->f->subscriptions, sub, context->body); } static void fanout_handle_message(node_t *n, sexp_t *m) { @@ -94,40 +79,9 @@ static void fanout_handle_message(node_t *n, sexp_t *m) { context.body = sexp_listref(args, 1); hashtable_foreach(&f->subscriptions, send_to_sub, &context); } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - unsigned char uuid[CMSG_UUID_BUF_SIZE]; - if (gen_uuid(uuid) != 0) { - warn("Could not generate UUID\n"); - } else { - sexp_t *reply_sink = sexp_listref(args, 3); - sexp_t *reply_name = sexp_listref(args, 4); - subscription_t *sub = malloc(sizeof(*sub)); - sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); - sub->sink = sexp_listref(args, 1); - sub->name = sexp_listref(args, 2); - if (!sexp_stringp(sub->sink) || !sexp_stringp(sub->name) - || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { - DECREF(sub->uuid, sexp_destructor); - free(sub); - warn("Bad sink/name/reply_sink/reply_name in subscribe"); - } else { - INCREF(sub->sink); - INCREF(sub->name); - hashtable_put(&f->subscriptions, sexp_data(sub->uuid), sub); - { - sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); - INCREF(subok); - post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); - DECREF(subok, sexp_destructor); - } - } - } + handle_subscribe_message(&f->subscriptions, args); } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - if (sexp_stringp(sexp_head(args))) { - cmsg_bytes_t uuid = sexp_data(sexp_head(args)); - hashtable_erase(&f->subscriptions, uuid); - } else { - warn("Invalid unsubscription\n"); - } + handle_unsubscribe_message(&f->subscriptions, args); } else { warn("Message not understood in fanout; selector <<%.*s>>, length %u\n", selector.len, selector.bytes, diff --git a/queue.c b/queue.c index 18d1a37..c011a56 100644 --- a/queue.c +++ b/queue.c @@ -20,6 +20,7 @@ #include "node.h" #include "queue.h" #include "dataq.h" +#include "subscription.h" typedef struct queue_extension_t_ { sexp_t *name; @@ -30,20 +31,6 @@ typedef struct queue_extension_t_ { int shovel_awake; } queue_extension_t; -typedef struct subscription_t_ { - sexp_t *uuid; - sexp_t *sink; - sexp_t *name; - struct subscription_t_ *link; -} subscription_t; - -static void free_subscription(subscription_t *sub) { - DECREF(sub->uuid, sexp_destructor); - DECREF(sub->sink, sexp_destructor); - DECREF(sub->name, sexp_destructor); - free(sub); -} - static sexp_t *queue_extend(node_t *n, sexp_t *args) { if ((sexp_length(args) == 1) && sexp_stringp(sexp_head(args))) { cmsg_bytes_t name = sexp_data(sexp_head(args)); @@ -79,10 +66,6 @@ static void queue_destructor(node_t *n) { } } -static int send_to_waiter(subscription_t *sub, sexp_t *body) { - return post_node(sexp_data(sub->sink), sexp_data(sub->name), body, sub->uuid); -} - static void end_burst(queue_extension_t *q, size_t *burst_count_ptr, size_t total_count) { if (*burst_count_ptr > 0) { info("Queue <<%.*s>>: burst count %lu; total %lu\n", @@ -98,7 +81,6 @@ static void shoveller(void *qv) { size_t burst_count = 0; size_t total_count = 0; sexp_t *body = NULL; /* held */ - queue_t examined; subscription_t *sub = NULL; check_for_work: @@ -110,14 +92,12 @@ static void shoveller(void *qv) { } body = INCREF(sexp_dequeue(q->backlog_q)); /* held */ - examined = EMPTY_QUEUE(subscription_t, link); find_valid_waiter: if (q->waiter_q.count == 0) { //info("No waiters\n"); sexp_queue_pushback(q->backlog_q, body); DECREF(body, sexp_destructor); - q->waiter_q = examined; goto wait_and_shovel; } @@ -129,10 +109,7 @@ static void shoveller(void *qv) { sexp_data(sub->name).len, sexp_data(sub->name).bytes); */ - if ((sub->uuid == NULL) /* It has been unsubscribed. */ - || !send_to_waiter(sub, body)) { /* Destination no longer exists. */ - info((sub->uuid == NULL) ? "Waiter was unsubscribed\n" : "Destination not found\n"); - free_subscription(sub); + if (!send_to_subscription(&q->subscriptions, sub, body)) { goto find_valid_waiter; } @@ -141,7 +118,6 @@ static void shoveller(void *qv) { //info("Delivery successful\n"); DECREF(body, sexp_destructor); - queue_append(&q->waiter_q, &examined); enqueue(&q->waiter_q, sub); if (burst_count >= 10000) { @@ -190,49 +166,13 @@ static void queue_handle_message(node_t *n, sexp_t *m) { sexp_enqueue(q->backlog_q, sexp_listref(args, 1)); throck_shovel(q); } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - unsigned char uuid[CMSG_UUID_BUF_SIZE]; - if (gen_uuid(uuid) != 0) { - warn("Could not generate UUID\n"); - } else { - sexp_t *reply_sink = sexp_listref(args, 3); - sexp_t *reply_name = sexp_listref(args, 4); - subscription_t *sub = malloc(sizeof(*sub)); - sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); - sub->sink = sexp_listref(args, 1); - sub->name = sexp_listref(args, 2); - sub->link = NULL; - if (!sexp_stringp(sub->sink) || !sexp_stringp(sub->name) - || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { - DECREF(sub->uuid, sexp_destructor); - free(sub); - warn("Bad sink/name/reply_sink/reply_name in subscribe"); - } else { - INCREF(sub->sink); - INCREF(sub->name); - hashtable_put(&q->subscriptions, sexp_data(sub->uuid), sub); - enqueue(&q->waiter_q, sub); - throck_shovel(q); - { - sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); - INCREF(subok); - post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); - DECREF(subok, sexp_destructor); - } - } + subscription_t *sub = handle_subscribe_message(&q->subscriptions, args); + if (sub != NULL) { + enqueue(&q->waiter_q, sub); + throck_shovel(q); } } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - if (sexp_stringp(sexp_head(args))) { - cmsg_bytes_t uuid = sexp_data(sexp_head(args)); - subscription_t *sub; - if (hashtable_get(&q->subscriptions, uuid, (void **) &sub)) { - /* TODO: clean up more eagerly perhaps? */ - hashtable_erase(&q->subscriptions, uuid); - DECREF(sub->uuid, sexp_destructor); - sub->uuid = NULL; - } - } else { - warn("Invalid unsubscription\n"); - } + handle_unsubscribe_message(&q->subscriptions, args); } else { warn("Message not understood in queue; selector <<%.*s>>, length %u\n", selector.len, selector.bytes, diff --git a/subscription.c b/subscription.c new file mode 100644 index 0000000..3f71981 --- /dev/null +++ b/subscription.c @@ -0,0 +1,132 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "cmsg_private.h" +#include "ref.h" +#include "sexp.h" +#include "hashtable.h" +#include "subscription.h" +#include "node.h" + +void free_subscription(subscription_t *sub) { + DECREF(sub->uuid, sexp_destructor); + DECREF(sub->sink, sexp_destructor); + DECREF(sub->name, sexp_destructor); + free(sub); +} + +void free_subscription_chain(subscription_t *chain) { + while (chain != NULL) { + subscription_t *next = chain->link; + free_subscription(chain); + chain = next; + } +} + +/* Returns true if the subscription has not been unsubscribed and the + destination of the subscription exists. */ +int send_to_subscription(hashtable_t *subscriptions, + subscription_t *sub, + sexp_t *body) +{ + if (sub->uuid == NULL) { + free_subscription(sub); + return 0; + } else if (!post_node(sexp_data(sub->sink), sexp_data(sub->name), body, sub->uuid)) { + hashtable_erase(subscriptions, sexp_data(sub->uuid)); + free_subscription(sub); + return 0; + } else { + return 1; + } +} + +subscription_t *send_to_subscription_chain(hashtable_t *subscriptions, + subscription_t *chain, + sexp_t *body) +{ + subscription_t *top = chain; + subscription_t *prev = NULL; + while (chain != NULL) { + subscription_t *next = chain->link; + if (!send_to_subscription(subscriptions, chain, body)) { + if (prev == NULL) { + top = next; + } else { + prev->link = next; + } + } + prev = chain; + chain = next; + } + return top; +} + +subscription_t *handle_subscribe_message(hashtable_t *subscriptions, sexp_t *args) { + unsigned char uuid[CMSG_UUID_BUF_SIZE]; + if (gen_uuid(uuid) != 0) { + warn("Could not generate UUID\n"); + return NULL; + } else { + sexp_t *filter = sexp_listref(args, 0); + sexp_t *reply_sink = sexp_listref(args, 3); + sexp_t *reply_name = sexp_listref(args, 4); + subscription_t *sub = malloc(sizeof(*sub)); + + sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); + sub->sink = sexp_listref(args, 1); + sub->name = sexp_listref(args, 2); + sub->link = NULL; + + if (!sexp_stringp(filter) + || !sexp_stringp(sub->sink) || !sexp_stringp(sub->name) + || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { + DECREF(sub->uuid, sexp_destructor); + free(sub); + warn("Bad sink/name/reply_sink/reply_name in subscribe"); + return NULL; + } + + INCREF(sub->sink); + INCREF(sub->name); + + hashtable_put(subscriptions, sexp_data(sub->uuid), sub); + + { + sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); + INCREF(subok); + post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); + DECREF(subok, sexp_destructor); + } + + return sub; + } +} + +void handle_unsubscribe_message(hashtable_t *subscriptions, sexp_t *args) { + cmsg_bytes_t uuid; + subscription_t *sub; + + if (!sexp_stringp(sexp_head(args))) { + warn("Invalid unsubscription\n"); + return; + } + + uuid = sexp_data(sexp_head(args)); + if (hashtable_get(subscriptions, uuid, (void **) &sub)) { + /* TODO: clean up more eagerly perhaps? */ + DECREF(sub->uuid, sexp_destructor); + sub->uuid = NULL; + hashtable_erase(subscriptions, uuid); + } +} diff --git a/subscription.h b/subscription.h new file mode 100644 index 0000000..e6ea884 --- /dev/null +++ b/subscription.h @@ -0,0 +1,24 @@ +#ifndef cmsg_subscription_h +#define cmsg_subscription_h + +typedef struct subscription_t_ { + sexp_t *uuid; + sexp_t *sink; + sexp_t *name; + struct subscription_t_ *link; +} subscription_t; + +extern void free_subscription(subscription_t *sub); +extern void free_subscription_chain(subscription_t *chain); + +extern int send_to_subscription(hashtable_t *subscriptions, + subscription_t *sub, + sexp_t *body); +extern subscription_t *send_to_subscription_chain(hashtable_t *subscriptions, + subscription_t *chain, + sexp_t *body); + +extern subscription_t *handle_subscribe_message(hashtable_t *subscriptions, sexp_t *args); +extern void handle_unsubscribe_message(hashtable_t *subscriptions, sexp_t *args); + +#endif From d09902fb0669542fb8be0072797daf6e0173e8e3 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 22:08:30 -0500 Subject: [PATCH 079/122] Rearrange message dispatch procedures slightly --- direct.c | 21 ++++++++++++++------- fanout.c | 23 +++++++++++++++-------- main.c | 12 ++++++------ queue.c | 21 ++++++++++++++------- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/direct.c b/direct.c index dfecc85..3162416 100644 --- a/direct.c +++ b/direct.c @@ -87,20 +87,27 @@ static void direct_handle_message(node_t *n, sexp_t *m) { } else { warn("Non-string routing key in direct\n"); } - } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { + return; + } + + if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { subscription_t *sub = handle_subscribe_message(&d->subscriptions, args); if (sub != NULL) { sexp_t *filter = sexp_listref(args, 0); hashtable_get(&d->routing_table, sexp_data(filter), (void **) &sub->link); hashtable_put(&d->routing_table, sexp_data(filter), sub); } - } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(&d->subscriptions, args); - } else { - warn("Message not understood in direct; selector <<%.*s>>, length %u\n", - selector.len, selector.bytes, - msglen); + return; } + + if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { + handle_unsubscribe_message(&d->subscriptions, args); + return; + } + + warn("Message not understood in direct; selector <<%.*s>>, length %u\n", + selector.len, selector.bytes, + msglen); } static node_class_t direct_class = { diff --git a/fanout.c b/fanout.c index 02722d7..4f65dda 100644 --- a/fanout.c +++ b/fanout.c @@ -78,15 +78,22 @@ static void fanout_handle_message(node_t *n, sexp_t *m) { context.f = f; context.body = sexp_listref(args, 1); hashtable_foreach(&f->subscriptions, send_to_sub, &context); - } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - handle_subscribe_message(&f->subscriptions, args); - } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(&f->subscriptions, args); - } else { - warn("Message not understood in fanout; selector <<%.*s>>, length %u\n", - selector.len, selector.bytes, - msglen); + return; } + + if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { + handle_subscribe_message(&f->subscriptions, args); + return; + } + + if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { + handle_unsubscribe_message(&f->subscriptions, args); + return; + } + + warn("Message not understood in fanout; selector <<%.*s>>, length %u\n", + selector.len, selector.bytes, + msglen); } static node_class_t fanout_class = { diff --git a/main.c b/main.c index cb55f9c..78f4ae7 100644 --- a/main.c +++ b/main.c @@ -39,8 +39,7 @@ static void factory_handle_message(node_t *n, sexp_t *m) { selector = sexp_data(sexp_head(m)); args = sexp_tail(m); - if ((msglen == 5) - && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("create"))) { + if ((msglen == 5) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("create"))) { sexp_t *classname = sexp_listref(args, 0); sexp_t *ctor_arg = sexp_listref(args, 1); sexp_t *reply_sink = sexp_listref(args, 2); @@ -63,11 +62,12 @@ static void factory_handle_message(node_t *n, sexp_t *m) { DECREF(reply, sexp_destructor); } } - } else { - warn("Message not understood in factory; selector <<%.*s>>, length %u\n", - selector.len, selector.bytes, - msglen); + return; } + + warn("Message not understood in factory; selector <<%.*s>>, length %u\n", + selector.len, selector.bytes, + msglen); } static node_class_t factory_class = { diff --git a/queue.c b/queue.c index c011a56..14852f2 100644 --- a/queue.c +++ b/queue.c @@ -165,19 +165,26 @@ static void queue_handle_message(node_t *n, sexp_t *m) { if ((msglen == 4) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post"))) { sexp_enqueue(q->backlog_q, sexp_listref(args, 1)); throck_shovel(q); - } else if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { + return; + } + + if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { subscription_t *sub = handle_subscribe_message(&q->subscriptions, args); if (sub != NULL) { enqueue(&q->waiter_q, sub); throck_shovel(q); } - } else if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(&q->subscriptions, args); - } else { - warn("Message not understood in queue; selector <<%.*s>>, length %u\n", - selector.len, selector.bytes, - msglen); + return; } + + if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { + handle_unsubscribe_message(&q->subscriptions, args); + return; + } + + warn("Message not understood in queue; selector <<%.*s>>, length %u\n", + selector.len, selector.bytes, + msglen); } static node_class_t queue_class = { From b40930997cf87cd3f494e7b8ce1c3bf82e5d2edd Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 22:45:44 -0500 Subject: [PATCH 080/122] Minor cleanups in relay.c --- relay.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/relay.c b/relay.c index c9268e5..d6614ba 100644 --- a/relay.c +++ b/relay.c @@ -31,6 +31,8 @@ typedef unsigned char u_char; #include "hashtable.h" #include "node.h" +#define WANT_MESSAGE_TRACE 0 + typedef struct relay_extension_t_ { struct sockaddr_in peername; char peername_str[256]; @@ -59,10 +61,10 @@ static void relay_destructor(node_t *n) { static void relay_handle_message(node_t *n, sexp_t *m) { relay_extension_t *r = n->extension; - /* +#if WANT_MESSAGE_TRACE info("fd %d <-- ", r->fd); sexp_writeln(stderr_h, m); - */ +#endif BCHECK(!sexp_write(r->outh, m), "relay_handle_message sexp_write"); } @@ -112,10 +114,10 @@ static void relay_main(node_t *n) { if (!sexp_read(inh, &message)) goto network_error; INCREF(message); - /* +#if WANT_MESSAGE_TRACE info("fd %d --> ", r->fd); sexp_writeln(stderr_h, message); - */ +#endif if (!(sexp_pairp(message) && sexp_stringp(sexp_head(message)))) { info("Ill-formed message\n"); @@ -143,13 +145,12 @@ static void relay_main(node_t *n) { && sexp_stringp(sexp_head(sexp_tail(sexp_tail(sexp_tail(sexp_tail(args))))))) { sexp_t *filter_sexp = sexp_head(args); cmsg_bytes_t filter = sexp_data(filter_sexp); - sexp_t *reply_sink_and_name = sexp_tail(sexp_tail(sexp_tail(args))); - cmsg_bytes_t reply_sink = sexp_data(sexp_head(reply_sink_and_name)); - cmsg_bytes_t reply_name = sexp_data(sexp_head(sexp_tail(reply_sink_and_name))); + sexp_t *reply_sink = sexp_listref(args, 3); + sexp_t *reply_name = sexp_listref(args, 4); if (bind_node(filter, n)) { sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(filter_sexp, NULL)); INCREF(subok); - post_node(reply_sink, reply_name, subok, sexp_empty_bytes); + post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); DECREF(subok, sexp_destructor); } else { warn("Bind failed <<%.*s>>\n", filter.len, filter.bytes); From a2aae0e93847839cb349ca6b9ee063fdfd4d951c Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 2 Jan 2011 22:46:48 -0500 Subject: [PATCH 081/122] First stab at "meta" exchange. --- Makefile | 2 +- TODO | 7 +++++++ direct.c | 6 +++--- fanout.c | 6 +++--- main.c | 2 ++ meta.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ meta.h | 12 ++++++++++++ node.c | 14 ++++++++++++++ queue.c | 6 +++--- subscription.c | 30 ++++++++++++++++++++++-------- subscription.h | 16 ++++++++++++---- t0 | 2 ++ 12 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 meta.c create mode 100644 meta.h create mode 100644 t0 diff --git a/Makefile b/Makefile index 2e5765d..b8ce632 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ TARGET = cmsg OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o \ - queue.o direct.o fanout.o subscription.o + queue.o direct.o fanout.o subscription.o meta.o UUID_CFLAGS:=$(shell uuid-config --cflags) UUID_LDFLAGS:=$(shell uuid-config --ldflags) diff --git a/TODO b/TODO index 62f7b68..d2505fc 100644 --- a/TODO +++ b/TODO @@ -4,3 +4,10 @@ collision, so choose a new token until there's no collision. SAX-style sexp reader/writer, so that we can do something sensible for enormous (e.g. gigabyte-sized) messages. + +The "meta" exchange probably wants to be a topic exchange. Or +something. + +The "meta" exchange probably wants to emit how-things-are-now messages +when people subscribe to it, to get them started; a kind of +last-value-cache type thing. diff --git a/direct.c b/direct.c index 3162416..c9d4532 100644 --- a/direct.c +++ b/direct.c @@ -59,7 +59,7 @@ static void route_message(direct_extension_t *d, sexp_t *rk, sexp_t *body) { subscription_t *chain = NULL; subscription_t *newchain; hashtable_get(&d->routing_table, sexp_data(rk), (void **) &chain); - newchain = send_to_subscription_chain(&d->subscriptions, chain, body); + newchain = send_to_subscription_chain(d->name, &d->subscriptions, chain, body); if (newchain != chain) { hashtable_put(&d->routing_table, sexp_data(rk), newchain); } @@ -91,7 +91,7 @@ static void direct_handle_message(node_t *n, sexp_t *m) { } if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - subscription_t *sub = handle_subscribe_message(&d->subscriptions, args); + subscription_t *sub = handle_subscribe_message(d->name, &d->subscriptions, args); if (sub != NULL) { sexp_t *filter = sexp_listref(args, 0); hashtable_get(&d->routing_table, sexp_data(filter), (void **) &sub->link); @@ -101,7 +101,7 @@ static void direct_handle_message(node_t *n, sexp_t *m) { } if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(&d->subscriptions, args); + handle_unsubscribe_message(d->name, &d->subscriptions, args); return; } diff --git a/fanout.c b/fanout.c index 4f65dda..25753bf 100644 --- a/fanout.c +++ b/fanout.c @@ -55,7 +55,7 @@ struct delivery_context { static void send_to_sub(void *contextv, cmsg_bytes_t key, void *subv) { struct delivery_context *context = contextv; subscription_t *sub = subv; - send_to_subscription(&context->f->subscriptions, sub, context->body); + send_to_subscription(context->f->name, &context->f->subscriptions, sub, context->body); } static void fanout_handle_message(node_t *n, sexp_t *m) { @@ -82,12 +82,12 @@ static void fanout_handle_message(node_t *n, sexp_t *m) { } if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - handle_subscribe_message(&f->subscriptions, args); + handle_subscribe_message(f->name, &f->subscriptions, args); return; } if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(&f->subscriptions, args); + handle_unsubscribe_message(f->name, &f->subscriptions, args); return; } diff --git a/main.c b/main.c index 78f4ae7..538d89e 100644 --- a/main.c +++ b/main.c @@ -23,6 +23,7 @@ typedef unsigned char u_char; #include "queue.h" #include "direct.h" #include "fanout.h" +#include "meta.h" #define WANT_CONSOLE_LISTENER 1 @@ -105,6 +106,7 @@ int main(int argc, char *argv[]) { init_queue(); init_direct(); init_fanout(); + init_meta(); #if WANT_CONSOLE_LISTENER spawn(console_listener, NULL); #endif diff --git a/meta.c b/meta.c new file mode 100644 index 0000000..158c1b7 --- /dev/null +++ b/meta.c @@ -0,0 +1,46 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "cmsg_private.h" +#include "ref.h" +#include "sexp.h" +#include "hashtable.h" +#include "node.h" +#include "meta.h" + +static sexp_t *meta_sym = NULL; + +void init_meta(void) { + sexp_t *args; + meta_sym = INCREF(sexp_cstring("meta")); + args = INCREF(sexp_cons(meta_sym, NULL)); + new_node(lookup_node_class(cmsg_cstring_bytes("direct")), args, NULL); + DECREF(args, sexp_destructor); +} + +void announce_subscription(sexp_t *source, + sexp_t *filter, + sexp_t *sink, + sexp_t *name, + int onoff) +{ + if (meta_sym != NULL) { /* use this as a proxy for whether meta has been initialized or not */ + sexp_t *msg = NULL; + msg = sexp_cons(name, msg); + msg = sexp_cons(sink, msg); + msg = sexp_cons(filter, msg); + msg = sexp_cons(source, msg); + msg = sexp_cons(sexp_cstring(onoff ? "subscribed" : "unsubscribed"), msg); + INCREF(msg); + post_node(sexp_data(meta_sym), sexp_data(source), msg, NULL); + DECREF(msg, sexp_destructor); + } +} diff --git a/meta.h b/meta.h new file mode 100644 index 0000000..1e878b7 --- /dev/null +++ b/meta.h @@ -0,0 +1,12 @@ +#ifndef cmsg_meta_h +#define cmsg_meta_h + +extern void init_meta(void); + +extern void announce_subscription(sexp_t *source, + sexp_t *filter, + sexp_t *sink, + sexp_t *name, + int onoff); + +#endif diff --git a/node.c b/node.c index da16273..b3fc2fd 100644 --- a/node.c +++ b/node.c @@ -14,6 +14,7 @@ #include "sexpio.h" #include "hashtable.h" #include "node.h" +#include "meta.h" static hashtable_t node_class_table; static hashtable_t directory; @@ -92,13 +93,25 @@ node_t *lookup_node(cmsg_bytes_t name) { return n; } +static void announce_binding(cmsg_bytes_t name, int onoff) { + sexp_t *filter = sexp_bytes(name); + INCREF(filter); + announce_subscription(sexp_empty_bytes, filter, sexp_empty_bytes, sexp_empty_bytes, onoff); + DECREF(filter, sexp_destructor); +} + int bind_node(cmsg_bytes_t name, node_t *n) { + if (name.len == 0) { + warn("Binding to empty name forbidden\n"); + return 0; + } if (hashtable_contains(&directory, name)) { return 0; } hashtable_put(&directory, name, n); hashtable_put(&n->names, name, NULL); info("Binding node <<%.*s>> of class %s\n", name.len, name.bytes, n->node_class->name); + announce_binding(name, 1); return 1; } @@ -111,6 +124,7 @@ int unbind_node(cmsg_bytes_t name) { info("Unbinding node <<%.*s>> of class %s\n", name.len, name.bytes, n->node_class->name); hashtable_erase(&n->names, name); hashtable_erase(&directory, name); + announce_binding(name, 0); return 1; } } diff --git a/queue.c b/queue.c index 14852f2..756225c 100644 --- a/queue.c +++ b/queue.c @@ -109,7 +109,7 @@ static void shoveller(void *qv) { sexp_data(sub->name).len, sexp_data(sub->name).bytes); */ - if (!send_to_subscription(&q->subscriptions, sub, body)) { + if (!send_to_subscription(q->name, &q->subscriptions, sub, body)) { goto find_valid_waiter; } @@ -169,7 +169,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { } if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - subscription_t *sub = handle_subscribe_message(&q->subscriptions, args); + subscription_t *sub = handle_subscribe_message(q->name, &q->subscriptions, args); if (sub != NULL) { enqueue(&q->waiter_q, sub); throck_shovel(q); @@ -178,7 +178,7 @@ static void queue_handle_message(node_t *n, sexp_t *m) { } if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(&q->subscriptions, args); + handle_unsubscribe_message(q->name, &q->subscriptions, args); return; } diff --git a/subscription.c b/subscription.c index 3f71981..53482cd 100644 --- a/subscription.c +++ b/subscription.c @@ -17,9 +17,11 @@ #include "hashtable.h" #include "subscription.h" #include "node.h" +#include "meta.h" void free_subscription(subscription_t *sub) { DECREF(sub->uuid, sexp_destructor); + DECREF(sub->filter, sexp_destructor); DECREF(sub->sink, sexp_destructor); DECREF(sub->name, sexp_destructor); free(sub); @@ -35,7 +37,8 @@ void free_subscription_chain(subscription_t *chain) { /* Returns true if the subscription has not been unsubscribed and the destination of the subscription exists. */ -int send_to_subscription(hashtable_t *subscriptions, +int send_to_subscription(sexp_t *source, + hashtable_t *subscriptions, subscription_t *sub, sexp_t *body) { @@ -43,6 +46,7 @@ int send_to_subscription(hashtable_t *subscriptions, free_subscription(sub); return 0; } else if (!post_node(sexp_data(sub->sink), sexp_data(sub->name), body, sub->uuid)) { + announce_subscription(source, sub->filter, sub->sink, sub->name, 0); hashtable_erase(subscriptions, sexp_data(sub->uuid)); free_subscription(sub); return 0; @@ -51,7 +55,8 @@ int send_to_subscription(hashtable_t *subscriptions, } } -subscription_t *send_to_subscription_chain(hashtable_t *subscriptions, +subscription_t *send_to_subscription_chain(sexp_t *source, + hashtable_t *subscriptions, subscription_t *chain, sexp_t *body) { @@ -59,7 +64,7 @@ subscription_t *send_to_subscription_chain(hashtable_t *subscriptions, subscription_t *prev = NULL; while (chain != NULL) { subscription_t *next = chain->link; - if (!send_to_subscription(subscriptions, chain, body)) { + if (!send_to_subscription(source, subscriptions, chain, body)) { if (prev == NULL) { top = next; } else { @@ -72,24 +77,26 @@ subscription_t *send_to_subscription_chain(hashtable_t *subscriptions, return top; } -subscription_t *handle_subscribe_message(hashtable_t *subscriptions, sexp_t *args) { +subscription_t *handle_subscribe_message(sexp_t *source, + hashtable_t *subscriptions, + sexp_t *args) +{ unsigned char uuid[CMSG_UUID_BUF_SIZE]; if (gen_uuid(uuid) != 0) { warn("Could not generate UUID\n"); return NULL; } else { - sexp_t *filter = sexp_listref(args, 0); sexp_t *reply_sink = sexp_listref(args, 3); sexp_t *reply_name = sexp_listref(args, 4); subscription_t *sub = malloc(sizeof(*sub)); sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); + sub->filter = sexp_listref(args, 0); sub->sink = sexp_listref(args, 1); sub->name = sexp_listref(args, 2); sub->link = NULL; - if (!sexp_stringp(filter) - || !sexp_stringp(sub->sink) || !sexp_stringp(sub->name) + if (!sexp_stringp(sub->filter) || !sexp_stringp(sub->sink) || !sexp_stringp(sub->name) || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { DECREF(sub->uuid, sexp_destructor); free(sub); @@ -97,11 +104,14 @@ subscription_t *handle_subscribe_message(hashtable_t *subscriptions, sexp_t *arg return NULL; } + INCREF(sub->filter); INCREF(sub->sink); INCREF(sub->name); hashtable_put(subscriptions, sexp_data(sub->uuid), sub); + announce_subscription(source, sub->filter, sub->sink, sub->name, 1); + { sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); INCREF(subok); @@ -113,7 +123,10 @@ subscription_t *handle_subscribe_message(hashtable_t *subscriptions, sexp_t *arg } } -void handle_unsubscribe_message(hashtable_t *subscriptions, sexp_t *args) { +void handle_unsubscribe_message(sexp_t *source, + hashtable_t *subscriptions, + sexp_t *args) +{ cmsg_bytes_t uuid; subscription_t *sub; @@ -125,6 +138,7 @@ void handle_unsubscribe_message(hashtable_t *subscriptions, sexp_t *args) { uuid = sexp_data(sexp_head(args)); if (hashtable_get(subscriptions, uuid, (void **) &sub)) { /* TODO: clean up more eagerly perhaps? */ + announce_subscription(source, sub->filter, sub->sink, sub->name, 0); DECREF(sub->uuid, sexp_destructor); sub->uuid = NULL; hashtable_erase(subscriptions, uuid); diff --git a/subscription.h b/subscription.h index e6ea884..9b5aeec 100644 --- a/subscription.h +++ b/subscription.h @@ -3,6 +3,7 @@ typedef struct subscription_t_ { sexp_t *uuid; + sexp_t *filter; sexp_t *sink; sexp_t *name; struct subscription_t_ *link; @@ -11,14 +12,21 @@ typedef struct subscription_t_ { extern void free_subscription(subscription_t *sub); extern void free_subscription_chain(subscription_t *chain); -extern int send_to_subscription(hashtable_t *subscriptions, +extern int send_to_subscription(sexp_t *source, + hashtable_t *subscriptions, subscription_t *sub, sexp_t *body); -extern subscription_t *send_to_subscription_chain(hashtable_t *subscriptions, +extern subscription_t *send_to_subscription_chain(sexp_t *source, + hashtable_t *subscriptions, subscription_t *chain, sexp_t *body); -extern subscription_t *handle_subscribe_message(hashtable_t *subscriptions, sexp_t *args); -extern void handle_unsubscribe_message(hashtable_t *subscriptions, sexp_t *args); +extern subscription_t *handle_subscribe_message(sexp_t *source, + hashtable_t *subscriptions, + sexp_t *args); + +extern void handle_unsubscribe_message(sexp_t *source, + hashtable_t *subscriptions, + sexp_t *args); #endif diff --git a/t0 b/t0 new file mode 100644 index 0000000..083b8e1 --- /dev/null +++ b/t0 @@ -0,0 +1,2 @@ +(9:subscribe5:test00:0:5:test05:login) +(4:post4:meta(9:subscribe0:5:test08:presence5:test01:k)0:) From fff756b9ff6125e303330dd3703bd3d147e976a7 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 4 Jan 2011 12:28:52 -0500 Subject: [PATCH 082/122] Note re website --- TODO | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO b/TODO index d2505fc..3901da6 100644 --- a/TODO +++ b/TODO @@ -11,3 +11,6 @@ something. The "meta" exchange probably wants to emit how-things-are-now messages when people subscribe to it, to get them started; a kind of last-value-cache type thing. + +Website + - http://www.flickr.com/photos/elemishra/158211069/ From e519fae8821dc9a89f6629c17ccc6ca3131d30df Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 4 Jan 2011 19:21:20 -0500 Subject: [PATCH 083/122] Awful horrible very bad latency test code --- Makefile | 2 +- test1_latency.c | 120 ++++++++++++++++++++++++++++++++++++++++++ test3_latency.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 test1_latency.c create mode 100644 test3_latency.c diff --git a/Makefile b/Makefile index b8ce632..5238fcf 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ clean: rm -f $(OBJECTS) rm -rf *.dSYM rm -f depend.mk - rm -f test1 test1.o test3 test3.o + rm -f test{1,3}{,_latency}{,.o} depend.mk: gcc $(CFLAGS) -M *.c > $@ diff --git a/test1_latency.c b/test1_latency.c new file mode 100644 index 0000000..f801853 --- /dev/null +++ b/test1_latency.c @@ -0,0 +1,120 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define EXPECTEDPREFIX "(4:post8:consumer8:" + +static void hunt_for_latencies_in(char *buf, size_t count) { + struct timeval now; + char *pos = buf; + char *sentinel = buf + count; + + gettimeofday(&now, NULL); + + while (1) { + char *openptr = memchr(pos, '(', sentinel - pos); + char *closeptr; + uint32_t s, us; + + if (openptr == NULL) break; + + closeptr = memchr(openptr + 1, ')', sentinel - (openptr + 1)); + if (closeptr == NULL) break; + + memcpy(&s, openptr + strlen(EXPECTEDPREFIX), sizeof(uint32_t)); + memcpy(&us, openptr + strlen(EXPECTEDPREFIX) + sizeof(uint32_t), sizeof(uint32_t)); + s = ntohl(s); + us = ntohl(us); + + if (s != 0 || us != 0) { + double delta = (now.tv_sec - s) * 1000000.0 + (now.tv_usec - us); + printf("Latency %g microseconds (%g milliseconds)\n", delta, delta / 1000.0); + } + + pos = closeptr + 1; + } +} + +int main(int argc, char *argv[]) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in s; + FILE *f; + struct timeval start_time; + long bytecount = -1; + + if (argc < 2) { + fprintf(stderr, "Usage: test1 \n"); + exit(1); + } + + { + struct hostent *h = gethostbyname(argv[1]); + if (h == NULL) { + fprintf(stderr, "serverhostname lookup: %d\n", h_errno); + exit(1); + } + s.sin_family = AF_INET; + s.sin_addr.s_addr = * (uint32_t *) h->h_addr_list[0]; + s.sin_port = htons(5671); + } + + if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; + + f = fdopen(fd, "a+"); + + fprintf(f, "(9:subscribe5:test10:0:5:test15:login)(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:)(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:)\n"); + fflush(f); + +#define PAYLOADSIZE 8 +#define MESSAGESIZE 59 + PAYLOADSIZE /* 59by overhead, incl 36by subscription token (!) */ + + while (1) { + char buf[1024]; + size_t n = read(fd, buf, sizeof(buf)); + if (n == 0) break; + if (n >= strlen(EXPECTEDPREFIX)) { + if (!memcmp(buf, EXPECTEDPREFIX, strlen(EXPECTEDPREFIX))) { + if (bytecount == -1) { + printf("Buffer at start: <<%.*s>>\n", (int) n, buf); + printf("Starting.\n"); + bytecount = 0; + gettimeofday(&start_time, NULL); + } + } + } + if (bytecount >= 0) { + hunt_for_latencies_in(buf, n); + bytecount += n; + if ((bytecount % 100000) < MESSAGESIZE) { + struct timeval now; + double delta; + gettimeofday(&now, NULL); + delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; + printf("So far received %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", + bytecount, + delta, + bytecount / delta, + bytecount / (delta * MESSAGESIZE)); + fflush(stdout); + } + } + } + + return 0; +} diff --git a/test3_latency.c b/test3_latency.c new file mode 100644 index 0000000..981644d --- /dev/null +++ b/test3_latency.c @@ -0,0 +1,137 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static size_t build_message(char *message, uint32_t s, uint32_t us) { + char const *msg_prefix = "(4:post2:q1(4:post0:8:"; + char const *msg_suffix = "0:)0:)"; + size_t prefix_len = strlen(msg_prefix); + size_t suffix_len = strlen(msg_suffix); + uint32_t v; + size_t total_len = 0; + + memcpy(message + total_len, msg_prefix, prefix_len); + total_len += prefix_len; + v = htonl(s); + memcpy(message + total_len, &v, sizeof(uint32_t)); + total_len += sizeof(uint32_t); + v = htonl(us); + memcpy(message + total_len, &v, sizeof(uint32_t)); + total_len += sizeof(uint32_t); + memcpy(message + total_len, msg_suffix, suffix_len); + total_len += suffix_len; + + /* + printf("%d<<", total_len); + fwrite(message, total_len, 1, stdout); + printf(">>\n"); + */ + return total_len; +} + +int main(int argc, char *argv[]) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in s; + FILE *f; + struct timeval start_time; + long bytecount = 0; + int i; + unsigned long hz_limit = 1000000; + + assert(sizeof(uint32_t) == 4); + + if (argc < 2) { + fprintf(stderr, "Usage: test1 []\n"); + exit(1); + } + + if (argc > 2) { + hz_limit = strtoul(argv[2], NULL, 0); + } + printf("hz_limit = %lu\n", hz_limit); + + { + struct hostent *h = gethostbyname(argv[1]); + if (h == NULL) { + fprintf(stderr, "serverhostname lookup: %d\n", h_errno); + exit(1); + } + s.sin_family = AF_INET; + s.sin_addr.s_addr = * (uint32_t *) h->h_addr_list[0]; + s.sin_port = htons(5671); + } + + if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; + + f = fdopen(fd, "a+"); + + fprintf(f, "(9:subscribe5:test30:0:5:test35:login)"); + fflush(f); + + usleep(100000); + { + char buf[4096]; + size_t n = read(fd, buf, sizeof(buf)); + printf("Received: <<%.*s>>\n", (int) n, buf); + } + + gettimeofday(&start_time, NULL); + + for (i = 0; i < 10000000; i++) { + char message[1024]; + size_t msglen; + while (1) { + struct timeval now; + double delta; + gettimeofday(&now, NULL); + delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; + if (i / delta <= hz_limit) break; + fflush(f); + usleep(1000); + } + if ((i % (hz_limit / 4)) == 0) { + struct timeval now; + gettimeofday(&now, NULL); + msglen = build_message(message, now.tv_sec, now.tv_usec); + } else { + msglen = build_message(message, 0, 0); + } + fwrite(message, msglen, 1, f); + bytecount += msglen; + if ((bytecount % 100000) < msglen) { + struct timeval now; + double delta; + gettimeofday(&now, NULL); + delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; + printf("So far sent %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", + bytecount, + delta, + bytecount / delta, + bytecount / (delta * msglen)); + fflush(stdout); + } + } + + fprintf(f, "(11:unsubscribe5:test3)"); + fflush(f); + + fclose(f); + + return 0; +} From e4697046977601420f195d641a49f0bcd81c844a Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 4 Jan 2011 19:24:29 -0500 Subject: [PATCH 084/122] Don't print burst reports; they're too noisy with low message rates --- queue.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/queue.c b/queue.c index 756225c..3017538 100644 --- a/queue.c +++ b/queue.c @@ -67,11 +67,13 @@ static void queue_destructor(node_t *n) { } static void end_burst(queue_extension_t *q, size_t *burst_count_ptr, size_t total_count) { +#if 0 if (*burst_count_ptr > 0) { info("Queue <<%.*s>>: burst count %lu; total %lu\n", sexp_data(q->name).len, sexp_data(q->name).bytes, *burst_count_ptr, total_count); } +#endif *burst_count_ptr = 0; } From 58605c35481efd686ecfea57eba5e595485d2ee7 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 4 Jan 2011 19:34:24 -0500 Subject: [PATCH 085/122] Better reporting in receiver --- test1.c | 4 +++- test1_latency.c | 43 +++++++++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/test1.c b/test1.c index e0cd37b..2e0d791 100644 --- a/test1.c +++ b/test1.c @@ -24,6 +24,7 @@ int main(int argc, char *argv[]) { FILE *f; struct timeval start_time; long bytecount = -1; + long last_report_bytecount = 0; if (argc < 2) { fprintf(stderr, "Usage: test1 \n"); @@ -66,7 +67,7 @@ int main(int argc, char *argv[]) { } if (bytecount >= 0) { bytecount += n; - if ((bytecount % 100000) < MESSAGESIZE) { + if ((bytecount - last_report_bytecount) > (100000 * MESSAGESIZE)) { struct timeval now; double delta; gettimeofday(&now, NULL); @@ -77,6 +78,7 @@ int main(int argc, char *argv[]) { bytecount / delta, bytecount / (delta * MESSAGESIZE)); fflush(stdout); + last_report_bytecount = bytecount; } } } diff --git a/test1_latency.c b/test1_latency.c index f801853..2d713a3 100644 --- a/test1_latency.c +++ b/test1_latency.c @@ -20,10 +20,11 @@ #define EXPECTEDPREFIX "(4:post8:consumer8:" -static void hunt_for_latencies_in(char *buf, size_t count) { +static size_t hunt_for_latencies_in(char *buf, size_t count) { struct timeval now; char *pos = buf; char *sentinel = buf + count; + size_t msgsize = 0; gettimeofday(&now, NULL); @@ -47,8 +48,12 @@ static void hunt_for_latencies_in(char *buf, size_t count) { printf("Latency %g microseconds (%g milliseconds)\n", delta, delta / 1000.0); } + msgsize = closeptr + 1 - openptr; + pos = closeptr + 1; } + + return msgsize; } int main(int argc, char *argv[]) { @@ -57,6 +62,8 @@ int main(int argc, char *argv[]) { FILE *f; struct timeval start_time; long bytecount = -1; + size_t message_size = 0; + long last_report_bytecount = 0; if (argc < 2) { fprintf(stderr, "Usage: test1 \n"); @@ -81,9 +88,6 @@ int main(int argc, char *argv[]) { fprintf(f, "(9:subscribe5:test10:0:5:test15:login)(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:)(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:)\n"); fflush(f); -#define PAYLOADSIZE 8 -#define MESSAGESIZE 59 + PAYLOADSIZE /* 59by overhead, incl 36by subscription token (!) */ - while (1) { char buf[1024]; size_t n = read(fd, buf, sizeof(buf)); @@ -99,19 +103,26 @@ int main(int argc, char *argv[]) { } } if (bytecount >= 0) { - hunt_for_latencies_in(buf, n); + size_t detected_msgsize = hunt_for_latencies_in(buf, n); bytecount += n; - if ((bytecount % 100000) < MESSAGESIZE) { - struct timeval now; - double delta; - gettimeofday(&now, NULL); - delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; - printf("So far received %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", - bytecount, - delta, - bytecount / delta, - bytecount / (delta * MESSAGESIZE)); - fflush(stdout); + if (detected_msgsize != 0 && message_size == 0) { + message_size = detected_msgsize; + printf("message_size = %lu\n", message_size); + } + if (message_size != 0) { + if ((bytecount - last_report_bytecount) > (100000 * message_size)) { + struct timeval now; + double delta; + gettimeofday(&now, NULL); + delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; + printf("So far received %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", + bytecount, + delta, + bytecount / delta, + bytecount / (delta * message_size)); + fflush(stdout); + last_report_bytecount = bytecount; + } } } } From 098b690e4d06e0a9d3b486d0d1c38c90a75a5fed Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 4 Jan 2011 20:39:11 -0500 Subject: [PATCH 086/122] For some reason the {} were not being expanded properly --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5238fcf..44ba6f9 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ clean: rm -f $(OBJECTS) rm -rf *.dSYM rm -f depend.mk - rm -f test{1,3}{,_latency}{,.o} + rm -f test1 test3 test1.o test3.o test1_latency test3_latency test1_latency.o test3_latency.o depend.mk: gcc $(CFLAGS) -M *.c > $@ From 76280ef51b6d5e41a3c24586ccf6037bce1349d9 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 5 Jan 2011 10:38:37 -0500 Subject: [PATCH 087/122] Indirect-to-direct-scheduling experiment --- TODO | 11 +++++ experiments/direct_scheduling.patch | 74 +++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 experiments/direct_scheduling.patch diff --git a/TODO b/TODO index 3901da6..45a4a5c 100644 --- a/TODO +++ b/TODO @@ -14,3 +14,14 @@ last-value-cache type thing. Website - http://www.flickr.com/photos/elemishra/158211069/ + +Switch from indirect scheduling to direct scheduling + - before, on walk: 173kHz test1/test3 + - after, no change. Probably because we only have a single thread + active in the system when running test1/test3, so every time some + work comes in, it baton-passes to the next guy in the chain. The + change *would* be an improvement but we have a separate worklist + (to process now) and runlist (to process after polling for events + once) so it always goes back to the scheduler because the worklist + is never longer than one. + - Revisit once we start testing multiple concurrent streams. diff --git a/experiments/direct_scheduling.patch b/experiments/direct_scheduling.patch new file mode 100644 index 0000000..ba52588 --- /dev/null +++ b/experiments/direct_scheduling.patch @@ -0,0 +1,74 @@ +diff --git a/harness.c b/harness.c +index 9c891b3..74061af 100644 +--- a/harness.c ++++ b/harness.c +@@ -50,18 +50,38 @@ Process *current_process = NULL; + static ucontext_t scheduler; + static queue_t runlist = EMPTY_PROCESS_QUEUE; + static queue_t deadlist = EMPTY_PROCESS_QUEUE; ++static queue_t current_worklist = EMPTY_PROCESS_QUEUE; + + static void enqueue_runlist(Process *p) { + p->state = PROCESS_RUNNING; + enqueue(&runlist, p); + } + ++static void clean_dead_processes(void) { ++ Process *deadp; ++ while ((deadp = dequeue(&deadlist)) != NULL) { ++ free(deadp->stack_base); ++ free(deadp); ++ } ++} ++ + static void schedule(void) { +- //info("schedule %p\n", current_process); + if (current_process == NULL) { + ICHECK(setcontext(&scheduler), "schedule setcontext"); + } else { +- ICHECK(swapcontext(¤t_process->context, &scheduler), "schedule swapcontext"); ++ Process *current = current_process; ++ Process *target_process = dequeue(¤t_worklist); ++ ucontext_t *target; ++ ++ if (target_process == NULL) { ++ target = &scheduler; ++ } else { ++ target = &target_process->context; ++ current_process = target_process; ++ } ++ ++ clean_dead_processes(); /* safe because we know we're not dead ourselves at this point */ ++ ICHECK(swapcontext(¤t->context, target), "schedule swapcontext"); + } + } + +@@ -255,14 +275,6 @@ void iohandle_settimeout(IOHandle *h, int timeout_read, int timeout_write) { + bufferevent_settimeout(h->io, timeout_read, timeout_write); + } + +-static void clean_dead_processes(void) { +- Process *deadp; +- while ((deadp = dequeue(&deadlist)) != NULL) { +- free(deadp->stack_base); +- free(deadp); +- } +-} +- + void boot_harness(void) { + stdin_h = new_iohandle(0); + stdout_h = new_iohandle(1); +@@ -272,10 +284,10 @@ void boot_harness(void) { + + while (1) { + while (runlist.count) { +- queue_t work = runlist; ++ current_worklist = runlist; + runlist = EMPTY_PROCESS_QUEUE; +- //info("Processing %d jobs\n", work.count); +- while ((current_process = dequeue(&work)) != NULL) { ++ //info("Processing %d jobs\n", current_worklist.count); ++ while ((current_process = dequeue(¤t_worklist)) != NULL) { + //info("entering %p\n", current_process); + ICHECK(swapcontext(&scheduler, ¤t_process->context), "boot_harness swapcontext"); + clean_dead_processes(); From 5df0c58c81807273dc934a43519f07f940ad25cb Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 5 Jan 2011 12:39:36 -0500 Subject: [PATCH 088/122] Warn about shovel only when one exists --- queue.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/queue.c b/queue.c index 3017538..0567415 100644 --- a/queue.c +++ b/queue.c @@ -61,7 +61,12 @@ static void queue_destructor(node_t *n) { } } destroy_hashtable(&q->subscriptions); - warn("TODO: the shovel needs to be taken down as well here\n"); + if (q->shovel) { + warn("TODO: the shovel needs to be taken down as well here\n"); + /* The difficulty is that the shovel may be running at the + moment, so careful ordering of operations is required to + avoid referencing deallocated memory. */ + } free(q); } } From 0ed20748382362a24ea74b31670055f9fc139beb Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 5 Jan 2011 12:46:15 -0500 Subject: [PATCH 089/122] Subdirectories --- Sexp.txt => doc/Sexp.txt | 0 Makefile => server/Makefile | 0 cmsg_private.h => server/cmsg_private.h | 0 dataq.c => server/dataq.c | 0 dataq.h => server/dataq.h | 0 direct.c => server/direct.c | 0 direct.h => server/direct.h | 0 fanout.c => server/fanout.c | 0 fanout.h => server/fanout.h | 0 harness.c => server/harness.c | 0 harness.h => server/harness.h | 0 hashtable.c => server/hashtable.c | 0 hashtable.h => server/hashtable.h | 0 main.c => server/main.c | 0 meta.c => server/meta.c | 0 meta.h => server/meta.h | 0 net.c => server/net.c | 0 net.h => server/net.h | 0 node.c => server/node.c | 0 node.h => server/node.h | 0 queue.c => server/queue.c | 0 queue.h => server/queue.h | 0 ref.h => server/ref.h | 0 relay.c => server/relay.c | 0 relay.h => server/relay.h | 0 sexp.c => server/sexp.c | 0 sexp.h => server/sexp.h | 0 sexpio.c => server/sexpio.c | 0 sexpio.h => server/sexpio.h | 0 subscription.c => server/subscription.c | 0 subscription.h => server/subscription.h | 0 test1.c => server/test1.c | 0 test1_latency.c => server/test1_latency.c | 0 test3.c => server/test3.c | 0 test3_latency.c => server/test3_latency.c | 0 util.c => server/util.c | 0 36 files changed, 0 insertions(+), 0 deletions(-) rename Sexp.txt => doc/Sexp.txt (100%) rename Makefile => server/Makefile (100%) rename cmsg_private.h => server/cmsg_private.h (100%) rename dataq.c => server/dataq.c (100%) rename dataq.h => server/dataq.h (100%) rename direct.c => server/direct.c (100%) rename direct.h => server/direct.h (100%) rename fanout.c => server/fanout.c (100%) rename fanout.h => server/fanout.h (100%) rename harness.c => server/harness.c (100%) rename harness.h => server/harness.h (100%) rename hashtable.c => server/hashtable.c (100%) rename hashtable.h => server/hashtable.h (100%) rename main.c => server/main.c (100%) rename meta.c => server/meta.c (100%) rename meta.h => server/meta.h (100%) rename net.c => server/net.c (100%) rename net.h => server/net.h (100%) rename node.c => server/node.c (100%) rename node.h => server/node.h (100%) rename queue.c => server/queue.c (100%) rename queue.h => server/queue.h (100%) rename ref.h => server/ref.h (100%) rename relay.c => server/relay.c (100%) rename relay.h => server/relay.h (100%) rename sexp.c => server/sexp.c (100%) rename sexp.h => server/sexp.h (100%) rename sexpio.c => server/sexpio.c (100%) rename sexpio.h => server/sexpio.h (100%) rename subscription.c => server/subscription.c (100%) rename subscription.h => server/subscription.h (100%) rename test1.c => server/test1.c (100%) rename test1_latency.c => server/test1_latency.c (100%) rename test3.c => server/test3.c (100%) rename test3_latency.c => server/test3_latency.c (100%) rename util.c => server/util.c (100%) diff --git a/Sexp.txt b/doc/Sexp.txt similarity index 100% rename from Sexp.txt rename to doc/Sexp.txt diff --git a/Makefile b/server/Makefile similarity index 100% rename from Makefile rename to server/Makefile diff --git a/cmsg_private.h b/server/cmsg_private.h similarity index 100% rename from cmsg_private.h rename to server/cmsg_private.h diff --git a/dataq.c b/server/dataq.c similarity index 100% rename from dataq.c rename to server/dataq.c diff --git a/dataq.h b/server/dataq.h similarity index 100% rename from dataq.h rename to server/dataq.h diff --git a/direct.c b/server/direct.c similarity index 100% rename from direct.c rename to server/direct.c diff --git a/direct.h b/server/direct.h similarity index 100% rename from direct.h rename to server/direct.h diff --git a/fanout.c b/server/fanout.c similarity index 100% rename from fanout.c rename to server/fanout.c diff --git a/fanout.h b/server/fanout.h similarity index 100% rename from fanout.h rename to server/fanout.h diff --git a/harness.c b/server/harness.c similarity index 100% rename from harness.c rename to server/harness.c diff --git a/harness.h b/server/harness.h similarity index 100% rename from harness.h rename to server/harness.h diff --git a/hashtable.c b/server/hashtable.c similarity index 100% rename from hashtable.c rename to server/hashtable.c diff --git a/hashtable.h b/server/hashtable.h similarity index 100% rename from hashtable.h rename to server/hashtable.h diff --git a/main.c b/server/main.c similarity index 100% rename from main.c rename to server/main.c diff --git a/meta.c b/server/meta.c similarity index 100% rename from meta.c rename to server/meta.c diff --git a/meta.h b/server/meta.h similarity index 100% rename from meta.h rename to server/meta.h diff --git a/net.c b/server/net.c similarity index 100% rename from net.c rename to server/net.c diff --git a/net.h b/server/net.h similarity index 100% rename from net.h rename to server/net.h diff --git a/node.c b/server/node.c similarity index 100% rename from node.c rename to server/node.c diff --git a/node.h b/server/node.h similarity index 100% rename from node.h rename to server/node.h diff --git a/queue.c b/server/queue.c similarity index 100% rename from queue.c rename to server/queue.c diff --git a/queue.h b/server/queue.h similarity index 100% rename from queue.h rename to server/queue.h diff --git a/ref.h b/server/ref.h similarity index 100% rename from ref.h rename to server/ref.h diff --git a/relay.c b/server/relay.c similarity index 100% rename from relay.c rename to server/relay.c diff --git a/relay.h b/server/relay.h similarity index 100% rename from relay.h rename to server/relay.h diff --git a/sexp.c b/server/sexp.c similarity index 100% rename from sexp.c rename to server/sexp.c diff --git a/sexp.h b/server/sexp.h similarity index 100% rename from sexp.h rename to server/sexp.h diff --git a/sexpio.c b/server/sexpio.c similarity index 100% rename from sexpio.c rename to server/sexpio.c diff --git a/sexpio.h b/server/sexpio.h similarity index 100% rename from sexpio.h rename to server/sexpio.h diff --git a/subscription.c b/server/subscription.c similarity index 100% rename from subscription.c rename to server/subscription.c diff --git a/subscription.h b/server/subscription.h similarity index 100% rename from subscription.h rename to server/subscription.h diff --git a/test1.c b/server/test1.c similarity index 100% rename from test1.c rename to server/test1.c diff --git a/test1_latency.c b/server/test1_latency.c similarity index 100% rename from test1_latency.c rename to server/test1_latency.c diff --git a/test3.c b/server/test3.c similarity index 100% rename from test3.c rename to server/test3.c diff --git a/test3_latency.c b/server/test3_latency.c similarity index 100% rename from test3_latency.c rename to server/test3_latency.c diff --git a/util.c b/server/util.c similarity index 100% rename from util.c rename to server/util.c From bfce65daf77ba9d145b88b61c63fb014ba60d0f7 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 5 Jan 2011 13:06:59 -0500 Subject: [PATCH 090/122] Copyright notices --- server/cmsg_private.h | 2 ++ server/dataq.c | 2 ++ server/dataq.h | 2 ++ server/direct.h | 2 ++ server/fanout.h | 2 ++ server/harness.h | 2 ++ server/hashtable.c | 2 ++ server/hashtable.h | 2 ++ server/meta.h | 2 ++ server/net.h | 2 ++ server/node.c | 2 ++ server/node.h | 2 ++ server/queue.h | 2 ++ server/ref.h | 2 ++ server/relay.h | 2 ++ server/sexp.c | 2 ++ server/sexp.h | 2 ++ server/sexpio.c | 2 ++ server/sexpio.h | 2 ++ server/subscription.h | 2 ++ 20 files changed, 40 insertions(+) diff --git a/server/cmsg_private.h b/server/cmsg_private.h index 0823782..6159ae9 100644 --- a/server/cmsg_private.h +++ b/server/cmsg_private.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_private_h #define cmsg_private_h diff --git a/server/dataq.c b/server/dataq.c index 6fe99a7..f3f7b8e 100644 --- a/server/dataq.c +++ b/server/dataq.c @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #include #include #include diff --git a/server/dataq.h b/server/dataq.h index e65b4d2..a0fe271 100644 --- a/server/dataq.h +++ b/server/dataq.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_dataq_h #define cmsg_dataq_h diff --git a/server/direct.h b/server/direct.h index 38b7528..8d5ffd2 100644 --- a/server/direct.h +++ b/server/direct.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_direct_h #define cmsg_direct_h diff --git a/server/fanout.h b/server/fanout.h index 0c73a39..426d137 100644 --- a/server/fanout.h +++ b/server/fanout.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_fanout_h #define cmsg_fanout_h diff --git a/server/harness.h b/server/harness.h index 552044c..f6c83c9 100644 --- a/server/harness.h +++ b/server/harness.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_harness_h #define cmsg_harness_h diff --git a/server/hashtable.c b/server/hashtable.c index c0d4e2e..4596f7f 100644 --- a/server/hashtable.c +++ b/server/hashtable.c @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #include #include #include diff --git a/server/hashtable.h b/server/hashtable.h index e8ff63f..44c378c 100644 --- a/server/hashtable.h +++ b/server/hashtable.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_hashtable_h #define cmsg_hashtable_h diff --git a/server/meta.h b/server/meta.h index 1e878b7..1cf4666 100644 --- a/server/meta.h +++ b/server/meta.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_meta_h #define cmsg_meta_h diff --git a/server/net.h b/server/net.h index b63307c..6e3e973 100644 --- a/server/net.h +++ b/server/net.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_net_h #define cmsg_net_h diff --git a/server/node.c b/server/node.c index b3fc2fd..25cc8fd 100644 --- a/server/node.c +++ b/server/node.c @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #include #include #include diff --git a/server/node.h b/server/node.h index 6399fd5..70fed43 100644 --- a/server/node.h +++ b/server/node.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_node_h #define cmsg_node_h diff --git a/server/queue.h b/server/queue.h index f7a0e65..18408b1 100644 --- a/server/queue.h +++ b/server/queue.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_queue_h #define cmsg_queue_h diff --git a/server/ref.h b/server/ref.h index e9e16ef..a4629cd 100644 --- a/server/ref.h +++ b/server/ref.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_ref_h #define cmsg_ref_h diff --git a/server/relay.h b/server/relay.h index dc7d92e..3bdfbb2 100644 --- a/server/relay.h +++ b/server/relay.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_relay_h #define cmsg_relay_h diff --git a/server/sexp.c b/server/sexp.c index ab7926d..3057b46 100644 --- a/server/sexp.c +++ b/server/sexp.c @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #include #include #include diff --git a/server/sexp.h b/server/sexp.h index 8632079..bf1b29b 100644 --- a/server/sexp.h +++ b/server/sexp.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_sexp_h #define cmsg_sexp_h diff --git a/server/sexpio.c b/server/sexpio.c index bada817..e263365 100644 --- a/server/sexpio.c +++ b/server/sexpio.c @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #include #include #include diff --git a/server/sexpio.h b/server/sexpio.h index afcef4e..5d62c51 100644 --- a/server/sexpio.h +++ b/server/sexpio.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_sexpio_h #define cmsg_sexpio_h diff --git a/server/subscription.h b/server/subscription.h index 9b5aeec..6a996af 100644 --- a/server/subscription.h +++ b/server/subscription.h @@ -1,3 +1,5 @@ +/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ + #ifndef cmsg_subscription_h #define cmsg_subscription_h From bfd7e24957319e5794f87257ae4fc8aebda37f89 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 5 Jan 2011 13:08:13 -0500 Subject: [PATCH 091/122] Update copyright notices --- server/cmsg_private.h | 2 +- server/dataq.c | 2 +- server/dataq.h | 2 +- server/direct.c | 2 +- server/direct.h | 2 +- server/fanout.c | 2 +- server/fanout.h | 2 +- server/harness.c | 2 +- server/harness.h | 2 +- server/hashtable.c | 2 +- server/hashtable.h | 2 +- server/main.c | 4 ++-- server/meta.c | 2 +- server/meta.h | 2 +- server/net.c | 2 +- server/net.h | 2 +- server/node.c | 2 +- server/node.h | 2 +- server/queue.c | 2 +- server/queue.h | 2 +- server/ref.h | 2 +- server/relay.c | 2 +- server/relay.h | 2 +- server/sexp.c | 2 +- server/sexp.h | 2 +- server/sexpio.c | 2 +- server/sexpio.h | 2 +- server/subscription.c | 2 +- server/subscription.h | 2 +- server/test1.c | 2 +- server/test1_latency.c | 2 +- server/test3.c | 2 +- server/test3_latency.c | 2 +- server/util.c | 2 +- 34 files changed, 35 insertions(+), 35 deletions(-) diff --git a/server/cmsg_private.h b/server/cmsg_private.h index 6159ae9..6cad748 100644 --- a/server/cmsg_private.h +++ b/server/cmsg_private.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_private_h #define cmsg_private_h diff --git a/server/dataq.c b/server/dataq.c index f3f7b8e..e045e05 100644 --- a/server/dataq.c +++ b/server/dataq.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/dataq.h b/server/dataq.h index a0fe271..6bc1152 100644 --- a/server/dataq.h +++ b/server/dataq.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_dataq_h #define cmsg_dataq_h diff --git a/server/direct.c b/server/direct.c index c9d4532..e6b0f6f 100644 --- a/server/direct.c +++ b/server/direct.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/direct.h b/server/direct.h index 8d5ffd2..89be112 100644 --- a/server/direct.h +++ b/server/direct.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_direct_h #define cmsg_direct_h diff --git a/server/fanout.c b/server/fanout.c index 25753bf..5cf503b 100644 --- a/server/fanout.c +++ b/server/fanout.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/fanout.h b/server/fanout.h index 426d137..1cdf52f 100644 --- a/server/fanout.h +++ b/server/fanout.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_fanout_h #define cmsg_fanout_h diff --git a/server/harness.c b/server/harness.c index 9c891b3..df18020 100644 --- a/server/harness.c +++ b/server/harness.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/harness.h b/server/harness.h index f6c83c9..9134842 100644 --- a/server/harness.h +++ b/server/harness.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_harness_h #define cmsg_harness_h diff --git a/server/hashtable.c b/server/hashtable.c index 4596f7f..d681d5e 100644 --- a/server/hashtable.c +++ b/server/hashtable.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/hashtable.h b/server/hashtable.h index 44c378c..8fa26c9 100644 --- a/server/hashtable.h +++ b/server/hashtable.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_hashtable_h #define cmsg_hashtable_h diff --git a/server/main.c b/server/main.c index 538d89e..f160bb6 100644 --- a/server/main.c +++ b/server/main.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include @@ -96,7 +96,7 @@ static void console_listener(void *arg) { #endif int main(int argc, char *argv[]) { - info("cmsg ALPHA, Copyright (C) 2010 Tony Garnock-Jones. All rights reserved.\n"); + info("cmsg ALPHA, Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved.\n"); event_init(); signal(SIGPIPE, SIG_IGN); /* avoid EPIPE when connections drop unexpectedly */ info("Using libevent version %s\n", event_get_version()); diff --git a/server/meta.c b/server/meta.c index 158c1b7..36e4ac9 100644 --- a/server/meta.c +++ b/server/meta.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/meta.h b/server/meta.h index 1cf4666..06989e3 100644 --- a/server/meta.h +++ b/server/meta.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_meta_h #define cmsg_meta_h diff --git a/server/net.c b/server/net.c index 245c2ca..c0776b8 100644 --- a/server/net.c +++ b/server/net.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/net.h b/server/net.h index 6e3e973..15293a7 100644 --- a/server/net.h +++ b/server/net.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_net_h #define cmsg_net_h diff --git a/server/node.c b/server/node.c index 25cc8fd..d6ece44 100644 --- a/server/node.c +++ b/server/node.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/node.h b/server/node.h index 70fed43..d33e99e 100644 --- a/server/node.h +++ b/server/node.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_node_h #define cmsg_node_h diff --git a/server/queue.c b/server/queue.c index 0567415..4ec5c73 100644 --- a/server/queue.c +++ b/server/queue.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/queue.h b/server/queue.h index 18408b1..c0f1071 100644 --- a/server/queue.h +++ b/server/queue.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_queue_h #define cmsg_queue_h diff --git a/server/ref.h b/server/ref.h index a4629cd..503311a 100644 --- a/server/ref.h +++ b/server/ref.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_ref_h #define cmsg_ref_h diff --git a/server/relay.c b/server/relay.c index d6614ba..11f0dae 100644 --- a/server/relay.c +++ b/server/relay.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/relay.h b/server/relay.h index 3bdfbb2..01e60bc 100644 --- a/server/relay.h +++ b/server/relay.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_relay_h #define cmsg_relay_h diff --git a/server/sexp.c b/server/sexp.c index 3057b46..9e62490 100644 --- a/server/sexp.c +++ b/server/sexp.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/sexp.h b/server/sexp.h index bf1b29b..1ed5f8d 100644 --- a/server/sexp.h +++ b/server/sexp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_sexp_h #define cmsg_sexp_h diff --git a/server/sexpio.c b/server/sexpio.c index e263365..d34e810 100644 --- a/server/sexpio.c +++ b/server/sexpio.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/sexpio.h b/server/sexpio.h index 5d62c51..1baf567 100644 --- a/server/sexpio.h +++ b/server/sexpio.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_sexpio_h #define cmsg_sexpio_h diff --git a/server/subscription.c b/server/subscription.c index 53482cd..5c0ed96 100644 --- a/server/subscription.c +++ b/server/subscription.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/subscription.h b/server/subscription.h index 6a996af..b514208 100644 --- a/server/subscription.h +++ b/server/subscription.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #ifndef cmsg_subscription_h #define cmsg_subscription_h diff --git a/server/test1.c b/server/test1.c index 2e0d791..65e3c3b 100644 --- a/server/test1.c +++ b/server/test1.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/test1_latency.c b/server/test1_latency.c index 2d713a3..2e7034d 100644 --- a/server/test1_latency.c +++ b/server/test1_latency.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/test3.c b/server/test3.c index 055336e..27c3c60 100644 --- a/server/test3.c +++ b/server/test3.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/test3_latency.c b/server/test3_latency.c index 981644d..c90a3b6 100644 --- a/server/test3_latency.c +++ b/server/test3_latency.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include diff --git a/server/util.c b/server/util.c index f0547a3..1c6665f 100644 --- a/server/util.c +++ b/server/util.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Tony Garnock-Jones. All rights reserved. */ +/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ #include #include From a3f5e89db8cd02908e73c3a8a379693ae3d2e74d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 5 Jan 2011 21:29:28 -0500 Subject: [PATCH 092/122] Java chassis and client --- java/hop/HalfQueue.java | 26 +++++ java/hop/InvalidGreetingException.java | 17 ++++ java/hop/Node.java | 11 +++ java/hop/NodeContainer.java | 71 ++++++++++++++ java/hop/Relay.java | 131 +++++++++++++++++++++++++ java/hop/ServerApi.java | 79 +++++++++++++++ java/hop/SexpBytes.java | 34 +++++++ java/hop/SexpDisplayHint.java | 30 ++++++ java/hop/SexpList.java | 40 ++++++++ java/hop/SexpMessage.java | 61 ++++++++++++ java/hop/SexpReader.java | 131 +++++++++++++++++++++++++ java/hop/SexpSyntaxError.java | 16 +++ java/hop/SexpWriter.java | 68 +++++++++++++ java/hop/Subscription.java | 37 +++++++ java/hop/Test1.java | 36 +++++++ java/hop/TestSexpIO.java | 82 ++++++++++++++++ 16 files changed, 870 insertions(+) create mode 100644 java/hop/HalfQueue.java create mode 100644 java/hop/InvalidGreetingException.java create mode 100644 java/hop/Node.java create mode 100644 java/hop/NodeContainer.java create mode 100644 java/hop/Relay.java create mode 100644 java/hop/ServerApi.java create mode 100644 java/hop/SexpBytes.java create mode 100644 java/hop/SexpDisplayHint.java create mode 100644 java/hop/SexpList.java create mode 100644 java/hop/SexpMessage.java create mode 100644 java/hop/SexpReader.java create mode 100644 java/hop/SexpSyntaxError.java create mode 100644 java/hop/SexpWriter.java create mode 100644 java/hop/Subscription.java create mode 100644 java/hop/Test1.java create mode 100644 java/hop/TestSexpIO.java diff --git a/java/hop/HalfQueue.java b/java/hop/HalfQueue.java new file mode 100644 index 0000000..03a5893 --- /dev/null +++ b/java/hop/HalfQueue.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + */ +public class HalfQueue implements Node { + public BlockingQueue _q; + + public HalfQueue() { + _q = new LinkedBlockingQueue(); + } + + public void handle(Object message) { + _q.add(message); + } + + public BlockingQueue getQueue() { + return _q; + } +} diff --git a/java/hop/InvalidGreetingException.java b/java/hop/InvalidGreetingException.java new file mode 100644 index 0000000..420e46a --- /dev/null +++ b/java/hop/InvalidGreetingException.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; + +/** + */ +public class InvalidGreetingException extends IOException { + Object _greeting; + + public InvalidGreetingException(Object greeting) { + _greeting = greeting; + } +} diff --git a/java/hop/Node.java b/java/hop/Node.java new file mode 100644 index 0000000..e4e9ac0 --- /dev/null +++ b/java/hop/Node.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +/** + */ +public interface Node { + void handle(Object message); +} diff --git a/java/hop/NodeContainer.java b/java/hop/NodeContainer.java new file mode 100644 index 0000000..3853650 --- /dev/null +++ b/java/hop/NodeContainer.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.lang.ref.WeakReference; +import java.util.Hashtable; +import java.util.Map; +import java.util.UUID; + +/** + */ +public class NodeContainer { + public String _name; + public Map> _directory; + + public NodeContainer() { + this(UUID.randomUUID().toString()); + } + + public NodeContainer(String name) { + _name = name; + _directory = new Hashtable>(); + } + + public String getName() { + return _name; + } + + public synchronized boolean bind(String name, Node n) { + if (_directory.containsKey(name)) + return false; + _directory.put(name, new WeakReference(n)); + return true; + } + + public synchronized boolean unbind(String name) { + if (!_directory.containsKey(name)) + return false; + _directory.remove(name); + return true; + } + + public synchronized void unbindReferencesTo(Node n) { + for (Map.Entry> e : _directory.entrySet()) { + if (e.getValue().get() == n) { + _directory.remove(e.getKey()); + } + } + } + + public synchronized Node lookup(String name) { + WeakReference r = _directory.get(name); + return (r == null) ? null : r.get(); + } + + public boolean post(String sink, Object name, Object message, Object token) { + return send(sink, SexpMessage.post(name, message, token)); + } + + public boolean send(String name, Object message) { + Node n = lookup(name); + if (n == null) { + System.err.println("Warning: sending to nonexistent node " + name + "; message " + message); + return false; + } + n.handle(message); + return true; + } +} diff --git a/java/hop/Relay.java b/java/hop/Relay.java new file mode 100644 index 0000000..626cdef --- /dev/null +++ b/java/hop/Relay.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + */ +public class Relay implements Runnable, Node { + NodeContainer _container; + String _remoteName; + Socket _sock; + String _hostname; + int _port; + SexpReader _r; + OutputStream _output; + SexpWriter _w; + + public Relay(NodeContainer container, String hostname) throws IOException, InterruptedException { + this(container, hostname, 5671); + } + + public Relay(NodeContainer container, String hostname, int port) throws IOException, InterruptedException { + _container = container; + _remoteName = null; + _hostname = hostname; + _port = port; + _connect(); + } + + public String getRemoteName() { + return _remoteName; + } + + public void _connect() throws IOException, InterruptedException { + _sock = new Socket(_hostname, _port); + _r = new SexpReader(_sock.getInputStream()); + _output = _sock.getOutputStream(); + _w = new SexpWriter(_output); + _login(); + new Thread(this).start(); + synchronized (this) { + while (_remoteName == null) { + this.wait(); + } + } + } + + public void _login() throws IOException { + SexpList greeting = _r.readList(); + if (!greeting.getBytes(0).getDataString().equals("hop")) { + throw new InvalidGreetingException(greeting); + } + + _w.write(SexpMessage.subscribe(_container.getName(), null, null, null, null)); + } + + public void handle(Object message) { + try { + _w.write(message); + } catch (IOException ioe) { + ioe.printStackTrace(); + System.err.print("Message to be written was: "); + try { + SexpWriter.write(System.err, message); + } catch (IOException ioe2) { + ioe2.printStackTrace(); + } + System.err.println(); + } + } + + public void run() { + SexpList m = null; + try { + while (true) { + m = _r.readList(); + if (m == null) { + break; + } + System.err.println("Received: " + m); + String selector = m.getBytes(0).getDataString(); + if (selector.equals("post") && m.size() == 4) { + _container.send(m.getBytes(1).getDataString(), m.get(2)); + } else if (selector.equals("subscribe") && m.size() == 6) { + if (_remoteName != null) { + System.err.println("Double bind attempted"); + } else { + _remoteName = m.getBytes(1).getDataString(); + synchronized (this) { + this.notifyAll(); + } + if (_container.bind(_remoteName, this)) { + _container.post(m.getBytes(4).getDataString(), m.get(5), SexpMessage.subscribe_ok(_remoteName), null); + } else { + System.err.println("Bind failed: " + _remoteName); + } + } + } else if (selector.equals("unsubscribe") && m.size() == 2) { + if (!m.getBytes(1).getDataString().equals(_remoteName)) { + System.err.println("Unknown unbind attempted"); + } else { + if (!_container.unbind(m.getBytes(1).getDataString())) { + System.err.println("Unbind failed: " + m.get(1)); + } + } + } else { + System.err.print("Unknown message: "); + SexpWriter.write(System.err, m); + System.err.println(); + } + } + } catch (IOException ioe) { + ioe.printStackTrace(); + System.err.print("Most recent received message: "); + try { + SexpWriter.write(System.err, m); + } catch (IOException ioe2) { + ioe2.printStackTrace(); + } + System.err.println(); + } + } +} diff --git a/java/hop/ServerApi.java b/java/hop/ServerApi.java new file mode 100644 index 0000000..3520442 --- /dev/null +++ b/java/hop/ServerApi.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.util.UUID; + +/** + */ +public class ServerApi { + public NodeContainer _container; + public String _serverName; + public String _kName; + public HalfQueue _k; + + public ServerApi(NodeContainer container, String serverName) { + _container = container; + _serverName = serverName; + _kName = UUID.randomUUID().toString(); + _k = new HalfQueue(); + _container.bind(_kName, _k); + } + + public SexpList _nextReply() throws InterruptedException, SexpSyntaxError { + Object x = _k.getQueue().take(); + if (x instanceof SexpList) return (SexpList) x; + throw new SexpSyntaxError("Unexpected non-list"); + } + + public void post(String sink, Object name, Object message, Object token) { + _container.post(_serverName, sink, SexpMessage.post(name, message, token), null); + } + + public void send(String sink, Object message) { + _container.post(_serverName, sink, message, null); + } + + public synchronized Object subscribe(String source, Object filter, String sink, String name) throws InterruptedException, SexpSyntaxError { + send(source, SexpMessage.subscribe(filter, sink, name, _container.getName(), _kName)); + SexpList reply = _nextReply(); + assert reply.getBytes(0).getDataString().equals(SexpMessage._subscribe_ok); + return reply.get(1); + } + + public synchronized Object subscribe(String source, Object filter, String name) throws InterruptedException, SexpSyntaxError { + return subscribe(source, filter, _container.getName(), name); + } + + public Subscription subscribe(String source, Object filter) throws InterruptedException, SexpSyntaxError { + return new Subscription(this, source, filter); + } + + public void unsubscribe(String source, Object token) { + send(source, SexpMessage.unsubscribe(token)); + /* TODO: optional synchronous reply? */ + } + + public synchronized Object create(String nodeClassName, Object arg) throws InterruptedException, SexpSyntaxError { + send("factory", SexpMessage.create(nodeClassName, arg, _container.getName(), _kName)); + SexpList reply = _nextReply(); + String selector = reply.getBytes(0).getDataString(); + if (selector.equals(SexpMessage._create_ok)) return null; + assert selector.equals(SexpMessage._create_failed); + return reply.get(1); + } + + public Object createQueue(String name) throws InterruptedException, SexpSyntaxError { + return create("queue", SexpList.with(name)); + } + + public Object createFanout(String name) throws InterruptedException, SexpSyntaxError { + return create("fanout", SexpList.with(name)); + } + + public Object createDirect(String name) throws InterruptedException, SexpSyntaxError { + return create("direct", SexpList.with(name)); + } +} diff --git a/java/hop/SexpBytes.java b/java/hop/SexpBytes.java new file mode 100644 index 0000000..a168d64 --- /dev/null +++ b/java/hop/SexpBytes.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; +import java.io.OutputStream; + +/** + */ +public class SexpBytes { + public byte[] _bytes; + + public SexpBytes(byte[] bytes) { + _bytes = bytes; + } + + public byte[] getData() { + return _bytes; + } + + public String getDataString() { + return new String(getData()); + } + + public void writeTo(OutputStream stream) throws IOException { + SexpWriter.writeSimpleString(stream, _bytes); + } + + public String toString() { + return SexpWriter.writeString(this); + } +} diff --git a/java/hop/SexpDisplayHint.java b/java/hop/SexpDisplayHint.java new file mode 100644 index 0000000..0ce41b1 --- /dev/null +++ b/java/hop/SexpDisplayHint.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; +import java.io.OutputStream; + +/** + */ +public class SexpDisplayHint extends SexpBytes { + public byte[] _hint; + + public SexpDisplayHint(byte[] hint, byte[] body) { + super(body); + _hint = hint; + } + + public byte[] getHint() { + return _hint; + } + + public void writeTo(OutputStream stream) throws IOException { + stream.write('['); + SexpWriter.writeSimpleString(stream, _hint); + stream.write(']'); + super.writeTo(stream); + } +} diff --git a/java/hop/SexpList.java b/java/hop/SexpList.java new file mode 100644 index 0000000..11d11c5 --- /dev/null +++ b/java/hop/SexpList.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.util.ArrayList; + +/** + */ +public class SexpList extends ArrayList { + public SexpBytes getBytes(int index) throws SexpSyntaxError { + Object x = get(index); + if (x != null && !(x instanceof SexpBytes)) { + throw new SexpSyntaxError("Unexpected non-bytes"); + } + return (SexpBytes) get(index); + } + + public SexpList getList(int index) throws SexpSyntaxError { + Object x = get(index); + if (x != null && !(x instanceof SexpList)) { + throw new SexpSyntaxError("Unexpected non-list"); + } + return (SexpList) get(index); + } + + public static SexpList empty() { + return new SexpList(); + } + + public static SexpList with(Object x) { + return empty().and(x); + } + + public SexpList and(Object x) { + this.add(x); + return this; + } +} diff --git a/java/hop/SexpMessage.java b/java/hop/SexpMessage.java new file mode 100644 index 0000000..32c3a42 --- /dev/null +++ b/java/hop/SexpMessage.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +/** + */ +public class SexpMessage { + public static SexpBytes _post = new SexpBytes("post".getBytes()); + public static SexpBytes _subscribe = new SexpBytes("subscribe".getBytes()); + public static SexpBytes _unsubscribe = new SexpBytes("unsubscribe".getBytes()); + public static SexpBytes _subscribe_ok = new SexpBytes("subscribe-ok".getBytes()); + public static SexpBytes _create = new SexpBytes("create".getBytes()); + public static SexpBytes _create_ok = new SexpBytes("create-ok".getBytes()); + public static SexpBytes _create_failed = new SexpBytes("create-failed".getBytes()); + + public static SexpList post(Object name, Object message, Object token) { + SexpList m = new SexpList(); + m.add(_post); + m.add(name); + m.add(message); + m.add(token); + return m; + } + + public static SexpList subscribe(Object filter, String sink, Object name, String replySink, Object replyName) { + SexpList m = new SexpList(); + m.add(_subscribe); + m.add(filter); + m.add(sink); + m.add(name); + m.add(replySink); + m.add(replyName); + return m; + } + + public static SexpList subscribe_ok(Object token) { + SexpList m = new SexpList(); + m.add(_subscribe_ok); + m.add(token); + return m; + } + + public static SexpList unsubscribe(Object token) { + SexpList m = new SexpList(); + m.add(_unsubscribe); + m.add(token); + return m; + } + + public static SexpList create(String nodeClassName, Object arg, String replySink, Object replyName) { + SexpList m = new SexpList(); + m.add(_create); + m.add(nodeClassName); + m.add(arg); + m.add(replySink); + m.add(replyName); + return m; + } +} diff --git a/java/hop/SexpReader.java b/java/hop/SexpReader.java new file mode 100644 index 0000000..6f73a64 --- /dev/null +++ b/java/hop/SexpReader.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; +import java.io.InputStream; + +public class SexpReader { + public InputStream _input; + + public SexpReader(InputStream input) { + _input = input; + } + + /** + * Reads a sexp length-prefix from _input. + * @return The read length, or -1 if the end of stream is reached + * @throws IOException + * @throws SexpSyntaxError + */ + public int _readLength(int lengthSoFar) throws IOException { + int length = lengthSoFar; + + while (true) { + int c = _input.read(); + if (c == -1) return -1; + if (c == ':') { + return length; + } + if (!Character.isDigit(c)) { + throw new SexpSyntaxError("Invalid length prefix"); + } + length = length * 10 + (c - '0'); + } + } + + /** + * Reads a simple length-prefixed string from _input, given either zero or the value + * of the first digit of the length-prefix being read. + * @param lengthSoFar either zero or the first digit of the length prefix to use + * @return the read string + * @throws IOException + * @throws SexpSyntaxError + */ + public byte[] _readSimpleString(int lengthSoFar) throws IOException { + int length = _readLength(lengthSoFar); + if (length == -1) return null; + byte[] buf = new byte[length]; + int offset = 0; + while (length > 0) { + int count = _input.read(buf, offset, length); + if (count == -1) { + throw new SexpSyntaxError("End-of-stream in the middle of a simple string"); + } + offset += count; + length -= count; + } + return buf; + } + + public byte[] readSimpleString() throws IOException { + return _readSimpleString(0); + } + + public SexpList _readList() throws IOException { + SexpList list = new SexpList(); + while (true) { + int c = _input.read(); + switch (c) { + case -1: + throw new SexpSyntaxError("Unclosed list"); + case ')': + return list; + default: + list.add(_read(c)); + break; + } + } + } + + public Object _read(int c) throws IOException { + switch (c) { + case -1: + return null; + case '(': + return _readList(); + case ')': + throw new SexpSyntaxError("Unexpected close-paren"); + case '[': + byte[] hint = readSimpleString(); + switch (_input.read()) { + case -1: + throw new SexpSyntaxError("End-of-stream between display hint and body"); + case ']': + break; + default: + throw new SexpSyntaxError("Unexpected character after display hint"); + } + byte[] body = readSimpleString(); + return new SexpDisplayHint(hint, body); + default: + if (Character.isDigit(c)) { + return new SexpBytes(_readSimpleString(c - '0')); + } else { + throw new SexpSyntaxError("Unexpected character"); + } + } + } + + public Object read() throws IOException { + return _read(_input.read()); + } + + public SexpList readList() throws IOException { + Object x = read(); + if (x != null && !(x instanceof SexpList)) { + throw new SexpSyntaxError("Unexpected non-list"); + } + return (SexpList) x; + } + + public SexpBytes readBytes() throws IOException { + Object x = read(); + if (x != null && !(x instanceof SexpBytes)) { + throw new SexpSyntaxError("Unexpected non-bytes"); + } + return (SexpBytes) x; + } +} \ No newline at end of file diff --git a/java/hop/SexpSyntaxError.java b/java/hop/SexpSyntaxError.java new file mode 100644 index 0000000..14c335c --- /dev/null +++ b/java/hop/SexpSyntaxError.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; + +/** + * Reports on a syntax problem reading an S-expression. + */ +public class SexpSyntaxError extends IOException { + public SexpSyntaxError(String s) { + super(s); + } +} diff --git a/java/hop/SexpWriter.java b/java/hop/SexpWriter.java new file mode 100644 index 0000000..91e1db0 --- /dev/null +++ b/java/hop/SexpWriter.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + */ +public class SexpWriter { + public OutputStream _output; + + public SexpWriter(OutputStream output) { + _output = output; + } + + public static void writeSimpleString(OutputStream stream, byte[] buf) throws IOException { + stream.write(Integer.toString(buf.length).getBytes()); + stream.write(':'); + stream.write(buf); + } + + public void write(Object x) throws IOException { + if (x instanceof String) { + writeSimpleString(_output, ((String) x).getBytes()); + return; + } + if (x instanceof byte[]) { + writeSimpleString(_output, ((byte[]) x)); + return; + } + if (x instanceof SexpBytes) { + ((SexpBytes) x).writeTo(_output); + return; + } + if (x instanceof List) { + _output.write('('); + for (Object v : ((List) x)) { + write(v); + } + _output.write(')'); + return; + } + if (x == null) { + _output.write("0:".getBytes()); + return; + } + throw new SexpSyntaxError("Unsupported sexp object type"); + } + + public static void write(OutputStream output, Object x) throws IOException { + new SexpWriter(output).write(x); + } + + public static String writeString(Object x) { + try { + ByteArrayOutputStream o = new ByteArrayOutputStream(); + write(o, x); + return new String(o.toByteArray()); + } catch (IOException ioe) { + return x.toString(); + } + } +} diff --git a/java/hop/Subscription.java b/java/hop/Subscription.java new file mode 100644 index 0000000..8f13f5f --- /dev/null +++ b/java/hop/Subscription.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.util.UUID; +import java.util.concurrent.BlockingQueue; + +/** + */ +public class Subscription { + public ServerApi _api; + public String _source; + public Object _filter; + public String _consumerName; + public HalfQueue _consumer; + public Object _subscriptionToken; + + public Subscription(ServerApi api, String source, Object filter) throws InterruptedException, SexpSyntaxError { + _api = api; + _source = source; + _filter = filter; + _consumerName = UUID.randomUUID().toString(); + _consumer = new HalfQueue(); + _api._container.bind(_consumerName, _consumer); + _subscriptionToken = _api.subscribe(source, filter, _consumerName); + } + + public BlockingQueue getQueue() { + return _consumer.getQueue(); + } + + public void unsubscribe() { + _api.unsubscribe(_source, _subscriptionToken); + } +} diff --git a/java/hop/Test1.java b/java/hop/Test1.java new file mode 100644 index 0000000..9bb9f08 --- /dev/null +++ b/java/hop/Test1.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; + +/** + */ +public class Test1 { + public static void main(String[] args) { + try { + run(args[0]); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void run(String hostname) throws IOException, InterruptedException { + NodeContainer nc = new NodeContainer(); + + System.out.println("Hostname: " + hostname); + System.out.println("Container: " + nc.getName()); + + Relay r = new Relay(nc, hostname); + ServerApi api = new ServerApi(nc, r.getRemoteName()); + + api.createQueue("q1"); + Subscription sub = api.subscribe("q1", null); + while (true) { + Object x = sub.getQueue().take(); + System.out.println("Message: " + x); + } + } +} diff --git a/java/hop/TestSexpIO.java b/java/hop/TestSexpIO.java new file mode 100644 index 0000000..3acd4a6 --- /dev/null +++ b/java/hop/TestSexpIO.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + */ +public class TestSexpIO extends TestCase { + public Object read(String s) throws IOException { + return new SexpReader(new ByteArrayInputStream(s.getBytes())).read(); + } + + public byte[] write(Object x) throws IOException { + ByteArrayOutputStream o = new ByteArrayOutputStream(); + new SexpWriter(o).write(x); + return o.toByteArray(); + } + + public void assertBytesEqual(String expected, byte[] actual) { + assertBytesEqual(expected.getBytes(), actual); + } + + public void assertBytesEqual(byte[] expected, byte[] actual) { + assertEquals(expected.length, actual.length); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], actual[i]); + } + } + + public void testEndOfStream() throws IOException { + assertNull(read("")); + } + + public void testEmptyList() throws IOException { + assertEquals(new ArrayList(), read("()")); + } + + public void testSimpleString() throws IOException { + assertBytesEqual("hello", ((SexpBytes) read("5:hello")).getData()); + } + + public void testDisplayHint() throws IOException { + SexpDisplayHint v = (SexpDisplayHint) read("[1:h]1:b"); + assertBytesEqual("b", v.getData()); + assertBytesEqual("h", v.getHint()); + assertBytesEqual("[1:h]1:b", write(v)); + } + + public void testSimpleList() throws IOException { + List l = (List) read("(1:a1:b1:c)"); + assertEquals(3, l.size()); + assertBytesEqual("a", ((SexpBytes) l.get(0)).getData()); + assertBytesEqual("b", ((SexpBytes) l.get(1)).getData()); + assertBytesEqual("c", ((SexpBytes) l.get(2)).getData()); + } + + public void testNestedList() throws IOException { + List l = (List) read("(1:a(1:b1:c)())"); + assertEquals(3, l.size()); + assertBytesEqual("a", ((SexpBytes) l.get(0)).getData()); + List k = (List) l.get(1); + assertEquals(2, k.size()); + assertBytesEqual("b", ((SexpBytes) k.get(0)).getData()); + assertBytesEqual("c", ((SexpBytes) k.get(1)).getData()); + assertEquals(new ArrayList(), l.get(2)); + assertBytesEqual("(1:b1:c)", write(k)); + assertBytesEqual("(1:a(1:b1:c)())", write(l)); + } + + public void testNullWrite() throws IOException { + assertBytesEqual("0:", write(null)); + } +} From e357165ed256a0216874d3bbe515aa678754418f Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 5 Jan 2011 21:29:58 -0500 Subject: [PATCH 093/122] Peers introduce each other at connect time --- server/main.c | 2 +- server/node.c | 16 +++++++++++++++- server/node.h | 4 +++- server/relay.c | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/server/main.c b/server/main.c index f160bb6..c6b0c24 100644 --- a/server/main.c +++ b/server/main.c @@ -101,7 +101,7 @@ int main(int argc, char *argv[]) { signal(SIGPIPE, SIG_IGN); /* avoid EPIPE when connections drop unexpectedly */ info("Using libevent version %s\n", event_get_version()); init_sexp(); - init_node(); + init_node(cmsg_cstring_bytes("server")); init_factory(); init_queue(); init_direct(); diff --git a/server/node.c b/server/node.c index d6ece44..fc7bace 100644 --- a/server/node.c +++ b/server/node.c @@ -18,6 +18,7 @@ #include "node.h" #include "meta.h" +static cmsg_bytes_t _container_name; static hashtable_t node_class_table; static hashtable_t directory; @@ -29,7 +30,16 @@ static void node_decref(void *arg) { DECREF((node_t *) arg, node_destructor); } -void init_node(void) { +void init_node(cmsg_bytes_t container_name) { + if (container_name.len == 0) { + unsigned char buf[CMSG_UUID_BUF_SIZE]; + gen_uuid(buf); + _container_name = cmsg_bytes_malloc_dup(CMSG_BYTES(CMSG_UUID_BUF_SIZE, buf)); + } else { + _container_name = cmsg_bytes_malloc_dup(container_name); + } + info("Local container name is <<%.*s>>\n", _container_name.len, _container_name.bytes); + init_hashtable(&node_class_table, 31, NULL, @@ -40,6 +50,10 @@ void init_node(void) { node_decref); } +cmsg_bytes_t local_container_name(void) { + return _container_name; +} + void register_node_class(node_class_t *nc) { cmsg_bytes_t key = cmsg_cstring_bytes(nc->name); if (hashtable_contains(&node_class_table, key)) { diff --git a/server/node.h b/server/node.h index d33e99e..172e11b 100644 --- a/server/node.h +++ b/server/node.h @@ -21,7 +21,9 @@ typedef struct node_class_t_ { node_message_handler_fn_t handle_message; } node_class_t; -extern void init_node(void); +extern void init_node(cmsg_bytes_t container_name); + +extern cmsg_bytes_t local_container_name(void); extern void basic_node_destroy(node_t *n); diff --git a/server/relay.c b/server/relay.c index 11f0dae..964dfe0 100644 --- a/server/relay.c +++ b/server/relay.c @@ -36,6 +36,7 @@ typedef unsigned char u_char; typedef struct relay_extension_t_ { struct sockaddr_in peername; char peername_str[256]; + sexp_t *remote_container_name; int fd; IOHandle *outh; } relay_extension_t; @@ -55,6 +56,7 @@ static void relay_destructor(node_t *n) { warn("Closing file descriptor %d produced errno %d: %s\n", r->fd, errno, strerror(errno)); } + DECREF(r->remote_container_name, sexp_destructor); free(r); } @@ -106,6 +108,19 @@ static void relay_main(node_t *n) { iohandle_write(r->outh, cmsg_cstring_bytes("(3:hop1:0)")); ICHECK(iohandle_flush(r->outh), "iohandle_flush greeting"); + { + sexp_t *s = NULL; + s = sexp_cons(sexp_empty_bytes, s); + s = sexp_cons(sexp_empty_bytes, s); + s = sexp_cons(sexp_empty_bytes, s); + s = sexp_cons(sexp_empty_bytes, s); + s = sexp_cons(sexp_bytes(local_container_name()), s); + s = sexp_cons(sexp_cstring("subscribe"), s); + INCREF(s); + sexp_write(r->outh, s); + DECREF(s, sexp_destructor); + } + //iohandle_settimeout(r->inh, 3, 0); while (1) { @@ -152,6 +167,9 @@ static void relay_main(node_t *n) { INCREF(subok); post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); DECREF(subok, sexp_destructor); + + DECREF(r->remote_container_name, sexp_destructor); + r->remote_container_name = INCREF(filter_sexp); } else { warn("Bind failed <<%.*s>>\n", filter.len, filter.bytes); } @@ -201,6 +219,7 @@ void start_relay(struct sockaddr_in const *peername, int fd) { relay_extension_t *r = n->extension; r->peername = *peername; endpoint_name(&r->peername, CMSG_BYTES(sizeof(r->peername_str), r->peername_str)); + r->remote_container_name = NULL; r->fd = fd; r->outh = new_iohandle(r->fd); spawn((process_main_t) relay_main, n); From 1cce984784b635e27aa81a9ce72c5c8c94415295 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 5 Jan 2011 21:31:07 -0500 Subject: [PATCH 094/122] Update .gitignore --- .gitignore | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2c0831e..f9b2df2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ scratch/ *.o -cmsg +server/cmsg +server/test1 +server/test3 +server/test1_latency +server/test3_latency depend.mk +java/out +*.iml +.idea From c8937f3f528ed4765f6de1d28c2c7be96869049c Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 6 Jan 2011 09:13:47 -0500 Subject: [PATCH 095/122] TCP_NODELAY --- server/net.c | 6 ++++++ server/test1.c | 6 ++++++ server/test1_latency.c | 6 ++++++ server/test3.c | 6 ++++++ server/test3_latency.c | 6 ++++++ 5 files changed, 30 insertions(+) diff --git a/server/net.c b/server/net.c index c0776b8..a5d848b 100644 --- a/server/net.c +++ b/server/net.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,11 @@ static void accept_connection(int servfd, short what, void *arg) { return; } + { + int i = 1; + ICHECK(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)), "setsockopt TCP_NODELAY"); + } + start_relay(&s, fd); } diff --git a/server/test1.c b/server/test1.c index 65e3c3b..0beab33 100644 --- a/server/test1.c +++ b/server/test1.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,11 @@ int main(int argc, char *argv[]) { if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; + { + int i = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)); + } + f = fdopen(fd, "a+"); fprintf(f, "(9:subscribe5:test10:0:5:test15:login)(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:)(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:)\n"); diff --git a/server/test1_latency.c b/server/test1_latency.c index 2e7034d..3dc0149 100644 --- a/server/test1_latency.c +++ b/server/test1_latency.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -83,6 +84,11 @@ int main(int argc, char *argv[]) { if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; + { + int i = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)); + } + f = fdopen(fd, "a+"); fprintf(f, "(9:subscribe5:test10:0:5:test15:login)(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:)(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:)\n"); diff --git a/server/test3.c b/server/test3.c index 27c3c60..48a2c59 100644 --- a/server/test3.c +++ b/server/test3.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,11 @@ int main(int argc, char *argv[]) { if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; + { + int i = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)); + } + f = fdopen(fd, "a+"); fprintf(f, "(9:subscribe5:test30:0:5:test35:login)"); diff --git a/server/test3_latency.c b/server/test3_latency.c index c90a3b6..36a17a7 100644 --- a/server/test3_latency.c +++ b/server/test3_latency.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,11 @@ int main(int argc, char *argv[]) { if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; + { + int i = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)); + } + f = fdopen(fd, "a+"); fprintf(f, "(9:subscribe5:test30:0:5:test35:login)"); From 7c348b8ff47bbe3da89fd17e024d70b963ea322e Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 6 Jan 2011 09:14:20 -0500 Subject: [PATCH 096/122] setTcpNoDelay --- java/hop/Relay.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/hop/Relay.java b/java/hop/Relay.java index 626cdef..ea42bb7 100644 --- a/java/hop/Relay.java +++ b/java/hop/Relay.java @@ -41,6 +41,7 @@ public class Relay implements Runnable, Node { public void _connect() throws IOException, InterruptedException { _sock = new Socket(_hostname, _port); + _sock.setTcpNoDelay(true); _r = new SexpReader(_sock.getInputStream()); _output = _sock.getOutputStream(); _w = new SexpWriter(_output); From d0e6e89ffb5aec12580c626a29ed9dd59969810e Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 6 Jan 2011 09:14:49 -0500 Subject: [PATCH 097/122] Only reply if non-empty sink name provided --- java/hop/Relay.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/java/hop/Relay.java b/java/hop/Relay.java index ea42bb7..e3b8e8d 100644 --- a/java/hop/Relay.java +++ b/java/hop/Relay.java @@ -7,9 +7,6 @@ package hop; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; /** */ @@ -86,7 +83,7 @@ public class Relay implements Runnable, Node { if (m == null) { break; } - System.err.println("Received: " + m); + //System.err.println("Received: " + m); String selector = m.getBytes(0).getDataString(); if (selector.equals("post") && m.size() == 4) { _container.send(m.getBytes(1).getDataString(), m.get(2)); @@ -99,7 +96,10 @@ public class Relay implements Runnable, Node { this.notifyAll(); } if (_container.bind(_remoteName, this)) { - _container.post(m.getBytes(4).getDataString(), m.get(5), SexpMessage.subscribe_ok(_remoteName), null); + String replySink = m.getBytes(4).getDataString(); + if (replySink.length() > 0) { + _container.post(replySink, m.get(5), SexpMessage.subscribe_ok(_remoteName), null); + } } else { System.err.println("Bind failed: " + _remoteName); } From 3ce415e106263b8f2765859ecd1739cfd7c4dbc3 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 6 Jan 2011 09:15:11 -0500 Subject: [PATCH 098/122] Java's equality is stupid --- java/hop/ServerApi.java | 2 +- java/hop/SexpBytes.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/java/hop/ServerApi.java b/java/hop/ServerApi.java index 3520442..f0205d4 100644 --- a/java/hop/ServerApi.java +++ b/java/hop/ServerApi.java @@ -59,7 +59,7 @@ public class ServerApi { public synchronized Object create(String nodeClassName, Object arg) throws InterruptedException, SexpSyntaxError { send("factory", SexpMessage.create(nodeClassName, arg, _container.getName(), _kName)); SexpList reply = _nextReply(); - String selector = reply.getBytes(0).getDataString(); + SexpBytes selector = reply.getBytes(0); if (selector.equals(SexpMessage._create_ok)) return null; assert selector.equals(SexpMessage._create_failed); return reply.get(1); diff --git a/java/hop/SexpBytes.java b/java/hop/SexpBytes.java index a168d64..1385e56 100644 --- a/java/hop/SexpBytes.java +++ b/java/hop/SexpBytes.java @@ -6,6 +6,7 @@ package hop; import java.io.IOException; import java.io.OutputStream; +import java.util.Arrays; /** */ @@ -31,4 +32,13 @@ public class SexpBytes { public String toString() { return SexpWriter.writeString(this); } + + public boolean equals(Object other) { + return (other instanceof SexpBytes) && + Arrays.equals(_bytes, ((SexpBytes) other).getData()); + } + + public int hashCode() { + return Arrays.hashCode(_bytes); + } } From 37e5f39dc18d13138bdf08eb6fa958fb44586662 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 6 Jan 2011 09:15:26 -0500 Subject: [PATCH 099/122] Ping-pong --- java/hop/TestPingPong.java | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 java/hop/TestPingPong.java diff --git a/java/hop/TestPingPong.java b/java/hop/TestPingPong.java new file mode 100644 index 0000000..c8e7107 --- /dev/null +++ b/java/hop/TestPingPong.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; + +/** + */ +public class TestPingPong { + public static void main(final String[] args) { + try { + new Thread(new Runnable() { public void run() { + try { + run1(args[0]); + } catch (Exception e) { + e.printStackTrace(); + } + } }).start(); + run2(args[0]); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void run1(String hostname) throws IOException, InterruptedException { + NodeContainer nc = new NodeContainer(); + + System.out.println("Hostname: " + hostname); + System.out.println("Container: " + nc.getName()); + + Relay r = new Relay(nc, hostname); + ServerApi api = new ServerApi(nc, r.getRemoteName()); + + api.createQueue("req"); + Subscription sub = api.subscribe("req", null); + while (true) { + Object x = sub.getQueue().take(); + //System.out.println("Message: " + x); + api.post("rep", "reply", SexpList.with("ok").and(x), null); + } + } + + public static void run2(String hostname) throws IOException, InterruptedException { + NodeContainer nc = new NodeContainer(); + + System.out.println("Hostname: " + hostname); + System.out.println("Container: " + nc.getName()); + + Relay r = new Relay(nc, hostname); + ServerApi api = new ServerApi(nc, r.getRemoteName()); + + api.createQueue("req"); + api.createQueue("rep"); + Subscription sub = api.subscribe("rep", null); + long startTime = System.currentTimeMillis(); + for (int i = 0; i < 100000; i++) { + api.post("req", "request", Integer.toString(i), null); + //System.out.println("Reply: " + sub.getQueue().take()); + int j = i + 1; + if ((j % 100) == 0) { + long now = System.currentTimeMillis(); + double delta = (now - startTime) / 1000.0; + System.out.println("Message " + j + ": " + (j / delta) + " Hz"); + } + } + } +} From 7db19f77ad62cfbfd8ba19132c6bed0987af8434 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 6 Jan 2011 09:32:42 -0500 Subject: [PATCH 100/122] Command-line java build infra --- .gitignore | 1 + java/build.xml | 24 ++++++++++++++++++++++++ java/lib/junit-4.8.2.jar | Bin 0 -> 237344 bytes 3 files changed, 25 insertions(+) create mode 100644 java/build.xml create mode 100644 java/lib/junit-4.8.2.jar diff --git a/.gitignore b/.gitignore index f9b2df2..77321e9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ server/test1_latency server/test3_latency depend.mk java/out +java/build *.iml .idea diff --git a/java/build.xml b/java/build.xml new file mode 100644 index 0000000..3b8a7e3 --- /dev/null +++ b/java/build.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/lib/junit-4.8.2.jar b/java/lib/junit-4.8.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..5b4bb849af9583fec1ea0a0ccc0d571ef49aa8ba GIT binary patch literal 237344 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 Date: Thu, 6 Jan 2011 09:33:03 -0500 Subject: [PATCH 101/122] Oops, wasn't retrieving the reply --- java/hop/TestPingPong.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/hop/TestPingPong.java b/java/hop/TestPingPong.java index c8e7107..e37dfae 100644 --- a/java/hop/TestPingPong.java +++ b/java/hop/TestPingPong.java @@ -57,7 +57,7 @@ public class TestPingPong { long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { api.post("req", "request", Integer.toString(i), null); - //System.out.println("Reply: " + sub.getQueue().take()); + sub.getQueue().take(); int j = i + 1; if ((j % 100) == 0) { long now = System.currentTimeMillis(); From 444dde2a0965d458d12f67ac2195926498f2b1da Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 6 Jan 2011 09:55:51 -0500 Subject: [PATCH 102/122] Permit experimentation with multiple consumers and different kinds of node --- server/test1_latency.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/server/test1_latency.c b/server/test1_latency.c index 3dc0149..fb7d2f9 100644 --- a/server/test1_latency.c +++ b/server/test1_latency.c @@ -65,12 +65,24 @@ int main(int argc, char *argv[]) { long bytecount = -1; size_t message_size = 0; long last_report_bytecount = 0; + char idchar = '1'; + char *qclass = "queue"; if (argc < 2) { - fprintf(stderr, "Usage: test1 \n"); + fprintf(stderr, "Usage: test1 [ []]\n"); exit(1); } + if (argc > 2) { + idchar = argv[2][0]; + } + printf("Idchar: '%c'\n", idchar); + + if (argc > 3) { + qclass = argv[3]; + } + printf("Qclass: %s\n", qclass); + { struct hostent *h = gethostbyname(argv[1]); if (h == NULL) { @@ -91,7 +103,8 @@ int main(int argc, char *argv[]) { f = fdopen(fd, "a+"); - fprintf(f, "(9:subscribe5:test10:0:5:test15:login)(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:)(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:)\n"); + fprintf(f, "(9:subscribe5:test%c0:0:5:test%c5:login)(4:post7:factory(6:create%d:%s(2:q1)5:test%c1:k)0:)(4:post2:q1(9:subscribe0:5:test%c8:consumer5:test%c1:k)0:)\n", + idchar, idchar, (int) strlen(qclass), qclass, idchar, idchar, idchar); fflush(f); while (1) { From e3b9f64d6d84193655b26f9387afd8a95a6955f6 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 6 Jan 2011 19:45:25 -0500 Subject: [PATCH 103/122] Notes on an extension to Sexp syntax --- TODO | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/TODO b/TODO index 45a4a5c..36ca8b7 100644 --- a/TODO +++ b/TODO @@ -25,3 +25,30 @@ Switch from indirect scheduling to direct scheduling once) so it always goes back to the scheduler because the worklist is never longer than one. - Revisit once we start testing multiple concurrent streams. + + +Extension to Sexps: + + - ! : binds the simplestring to the value when + seen. Note that the new binding for simplestring is NOT in scope + during parsing of the value, so no circular data can be constructed + this way. Any previous binding is discarded *after* the value is + completely read. + + - ? : retrieves the bound value of the simplestring. + +So + !1:a11:hello world(?1:a1: ?1:a1: ?1:a) +is equivalent to + (11:hello world1: 11:hello world1: 11:hello world) + +And, more to the point, after a receiver has seen + !1:a36:af49f5dc-0454-4ba1-9f48-a55e9c10ee35 +then a simple + ?1:a +is all that's needed to refer to that gargantuan UUID. + +Another example: + !1:a()!1:a(?1:a?1:a)!1:a(?1:a?1:a)?1:a +is equivalent to + ((()())(()())) From 9e5ac54d82a2de1a3dc040c8e2be51e66121f276 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 7 Jan 2011 10:20:03 -0500 Subject: [PATCH 104/122] Note re metrics/management --- TODO | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TODO b/TODO index 36ca8b7..8caef68 100644 --- a/TODO +++ b/TODO @@ -52,3 +52,8 @@ Another example: !1:a()!1:a(?1:a?1:a)!1:a(?1:a?1:a)?1:a is equivalent to ((()())(()())) + + +"Having metrics around as many things as possible really helped us + identify a difficult problem to diagnose." + -- https://github.com/blog/767-recent-services-interruptions From 8b3f23e60000fefe697255a9adee1d9c13211eaf Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 7 Jan 2011 15:05:56 -0500 Subject: [PATCH 105/122] Buffering; flushing; throughput test --- java/hop/NodeContainer.java | 43 +++++++++++++++++++++++++++++++------ java/hop/Relay.java | 13 ++++++----- java/hop/ServerApi.java | 20 ++++++++++------- java/hop/Subscription.java | 5 +++-- java/hop/Test1.java | 12 ++++++++++- java/hop/Test3.java | 43 +++++++++++++++++++++++++++++++++++++ 6 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 java/hop/Test3.java diff --git a/java/hop/NodeContainer.java b/java/hop/NodeContainer.java index 3853650..789201f 100644 --- a/java/hop/NodeContainer.java +++ b/java/hop/NodeContainer.java @@ -4,14 +4,14 @@ package hop; +import java.io.Flushable; +import java.io.IOException; import java.lang.ref.WeakReference; -import java.util.Hashtable; -import java.util.Map; -import java.util.UUID; +import java.util.*; /** */ -public class NodeContainer { +public class NodeContainer implements Flushable { public String _name; public Map> _directory; @@ -29,9 +29,12 @@ public class NodeContainer { } public synchronized boolean bind(String name, Node n) { - if (_directory.containsKey(name)) + WeakReference ref = _directory.get(name); + if (ref != null && ref.get() != null) { return false; - _directory.put(name, new WeakReference(n)); + } + ref = new WeakReference(n); + _directory.put(name, ref); return true; } @@ -42,6 +45,34 @@ public class NodeContainer { return true; } + public void flush() throws IOException { + ArrayList fs = new ArrayList(); + synchronized (this) { + for (Map.Entry> e : _directory.entrySet()) { + Node n = e.getValue().get(); + if (n instanceof Flushable) { + fs.add((Flushable) n); + } + } + } + for (Flushable f : fs) { + f.flush(); + } + } + + public void flush(String name) throws IOException { + Flushable f; + synchronized (this) { + WeakReference ref = _directory.get(name); + if (ref == null) return; + Node n = ref.get(); + if (n == null) return; + if (!(n instanceof Flushable)) return; + f = ((Flushable) n); + } + f.flush(); + } + public synchronized void unbindReferencesTo(Node n) { for (Map.Entry> e : _directory.entrySet()) { if (e.getValue().get() == n) { diff --git a/java/hop/Relay.java b/java/hop/Relay.java index e3b8e8d..698be96 100644 --- a/java/hop/Relay.java +++ b/java/hop/Relay.java @@ -4,13 +4,12 @@ package hop; -import java.io.IOException; -import java.io.OutputStream; +import java.io.*; import java.net.Socket; /** */ -public class Relay implements Runnable, Node { +public class Relay implements Runnable, Node, Flushable { NodeContainer _container; String _remoteName; Socket _sock; @@ -39,8 +38,8 @@ public class Relay implements Runnable, Node { public void _connect() throws IOException, InterruptedException { _sock = new Socket(_hostname, _port); _sock.setTcpNoDelay(true); - _r = new SexpReader(_sock.getInputStream()); - _output = _sock.getOutputStream(); + _r = new SexpReader(new BufferedInputStream(_sock.getInputStream())); + _output = new BufferedOutputStream(_sock.getOutputStream()); _w = new SexpWriter(_output); _login(); new Thread(this).start(); @@ -75,6 +74,10 @@ public class Relay implements Runnable, Node { } } + public void flush() throws IOException { + _output.flush(); + } + public void run() { SexpList m = null; try { diff --git a/java/hop/ServerApi.java b/java/hop/ServerApi.java index f0205d4..2631af6 100644 --- a/java/hop/ServerApi.java +++ b/java/hop/ServerApi.java @@ -4,6 +4,7 @@ package hop; +import java.io.IOException; import java.util.UUID; /** @@ -36,28 +37,31 @@ public class ServerApi { _container.post(_serverName, sink, message, null); } - public synchronized Object subscribe(String source, Object filter, String sink, String name) throws InterruptedException, SexpSyntaxError { + public synchronized Object subscribe(String source, Object filter, String sink, String name) throws InterruptedException, IOException { send(source, SexpMessage.subscribe(filter, sink, name, _container.getName(), _kName)); + _container.flush(_serverName); SexpList reply = _nextReply(); assert reply.getBytes(0).getDataString().equals(SexpMessage._subscribe_ok); return reply.get(1); } - public synchronized Object subscribe(String source, Object filter, String name) throws InterruptedException, SexpSyntaxError { + public synchronized Object subscribe(String source, Object filter, String name) throws InterruptedException, IOException { return subscribe(source, filter, _container.getName(), name); } - public Subscription subscribe(String source, Object filter) throws InterruptedException, SexpSyntaxError { + public Subscription subscribe(String source, Object filter) throws InterruptedException, IOException { return new Subscription(this, source, filter); } - public void unsubscribe(String source, Object token) { + public void unsubscribe(String source, Object token) throws IOException { send(source, SexpMessage.unsubscribe(token)); + _container.flush(_serverName); /* TODO: optional synchronous reply? */ } - public synchronized Object create(String nodeClassName, Object arg) throws InterruptedException, SexpSyntaxError { + public synchronized Object create(String nodeClassName, Object arg) throws InterruptedException, IOException { send("factory", SexpMessage.create(nodeClassName, arg, _container.getName(), _kName)); + _container.flush(_serverName); SexpList reply = _nextReply(); SexpBytes selector = reply.getBytes(0); if (selector.equals(SexpMessage._create_ok)) return null; @@ -65,15 +69,15 @@ public class ServerApi { return reply.get(1); } - public Object createQueue(String name) throws InterruptedException, SexpSyntaxError { + public Object createQueue(String name) throws InterruptedException, IOException { return create("queue", SexpList.with(name)); } - public Object createFanout(String name) throws InterruptedException, SexpSyntaxError { + public Object createFanout(String name) throws InterruptedException, IOException { return create("fanout", SexpList.with(name)); } - public Object createDirect(String name) throws InterruptedException, SexpSyntaxError { + public Object createDirect(String name) throws InterruptedException, IOException { return create("direct", SexpList.with(name)); } } diff --git a/java/hop/Subscription.java b/java/hop/Subscription.java index 8f13f5f..0f09010 100644 --- a/java/hop/Subscription.java +++ b/java/hop/Subscription.java @@ -4,6 +4,7 @@ package hop; +import java.io.IOException; import java.util.UUID; import java.util.concurrent.BlockingQueue; @@ -17,7 +18,7 @@ public class Subscription { public HalfQueue _consumer; public Object _subscriptionToken; - public Subscription(ServerApi api, String source, Object filter) throws InterruptedException, SexpSyntaxError { + public Subscription(ServerApi api, String source, Object filter) throws InterruptedException, IOException { _api = api; _source = source; _filter = filter; @@ -31,7 +32,7 @@ public class Subscription { return _consumer.getQueue(); } - public void unsubscribe() { + public void unsubscribe() throws IOException { _api.unsubscribe(_source, _subscriptionToken); } } diff --git a/java/hop/Test1.java b/java/hop/Test1.java index 9bb9f08..45e3332 100644 --- a/java/hop/Test1.java +++ b/java/hop/Test1.java @@ -28,9 +28,19 @@ public class Test1 { api.createQueue("q1"); Subscription sub = api.subscribe("q1", null); + long startTime = 0; + int count = 0; while (true) { Object x = sub.getQueue().take(); - System.out.println("Message: " + x); + if (startTime == 0) { + startTime = System.currentTimeMillis(); + } + count++; + if ((count % 100000) == 0) { + long now = System.currentTimeMillis(); + double delta = (now - startTime) / 1000.0; + System.out.println("Received "+count+" messages in "+delta+" seconds, rate = " + (count / delta) + " Hz"); + } } } } diff --git a/java/hop/Test3.java b/java/hop/Test3.java new file mode 100644 index 0000000..f77749b --- /dev/null +++ b/java/hop/Test3.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; + +/** + */ +public class Test3 { + public static void main(String[] args) { + try { + run(args[0]); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void run(String hostname) throws IOException, InterruptedException { + NodeContainer nc = new NodeContainer(); + + System.out.println("Hostname: " + hostname); + System.out.println("Container: " + nc.getName()); + + Relay r = new Relay(nc, hostname); + ServerApi api = new ServerApi(nc, r.getRemoteName()); + + api.createQueue("q1"); + long startTime = System.currentTimeMillis(); + int count = 0; + for (int i = 0; i < 10000000; i++) { + api.post("q1", null, Integer.toString(i), null); + count++; + if ((count % 100000) == 0) { + r.flush(); + long now = System.currentTimeMillis(); + double delta = (now - startTime) / 1000.0; + System.out.println("Sent "+count+" messages in "+delta+" seconds, rate = " + (count / delta) + " Hz"); + } + } + } +} From 76cf11c69a56f33b47ba173907f685f7dd2f5ff7 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 7 Jan 2011 15:35:18 -0500 Subject: [PATCH 106/122] Make ServerApi flushable, and flush in ping-pong test --- java/hop/ServerApi.java | 13 +++++++++---- java/hop/Test3.java | 3 ++- java/hop/TestPingPong.java | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/java/hop/ServerApi.java b/java/hop/ServerApi.java index 2631af6..bf72c6a 100644 --- a/java/hop/ServerApi.java +++ b/java/hop/ServerApi.java @@ -4,12 +4,13 @@ package hop; +import java.io.Flushable; import java.io.IOException; import java.util.UUID; /** */ -public class ServerApi { +public class ServerApi implements Flushable { public NodeContainer _container; public String _serverName; public String _kName; @@ -37,9 +38,13 @@ public class ServerApi { _container.post(_serverName, sink, message, null); } + public void flush() throws IOException { + _container.flush(_serverName); + } + public synchronized Object subscribe(String source, Object filter, String sink, String name) throws InterruptedException, IOException { send(source, SexpMessage.subscribe(filter, sink, name, _container.getName(), _kName)); - _container.flush(_serverName); + flush(); SexpList reply = _nextReply(); assert reply.getBytes(0).getDataString().equals(SexpMessage._subscribe_ok); return reply.get(1); @@ -55,13 +60,13 @@ public class ServerApi { public void unsubscribe(String source, Object token) throws IOException { send(source, SexpMessage.unsubscribe(token)); - _container.flush(_serverName); + flush(); /* TODO: optional synchronous reply? */ } public synchronized Object create(String nodeClassName, Object arg) throws InterruptedException, IOException { send("factory", SexpMessage.create(nodeClassName, arg, _container.getName(), _kName)); - _container.flush(_serverName); + flush(); SexpList reply = _nextReply(); SexpBytes selector = reply.getBytes(0); if (selector.equals(SexpMessage._create_ok)) return null; diff --git a/java/hop/Test3.java b/java/hop/Test3.java index f77749b..63512ef 100644 --- a/java/hop/Test3.java +++ b/java/hop/Test3.java @@ -21,6 +21,7 @@ public class Test3 { NodeContainer nc = new NodeContainer(); System.out.println("Hostname: " + hostname); + System.out.println("Container: " + nc.getName()); Relay r = new Relay(nc, hostname); @@ -33,7 +34,7 @@ public class Test3 { api.post("q1", null, Integer.toString(i), null); count++; if ((count % 100000) == 0) { - r.flush(); + api.flush(); long now = System.currentTimeMillis(); double delta = (now - startTime) / 1000.0; System.out.println("Sent "+count+" messages in "+delta+" seconds, rate = " + (count / delta) + " Hz"); diff --git a/java/hop/TestPingPong.java b/java/hop/TestPingPong.java index e37dfae..d8de0b6 100644 --- a/java/hop/TestPingPong.java +++ b/java/hop/TestPingPong.java @@ -39,6 +39,7 @@ public class TestPingPong { Object x = sub.getQueue().take(); //System.out.println("Message: " + x); api.post("rep", "reply", SexpList.with("ok").and(x), null); + api.flush(); } } @@ -57,7 +58,8 @@ public class TestPingPong { long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { api.post("req", "request", Integer.toString(i), null); - sub.getQueue().take(); + api.flush(); + sub.getQueue().take(); int j = i + 1; if ((j % 100) == 0) { long now = System.currentTimeMillis(); From ce48a0fbea5673d1eec5d758341c20f8e66887c0 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 9 Jan 2011 17:07:53 -0500 Subject: [PATCH 107/122] Add sexp_cmp --- server/sexp.c | 19 +++++++++++++++++++ server/sexp.h | 1 + 2 files changed, 20 insertions(+) diff --git a/server/sexp.c b/server/sexp.c index 9e62490..9da78a4 100644 --- a/server/sexp.c +++ b/server/sexp.c @@ -155,6 +155,25 @@ cmsg_bytes_t sexp_data(sexp_t *x) { } } +int sexp_cmp(sexp_t *a, sexp_t *b) { + tail: + if (a == b) return 0; + if (sexp_stringp(a) && sexp_stringp(b)) { + return cmsg_bytes_cmp(sexp_data(a), sexp_data(b)); + } + if (sexp_pairp(a) && sexp_pairp(b)) { + int result = sexp_cmp(sexp_head(a), sexp_head(b)); + if (result) return result; + a = sexp_tail(a); + b = sexp_tail(b); + goto tail; + } + if (a == NULL) return -1; + if (b == NULL) return 1; + if (a->kind < b->kind) return -1; + return 1; +} + sexp_t *sexp_assoc(sexp_t *list, cmsg_bytes_t key) { while (list != NULL) { sexp_t *candidate = sexp_head(list); diff --git a/server/sexp.h b/server/sexp.h index 1ed5f8d..2c9058f 100644 --- a/server/sexp.h +++ b/server/sexp.h @@ -65,6 +65,7 @@ static inline int sexp_pairp(sexp_t *x) { } extern cmsg_bytes_t sexp_data(sexp_t *x); +extern int sexp_cmp(sexp_t *a, sexp_t *b); static inline sexp_t *sexp_head(sexp_t *x) { assert(x->kind == SEXP_PAIR); From 4ab09181c3496d900c46396299a5023b65a2b2aa Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 9 Jan 2011 17:16:07 -0500 Subject: [PATCH 108/122] Add send_node_release; no need for inc/decref when using post_node --- server/main.c | 2 -- server/meta.c | 2 -- server/node.c | 15 +++++++++------ server/node.h | 1 + server/relay.c | 2 -- server/subscription.c | 2 -- 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/server/main.c b/server/main.c index c6b0c24..e04d8c2 100644 --- a/server/main.c +++ b/server/main.c @@ -58,9 +58,7 @@ static void factory_handle_message(node_t *n, sexp_t *m) { } else { reply = sexp_cons(sexp_cstring("create-failed"), sexp_cons(error, NULL)); } - INCREF(reply); post_node(sexp_data(reply_sink), sexp_data(reply_name), reply, sexp_empty_bytes); - DECREF(reply, sexp_destructor); } } return; diff --git a/server/meta.c b/server/meta.c index 36e4ac9..17074f1 100644 --- a/server/meta.c +++ b/server/meta.c @@ -39,8 +39,6 @@ void announce_subscription(sexp_t *source, msg = sexp_cons(filter, msg); msg = sexp_cons(source, msg); msg = sexp_cons(sexp_cstring(onoff ? "subscribed" : "unsubscribed"), msg); - INCREF(msg); post_node(sexp_data(meta_sym), sexp_data(source), msg, NULL); - DECREF(msg, sexp_destructor); } } diff --git a/server/node.c b/server/node.c index fc7bace..25da8fe 100644 --- a/server/node.c +++ b/server/node.c @@ -159,7 +159,6 @@ void unbind_all_names_for_node(node_t *n) { int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body, sexp_t *token) { static sexp_t *post_atom = NULL; sexp_t *msg = NULL; - int result; if (post_atom == NULL) { post_atom = INCREF(sexp_cstring("post")); @@ -169,11 +168,7 @@ int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body, sexp_t *token) msg = sexp_cons(body, msg); msg = sexp_cons(sexp_bytes(name), msg); msg = sexp_cons(post_atom, msg); - INCREF(msg); - result = send_node(node, msg); - DECREF(msg, sexp_destructor); - - return result; + return send_node_release(node, msg); } int send_node(cmsg_bytes_t node, sexp_t *message) { @@ -184,3 +179,11 @@ int send_node(cmsg_bytes_t node, sexp_t *message) { n->node_class->handle_message(n, message); return 1; } + +int send_node_release(cmsg_bytes_t node, sexp_t *message) { + int result; + INCREF(message); + result = send_node(node, message); + DECREF(message, sexp_destructor); + return result; +} diff --git a/server/node.h b/server/node.h index 172e11b..8df80b7 100644 --- a/server/node.h +++ b/server/node.h @@ -40,5 +40,6 @@ extern void unbind_all_names_for_node(node_t *n); extern int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body, sexp_t *token); extern int send_node(cmsg_bytes_t node, sexp_t *message); +extern int send_node_release(cmsg_bytes_t node, sexp_t *message); #endif diff --git a/server/relay.c b/server/relay.c index 964dfe0..b5f73b4 100644 --- a/server/relay.c +++ b/server/relay.c @@ -164,9 +164,7 @@ static void relay_main(node_t *n) { sexp_t *reply_name = sexp_listref(args, 4); if (bind_node(filter, n)) { sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(filter_sexp, NULL)); - INCREF(subok); post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); - DECREF(subok, sexp_destructor); DECREF(r->remote_container_name, sexp_destructor); r->remote_container_name = INCREF(filter_sexp); diff --git a/server/subscription.c b/server/subscription.c index 5c0ed96..6d42b71 100644 --- a/server/subscription.c +++ b/server/subscription.c @@ -114,9 +114,7 @@ subscription_t *handle_subscribe_message(sexp_t *source, { sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); - INCREF(subok); post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); - DECREF(subok, sexp_destructor); } return sub; From 5913f2008a94f1fe8c357c4da02e29c78bfecf7d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 9 Jan 2011 19:42:36 -0500 Subject: [PATCH 109/122] Use generated message-matching code --- .gitignore | 2 + server/Makefile | 12 +++++- server/codegen.py | 95 +++++++++++++++++++++++++++++++++++++++++ server/direct.c | 40 ++++++----------- server/fanout.c | 32 +++++--------- server/main.c | 43 ++++++++----------- server/messages.json | 42 ++++++++++++++++++ server/meta.c | 21 ++++----- server/node.c | 14 +----- server/queue.c | 31 +++++--------- server/relay.c | 99 +++++++++++++++++-------------------------- server/sexp.h | 5 +++ server/subscription.c | 29 ++++++------- server/subscription.h | 4 +- 14 files changed, 272 insertions(+), 197 deletions(-) create mode 100644 server/codegen.py create mode 100644 server/messages.json diff --git a/.gitignore b/.gitignore index 77321e9..c235ef9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ scratch/ *.o +server/messages.h +server/messages.c server/cmsg server/test1 server/test3 diff --git a/server/Makefile b/server/Makefile index 44ba6f9..dda4a5a 100644 --- a/server/Makefile +++ b/server/Makefile @@ -1,6 +1,6 @@ TARGET = cmsg OBJECTS = main.o harness.o net.o util.o relay.o hashtable.o dataq.o sexp.o sexpio.o node.o \ - queue.o direct.o fanout.o subscription.o meta.o + queue.o direct.o fanout.o subscription.o meta.o messages.o UUID_CFLAGS:=$(shell uuid-config --cflags) UUID_LDFLAGS:=$(shell uuid-config --ldflags) @@ -25,14 +25,22 @@ $(TARGET): $(OBJECTS) %.o: %.c $(CC) $(CFLAGS) -c $< +messages.c: messages.json codegen.py + python codegen.py body > $@ + +messages.h: messages.json codegen.py + python codegen.py header > $@ + clean: rm -f $(TARGET) rm -f $(OBJECTS) rm -rf *.dSYM - rm -f depend.mk + rm -f depend.mk messages.c messages.h rm -f test1 test3 test1.o test3.o test1_latency test3_latency test1_latency.o test3_latency.o depend.mk: + touch messages.h gcc $(CFLAGS) -M *.c > $@ + rm messages.h echo "depend.mk:" Makefile *.c >> $@ -include depend.mk diff --git a/server/codegen.py b/server/codegen.py new file mode 100644 index 0000000..0810bd1 --- /dev/null +++ b/server/codegen.py @@ -0,0 +1,95 @@ +from __future__ import with_statement + +# Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. +copyright_stmt = '/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */' + +import sys +import json + +def cify(s): + s = s.replace('-', '_') + s = s.replace(' ', '_') + return s + +class MessageType: + def __init__(self, j): + self.wire_selector = j['selector'] + self.selector = cify(self.wire_selector) + self.wire_argnames = j['args'] + self.argnames = map(cify, self.wire_argnames) + + def format_args(self, template, separator = ', '): + return separator.join([template % (x,) for x in self.argnames]) + +with file("messages.json") as f: + spec = map(MessageType, json.load(f)) + +def entrypoint_header(): + print copyright_stmt + print + print '#ifndef cmsg_messages_h' + print '#define cmsg_messages_h' + print + print 'extern void init_messages(void);' + print + for t in spec: + print 'extern sexp_t *selector_%s;' % (t.selector,) + print + for t in spec: + print 'extern sexp_t *message_%s(%s);' % (t.selector, t.format_args('sexp_t *%s')) + print + print 'typedef union parsed_message_t_ {' + for t in spec: + if t.argnames: + print ' struct { sexp_t %s; } %s;' % (t.format_args('*%s'), t.selector) + print '} parsed_message_t;' + for t in spec: + print + print 'static inline int parse_%s(sexp_t *message, parsed_message_t *out) {' % \ + (t.selector,) + print ' if (!sexp_pairp(message)) return 0;' + print ' if (sexp_cmp(sexp_head(message), selector_%s) != 0) return 0;' % (t.selector,) + for n in t.argnames: + print ' if (!sexp_pseudo_pop(&message)) return 0;' + print ' out->%s.%s = sexp_head(message);' % (t.selector, n) + print ' return sexp_tail(message) == NULL;' + print '}' + print + print '#endif' + +def entrypoint_body(): + print copyright_stmt + print + print '#include ' + print '#include ' + print '#include ' + print '#include ' + print + print '#include ' + print + print '#include "cmsg_private.h"' + print '#include "ref.h"' + print '#include "sexp.h"' + print '#include "messages.h"' + print + for t in spec: + print 'sexp_t *selector_%s = NULL;' % (t.selector,) + print + print 'void init_messages(void) {' + for t in spec: + print ' selector_%s = sexp_cstring("%s");' % (t.selector, t.wire_selector) + for t in spec: + print ' INCREF(selector_%s);' % (t.selector,) + print '}' + for t in spec: + print + print 'sexp_t *message_%s(%s) {' % (t.selector, t.format_args('sexp_t *%s')) + print ' sexp_t *m = NULL;' + for n in reversed(t.argnames): + print ' m = sexp_cons(%s, m);' % (n,) + print ' return sexp_cons(selector_%s, m);' % (t.selector,) + print '}' + +if __name__ == '__main__': + drivername = sys.argv[1] + globals()['entrypoint_' + drivername]() diff --git a/server/direct.c b/server/direct.c index e6b0f6f..5bd0b21 100644 --- a/server/direct.c +++ b/server/direct.c @@ -17,7 +17,9 @@ #include "sexp.h" #include "hashtable.h" #include "node.h" +#include "messages.h" #include "subscription.h" +#include "sexpio.h" typedef struct direct_extension_t_ { sexp_t *name; @@ -67,47 +69,33 @@ static void route_message(direct_extension_t *d, sexp_t *rk, sexp_t *body) { static void direct_handle_message(node_t *n, sexp_t *m) { direct_extension_t *d = n->extension; + parsed_message_t p; - size_t msglen = sexp_length(m); - sexp_t *args; - cmsg_bytes_t selector; - - if (msglen == 0 || !sexp_stringp(sexp_head(m))) { - warn("Invalid message in direct\n"); - return; - } - - selector = sexp_data(sexp_head(m)); - args = sexp_tail(m); - - if ((msglen == 4) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post"))) { - sexp_t *rk = sexp_listref(args, 0); - if (sexp_stringp(rk)) { - route_message(d, rk, sexp_listref(args, 1)); + if (parse_post(m, &p)) { + if (sexp_stringp(p.post.name)) { + route_message(d, p.post.name, p.post.body); } else { warn("Non-string routing key in direct\n"); } return; } - if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - subscription_t *sub = handle_subscribe_message(d->name, &d->subscriptions, args); + if (parse_subscribe(m, &p)) { + subscription_t *sub = handle_subscribe_message(d->name, &d->subscriptions, &p); if (sub != NULL) { - sexp_t *filter = sexp_listref(args, 0); - hashtable_get(&d->routing_table, sexp_data(filter), (void **) &sub->link); - hashtable_put(&d->routing_table, sexp_data(filter), sub); + hashtable_get(&d->routing_table, sexp_data(p.subscribe.filter), (void **) &sub->link); + hashtable_put(&d->routing_table, sexp_data(p.subscribe.filter), sub); } return; } - if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(d->name, &d->subscriptions, args); + if (parse_unsubscribe(m, &p)) { + handle_unsubscribe_message(d->name, &d->subscriptions, &p); return; } - warn("Message not understood in direct; selector <<%.*s>>, length %u\n", - selector.len, selector.bytes, - msglen); + warn("Message not understood in direct: "); + sexp_writeln(stderr_h, m); } static node_class_t direct_class = { diff --git a/server/fanout.c b/server/fanout.c index 5cf503b..47d3418 100644 --- a/server/fanout.c +++ b/server/fanout.c @@ -17,7 +17,9 @@ #include "sexp.h" #include "hashtable.h" #include "node.h" +#include "messages.h" #include "subscription.h" +#include "sexpio.h" typedef struct fanout_extension_t_ { sexp_t *name; @@ -60,40 +62,28 @@ static void send_to_sub(void *contextv, cmsg_bytes_t key, void *subv) { static void fanout_handle_message(node_t *n, sexp_t *m) { fanout_extension_t *f = n->extension; + parsed_message_t p; - size_t msglen = sexp_length(m); - sexp_t *args; - cmsg_bytes_t selector; - - if (msglen == 0 || !sexp_stringp(sexp_head(m))) { - warn("Invalid message in fanout\n"); - return; - } - - selector = sexp_data(sexp_head(m)); - args = sexp_tail(m); - - if ((msglen == 4) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post"))) { + if (parse_post(m, &p)) { struct delivery_context context; context.f = f; - context.body = sexp_listref(args, 1); + context.body = p.post.body; hashtable_foreach(&f->subscriptions, send_to_sub, &context); return; } - if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - handle_subscribe_message(f->name, &f->subscriptions, args); + if (parse_subscribe(m, &p)) { + handle_subscribe_message(f->name, &f->subscriptions, &p); return; } - if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(f->name, &f->subscriptions, args); + if (parse_unsubscribe(m, &p)) { + handle_unsubscribe_message(f->name, &f->subscriptions, &p); return; } - warn("Message not understood in fanout; selector <<%.*s>>, length %u\n", - selector.len, selector.bytes, - msglen); + warn("Message not understood in fanout: "); + sexp_writeln(stderr_h, m); } static node_class_t fanout_class = { diff --git a/server/main.c b/server/main.c index e04d8c2..5bae2b2 100644 --- a/server/main.c +++ b/server/main.c @@ -24,49 +24,41 @@ typedef unsigned char u_char; #include "direct.h" #include "fanout.h" #include "meta.h" +#include "messages.h" +#include "sexpio.h" #define WANT_CONSOLE_LISTENER 1 static void factory_handle_message(node_t *n, sexp_t *m) { - size_t msglen = sexp_length(m); - sexp_t *args; - cmsg_bytes_t selector; + parsed_message_t p; - if (msglen == 0 || !sexp_stringp(sexp_head(m))) { - warn("Invalid message in factory\n"); - return; - } - - selector = sexp_data(sexp_head(m)); - args = sexp_tail(m); - - if ((msglen == 5) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("create"))) { - sexp_t *classname = sexp_listref(args, 0); - sexp_t *ctor_arg = sexp_listref(args, 1); - sexp_t *reply_sink = sexp_listref(args, 2); - sexp_t *reply_name = sexp_listref(args, 3); - if (sexp_stringp(classname) && sexp_stringp(reply_sink) && sexp_stringp(reply_name)) { - cmsg_bytes_t classname_bytes = sexp_data(classname); + if (parse_create(m, &p)) { + if (sexp_stringp(p.create.classname) + && sexp_stringp(p.create.reply_sink) + && sexp_stringp(p.create.reply_name)) { + cmsg_bytes_t classname_bytes = sexp_data(p.create.classname); node_class_t *nc = lookup_node_class(classname_bytes); if (nc == NULL) { warn("Node class not found <<%.*s>>\n", classname_bytes.len, classname_bytes.bytes); } else { sexp_t *error = NULL; sexp_t *reply; - if (new_node(nc, ctor_arg, &error) != NULL) { - reply = sexp_cons(sexp_cstring("create-ok"), NULL); + if (new_node(nc, p.create.arg, &error) != NULL) { + reply = message_create_ok(); } else { - reply = sexp_cons(sexp_cstring("create-failed"), sexp_cons(error, NULL)); + reply = message_create_failed(error); } - post_node(sexp_data(reply_sink), sexp_data(reply_name), reply, sexp_empty_bytes); + post_node(sexp_data(p.create.reply_sink), + sexp_data(p.create.reply_name), + reply, + sexp_empty_bytes); } } return; } - warn("Message not understood in factory; selector <<%.*s>>, length %u\n", - selector.len, selector.bytes, - msglen); + warn("Message not understood in factory: "); + sexp_writeln(stderr_h, m); } static node_class_t factory_class = { @@ -99,6 +91,7 @@ int main(int argc, char *argv[]) { signal(SIGPIPE, SIG_IGN); /* avoid EPIPE when connections drop unexpectedly */ info("Using libevent version %s\n", event_get_version()); init_sexp(); + init_messages(); init_node(cmsg_cstring_bytes("server")); init_factory(); init_queue(); diff --git a/server/messages.json b/server/messages.json new file mode 100644 index 0000000..163aab7 --- /dev/null +++ b/server/messages.json @@ -0,0 +1,42 @@ +[ + { + "selector": "create", + "args": ["classname", "arg", "reply-sink", "reply-name"] + }, + { + "selector": "create-ok", + "args": [] + }, + { + "selector": "create-failed", + "args": ["reason"] + }, + { + "selector": "subscribed", + "args": ["source", "filter", "sink", "name"] + }, + { + "selector": "unsubscribed", + "args": ["source", "filter", "sink", "name"] + }, + { + "selector": "post", + "args": ["name", "body", "token"] + }, + { + "selector": "subscribe", + "args": ["filter", "sink", "name", "reply_sink", "reply_name"] + }, + { + "selector": "subscribe-ok", + "args": ["token"] + }, + { + "selector": "unsubscribe", + "args": ["token"] + }, + { + "selector": "error", + "args": ["message", "details"] + } +] diff --git a/server/meta.c b/server/meta.c index 17074f1..bdd6a32 100644 --- a/server/meta.c +++ b/server/meta.c @@ -15,13 +15,11 @@ #include "hashtable.h" #include "node.h" #include "meta.h" - -static sexp_t *meta_sym = NULL; +#include "messages.h" void init_meta(void) { sexp_t *args; - meta_sym = INCREF(sexp_cstring("meta")); - args = INCREF(sexp_cons(meta_sym, NULL)); + args = INCREF(sexp_cons(sexp_cstring("meta"), NULL)); new_node(lookup_node_class(cmsg_cstring_bytes("direct")), args, NULL); DECREF(args, sexp_destructor); } @@ -32,13 +30,10 @@ void announce_subscription(sexp_t *source, sexp_t *name, int onoff) { - if (meta_sym != NULL) { /* use this as a proxy for whether meta has been initialized or not */ - sexp_t *msg = NULL; - msg = sexp_cons(name, msg); - msg = sexp_cons(sink, msg); - msg = sexp_cons(filter, msg); - msg = sexp_cons(source, msg); - msg = sexp_cons(sexp_cstring(onoff ? "subscribed" : "unsubscribed"), msg); - post_node(sexp_data(meta_sym), sexp_data(source), msg, NULL); - } + post_node(cmsg_cstring_bytes("meta"), + sexp_data(source), + onoff + ? message_subscribed(source, filter, sink, name) + : message_unsubscribed(source, filter, sink, name), + NULL); } diff --git a/server/node.c b/server/node.c index 25da8fe..427e95a 100644 --- a/server/node.c +++ b/server/node.c @@ -17,6 +17,7 @@ #include "hashtable.h" #include "node.h" #include "meta.h" +#include "messages.h" static cmsg_bytes_t _container_name; static hashtable_t node_class_table; @@ -157,18 +158,7 @@ void unbind_all_names_for_node(node_t *n) { } int post_node(cmsg_bytes_t node, cmsg_bytes_t name, sexp_t *body, sexp_t *token) { - static sexp_t *post_atom = NULL; - sexp_t *msg = NULL; - - if (post_atom == NULL) { - post_atom = INCREF(sexp_cstring("post")); - } - - msg = sexp_cons(token, msg); - msg = sexp_cons(body, msg); - msg = sexp_cons(sexp_bytes(name), msg); - msg = sexp_cons(post_atom, msg); - return send_node_release(node, msg); + return send_node_release(node, message_post(sexp_bytes(name), body, token)); } int send_node(cmsg_bytes_t node, sexp_t *message) { diff --git a/server/queue.c b/server/queue.c index 4ec5c73..6ac9488 100644 --- a/server/queue.c +++ b/server/queue.c @@ -20,6 +20,7 @@ #include "node.h" #include "queue.h" #include "dataq.h" +#include "messages.h" #include "subscription.h" typedef struct queue_extension_t_ { @@ -156,27 +157,16 @@ static void throck_shovel(queue_extension_t *q) { static void queue_handle_message(node_t *n, sexp_t *m) { queue_extension_t *q = n->extension; + parsed_message_t p; - size_t msglen = sexp_length(m); - sexp_t *args; - cmsg_bytes_t selector; - - if (msglen == 0 || !sexp_stringp(sexp_head(m))) { - warn("Invalid message in queue\n"); - return; - } - - selector = sexp_data(sexp_head(m)); - args = sexp_tail(m); - - if ((msglen == 4) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post"))) { - sexp_enqueue(q->backlog_q, sexp_listref(args, 1)); + if (parse_post(m, &p)) { + sexp_enqueue(q->backlog_q, p.post.body); throck_shovel(q); return; } - if ((msglen == 6) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe"))) { - subscription_t *sub = handle_subscribe_message(q->name, &q->subscriptions, args); + if (parse_subscribe(m, &p)) { + subscription_t *sub = handle_subscribe_message(q->name, &q->subscriptions, &p); if (sub != NULL) { enqueue(&q->waiter_q, sub); throck_shovel(q); @@ -184,14 +174,13 @@ static void queue_handle_message(node_t *n, sexp_t *m) { return; } - if ((msglen == 2) && !cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe"))) { - handle_unsubscribe_message(q->name, &q->subscriptions, args); + if (parse_unsubscribe(m, &p)) { + handle_unsubscribe_message(q->name, &q->subscriptions, &p); return; } - warn("Message not understood in queue; selector <<%.*s>>, length %u\n", - selector.len, selector.bytes, - msglen); + warn("Message not understood in queue: "); + sexp_writeln(stderr_h, m); } static node_class_t queue_class = { diff --git a/server/relay.c b/server/relay.c index b5f73b4..35c6469 100644 --- a/server/relay.c +++ b/server/relay.c @@ -30,6 +30,7 @@ typedef unsigned char u_char; #include "sexpio.h" #include "hashtable.h" #include "node.h" +#include "messages.h" #define WANT_MESSAGE_TRACE 0 @@ -78,12 +79,11 @@ static node_class_t relay_class = { .handle_message = relay_handle_message }; -static void send_error(IOHandle *h, char const *message, sexp_t *extra) { - sexp_t *m = extra; - m = sexp_cons(sexp_cstring(message), m); - m = sexp_cons(sexp_cstring("error"), m); +static void send_error(IOHandle *h, char const *message, sexp_t *details) { + sexp_t *m = message_error(sexp_cstring(message), details); INCREF(m); - + warn("Sending error: "); + sexp_writeln(stderr_h, m); iohandle_clear_error(h); BCHECK(!sexp_write(h, m), "send_error sexp_write"); DECREF(m, sexp_destructor); @@ -92,13 +92,14 @@ static void send_error(IOHandle *h, char const *message, sexp_t *extra) { static void send_sexp_syntax_error(IOHandle *h, char const *message) { char const *url = "http://people.csail.mit.edu/rivest/Sexp.txt"; - send_error(h, message, sexp_cons(sexp_cstring(url), NULL)); + send_error(h, message, sexp_cstring(url)); } static void relay_main(node_t *n) { relay_extension_t *r = n->extension; IOHandle *inh = new_iohandle(r->fd); sexp_t *message = NULL; /* held */ + parsed_message_t p; INCREF(n); /* because the caller doesn't hold a ref, and we need to drop ours on our death */ @@ -109,13 +110,9 @@ static void relay_main(node_t *n) { ICHECK(iohandle_flush(r->outh), "iohandle_flush greeting"); { - sexp_t *s = NULL; - s = sexp_cons(sexp_empty_bytes, s); - s = sexp_cons(sexp_empty_bytes, s); - s = sexp_cons(sexp_empty_bytes, s); - s = sexp_cons(sexp_empty_bytes, s); - s = sexp_cons(sexp_bytes(local_container_name()), s); - s = sexp_cons(sexp_cstring("subscribe"), s); + sexp_t *s = message_subscribe(sexp_bytes(local_container_name()), + sexp_empty_bytes, sexp_empty_bytes, + sexp_empty_bytes, sexp_empty_bytes); INCREF(s); sexp_write(r->outh, s); DECREF(s, sexp_destructor); @@ -134,54 +131,36 @@ static void relay_main(node_t *n) { sexp_writeln(stderr_h, message); #endif - if (!(sexp_pairp(message) && sexp_stringp(sexp_head(message)))) { - info("Ill-formed message\n"); - send_error(r->outh, "ill-formed message", NULL); - goto protocol_error; - } - - { - cmsg_bytes_t selector = sexp_data(sexp_head(message)); - sexp_t *args = sexp_tail(message); - size_t msglen = sexp_length(message); - - /* TODO: have constant atoms for post, subscribe, unsubscribe - etc (see also post_node() and the handler in queue.c etc) */ - - if (!cmsg_bytes_cmp(selector, cmsg_cstring_bytes("post")) && (msglen == 4) - && sexp_stringp(sexp_head(args))) { - cmsg_bytes_t nodename = sexp_data(sexp_head(args)); - if (!send_node(nodename, sexp_head(sexp_tail(args)))) { - warn("Was asked to post to unknown node <<%.*s>>\n", nodename.len, nodename.bytes); - } - } else if (!cmsg_bytes_cmp(selector, cmsg_cstring_bytes("subscribe")) && (msglen == 6) - && sexp_stringp(sexp_head(args)) - && sexp_stringp(sexp_head(sexp_tail(sexp_tail(sexp_tail(args))))) - && sexp_stringp(sexp_head(sexp_tail(sexp_tail(sexp_tail(sexp_tail(args))))))) { - sexp_t *filter_sexp = sexp_head(args); - cmsg_bytes_t filter = sexp_data(filter_sexp); - sexp_t *reply_sink = sexp_listref(args, 3); - sexp_t *reply_name = sexp_listref(args, 4); - if (bind_node(filter, n)) { - sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(filter_sexp, NULL)); - post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); - - DECREF(r->remote_container_name, sexp_destructor); - r->remote_container_name = INCREF(filter_sexp); - } else { - warn("Bind failed <<%.*s>>\n", filter.len, filter.bytes); - } - } else if (!cmsg_bytes_cmp(selector, cmsg_cstring_bytes("unsubscribe")) && (msglen == 2) - && sexp_stringp(sexp_head(args))) { - sexp_t *id_sexp = sexp_head(args); - cmsg_bytes_t id = sexp_data(id_sexp); - if (!unbind_node(id)) { - warn("Unbind failed <<%.*s>>\n", id.len, id.bytes); - } - } else { - send_error(r->outh, "message not understood", sexp_cons(message, NULL)); - goto protocol_error; + if (parse_post(message, &p) && sexp_stringp(p.post.name)) { + cmsg_bytes_t nodename = sexp_data(p.post.name); + if (!send_node(nodename, p.post.body)) { + warn("Was asked to post to unknown node <<%.*s>>\n", nodename.len, nodename.bytes); } + } else if (parse_subscribe(message, &p) + && sexp_stringp(p.subscribe.filter) + && sexp_stringp(p.subscribe.reply_sink) + && sexp_stringp(p.subscribe.reply_name)) { + if (bind_node(sexp_data(p.subscribe.filter), n)) { + post_node(sexp_data(p.subscribe.reply_sink), + sexp_data(p.subscribe.reply_name), + message_subscribe_ok(p.subscribe.filter), + sexp_empty_bytes); + + DECREF(r->remote_container_name, sexp_destructor); + r->remote_container_name = INCREF(p.subscribe.filter); + } else { + cmsg_bytes_t filter = sexp_data(p.subscribe.filter); + warn("Bind failed <<%.*s>>\n", filter.len, filter.bytes); + } + } else if (parse_unsubscribe(message, &p) + && sexp_stringp(p.unsubscribe.token)) { + cmsg_bytes_t id = sexp_data(p.unsubscribe.token); + if (!unbind_node(id)) { + warn("Unbind failed <<%.*s>>\n", id.len, id.bytes); + } + } else { + send_error(r->outh, "message not understood", message); + goto protocol_error; } } diff --git a/server/sexp.h b/server/sexp.h index 2c9058f..efacc97 100644 --- a/server/sexp.h +++ b/server/sexp.h @@ -118,6 +118,11 @@ static inline sexp_t *sexp_pop(sexp_t *oldstack, sexp_t **valp) { return nextstack; } +static inline int sexp_pseudo_pop(sexp_t **px) { + *px = sexp_tail(*px); + return sexp_pairp(*px); +} + static inline sexp_t *sexp_listtail(sexp_t *list, size_t dropcount) { while (dropcount) { list = sexp_tail(list); diff --git a/server/subscription.c b/server/subscription.c index 6d42b71..e9032fd 100644 --- a/server/subscription.c +++ b/server/subscription.c @@ -15,9 +15,10 @@ #include "ref.h" #include "sexp.h" #include "hashtable.h" -#include "subscription.h" #include "node.h" #include "meta.h" +#include "messages.h" +#include "subscription.h" void free_subscription(subscription_t *sub) { DECREF(sub->uuid, sexp_destructor); @@ -79,25 +80,23 @@ subscription_t *send_to_subscription_chain(sexp_t *source, subscription_t *handle_subscribe_message(sexp_t *source, hashtable_t *subscriptions, - sexp_t *args) + parsed_message_t *p) { unsigned char uuid[CMSG_UUID_BUF_SIZE]; if (gen_uuid(uuid) != 0) { warn("Could not generate UUID\n"); return NULL; } else { - sexp_t *reply_sink = sexp_listref(args, 3); - sexp_t *reply_name = sexp_listref(args, 4); subscription_t *sub = malloc(sizeof(*sub)); sub->uuid = INCREF(sexp_bytes(CMSG_BYTES(sizeof(uuid), uuid))); - sub->filter = sexp_listref(args, 0); - sub->sink = sexp_listref(args, 1); - sub->name = sexp_listref(args, 2); + sub->filter = p->subscribe.filter; + sub->sink = p->subscribe.sink; + sub->name = p->subscribe.name; sub->link = NULL; if (!sexp_stringp(sub->filter) || !sexp_stringp(sub->sink) || !sexp_stringp(sub->name) - || !sexp_stringp(reply_sink) || !sexp_stringp(reply_name)) { + || !sexp_stringp(p->subscribe.reply_sink) || !sexp_stringp(p->subscribe.reply_name)) { DECREF(sub->uuid, sexp_destructor); free(sub); warn("Bad sink/name/reply_sink/reply_name in subscribe"); @@ -112,10 +111,10 @@ subscription_t *handle_subscribe_message(sexp_t *source, announce_subscription(source, sub->filter, sub->sink, sub->name, 1); - { - sexp_t *subok = sexp_cons(sexp_cstring("subscribe-ok"), sexp_cons(sub->uuid, NULL)); - post_node(sexp_data(reply_sink), sexp_data(reply_name), subok, sexp_empty_bytes); - } + post_node(sexp_data(p->subscribe.reply_sink), + sexp_data(p->subscribe.reply_name), + message_subscribe_ok(sub->uuid), + sexp_empty_bytes); return sub; } @@ -123,17 +122,17 @@ subscription_t *handle_subscribe_message(sexp_t *source, void handle_unsubscribe_message(sexp_t *source, hashtable_t *subscriptions, - sexp_t *args) + parsed_message_t *p) { cmsg_bytes_t uuid; subscription_t *sub; - if (!sexp_stringp(sexp_head(args))) { + if (!sexp_stringp(p->unsubscribe.token)) { warn("Invalid unsubscription\n"); return; } - uuid = sexp_data(sexp_head(args)); + uuid = sexp_data(p->unsubscribe.token); if (hashtable_get(subscriptions, uuid, (void **) &sub)) { /* TODO: clean up more eagerly perhaps? */ announce_subscription(source, sub->filter, sub->sink, sub->name, 0); diff --git a/server/subscription.h b/server/subscription.h index b514208..273d1e9 100644 --- a/server/subscription.h +++ b/server/subscription.h @@ -25,10 +25,10 @@ extern subscription_t *send_to_subscription_chain(sexp_t *source, extern subscription_t *handle_subscribe_message(sexp_t *source, hashtable_t *subscriptions, - sexp_t *args); + parsed_message_t *p); extern void handle_unsubscribe_message(sexp_t *source, hashtable_t *subscriptions, - sexp_t *args); + parsed_message_t *p); #endif From e0ca281c324d8d9f4a81a1ec1d6c2f7ae0cdbe67 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 10 Jan 2011 14:33:42 -0500 Subject: [PATCH 110/122] Make nap() interruptable. Use nap() in shoveller and exit if idle. --- server/harness.c | 25 +++++++++++++++++++------ server/harness.h | 2 +- server/queue.c | 14 ++++++++++++-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/server/harness.c b/server/harness.c index df18020..5e41527 100644 --- a/server/harness.c +++ b/server/harness.c @@ -117,24 +117,37 @@ Process *spawn(void (*f)(void *), void *arg) { return p; } +typedef struct nap_context_t_ { + Process *p; + int timeout_fired; +} nap_context_t; + void nap_isr(int fd, short what, void *arg) { - Process *p = (Process *) arg; + nap_context_t *context = arg; //info("nap_isr %p\n", p); - assert((p->state == PROCESS_WAITING) && (p->wait_flags & EV_TIMEOUT)); - p->wait_flags &= ~EV_TIMEOUT; - enqueue_runlist(p); + assert((context->p->state == PROCESS_WAITING) && (context->p->wait_flags & EV_TIMEOUT)); + context->timeout_fired = 1; + enqueue_runlist(context->p); } -void nap(long millis) { +int nap(long millis) { + struct event ev; struct timeval tv; + nap_context_t context; assert(current_process != NULL); assert(current_process->state == PROCESS_RUNNING); + context.p = current_process; + context.timeout_fired = 0; tv.tv_sec = millis / 1000; tv.tv_usec = (millis % 1000) * 1000; - ICHECK(event_once(-1, EV_TIMEOUT, nap_isr, current_process, &tv), "event_once"); + evtimer_set(&ev, nap_isr, &context); + ICHECK(evtimer_add(&ev, &tv), "evtimer_add"); current_process->state = PROCESS_WAITING; current_process->wait_flags |= EV_TIMEOUT; schedule(); + current_process->wait_flags &= ~EV_TIMEOUT; + evtimer_del(&ev); + return context.timeout_fired; } static void awaken_waiters(IOHandle *h, short mask) { diff --git a/server/harness.h b/server/harness.h index 9134842..bd54ef0 100644 --- a/server/harness.h +++ b/server/harness.h @@ -37,7 +37,7 @@ extern Process *current_process; extern void yield(void); extern Process *spawn(process_main_t f, void *arg); -extern void nap(long millis); +extern int nap(long millis); /* 1 for timeout expired; 0 for resumed early */ extern void suspend(void); extern void resume(Process *p); diff --git a/server/queue.c b/server/queue.c index 6ac9488..3035ebe 100644 --- a/server/queue.c +++ b/server/queue.c @@ -91,6 +91,11 @@ static void shoveller(void *qv) { sexp_t *body = NULL; /* held */ subscription_t *sub = NULL; + { + cmsg_bytes_t n = sexp_data(q->name); + info("Queue <<%.*s>> busy. Shoveller entering\n", n.len, n.bytes); + } + check_for_work: //info("Checking for work\n"); @@ -139,19 +144,24 @@ static void shoveller(void *qv) { end_burst(q, &burst_count, total_count); //info("Waiting for throck\n"); q->shovel_awake = 0; - suspend(); + if (nap(100)) { + cmsg_bytes_t n = sexp_data(q->name); + info("Queue <<%.*s>> idle. Shoveller exiting\n", n.len, n.bytes); + q->shovel = NULL; + return; + } //info("Throck received!\n"); goto check_for_work; } static void throck_shovel(queue_extension_t *q) { if (!q->shovel_awake) { + q->shovel_awake = 1; if (!q->shovel) { q->shovel = spawn(shoveller, q); } else { resume(q->shovel); } - q->shovel_awake = 1; } } From fa222940fccc38a3f0522424389157a59dbd73dc Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 10 Jan 2011 14:44:47 -0500 Subject: [PATCH 111/122] Avoid race to wake up napping processes --- server/harness.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/harness.c b/server/harness.c index 5e41527..ea222b7 100644 --- a/server/harness.c +++ b/server/harness.c @@ -125,9 +125,10 @@ typedef struct nap_context_t_ { void nap_isr(int fd, short what, void *arg) { nap_context_t *context = arg; //info("nap_isr %p\n", p); - assert((context->p->state == PROCESS_WAITING) && (context->p->wait_flags & EV_TIMEOUT)); - context->timeout_fired = 1; - enqueue_runlist(context->p); + if ((context->p->state == PROCESS_WAITING) && (context->p->wait_flags & EV_TIMEOUT)) { + context->timeout_fired = 1; + enqueue_runlist(context->p); + } } int nap(long millis) { From 4df94ffe4080009d0219865555f81371ab19e008 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 10 Jan 2011 14:48:39 -0500 Subject: [PATCH 112/122] Note re memory pressure and the queue shoveller --- server/queue.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/queue.c b/server/queue.c index 3035ebe..721ad77 100644 --- a/server/queue.c +++ b/server/queue.c @@ -144,6 +144,9 @@ static void shoveller(void *qv) { end_burst(q, &burst_count, total_count); //info("Waiting for throck\n"); q->shovel_awake = 0; + /* TODO: if the number of active processes is large, assume we have + memory pressure, and quit the shovel early rather than waiting + for a few milliseconds to see if we're idle. */ if (nap(100)) { cmsg_bytes_t n = sexp_data(q->name); info("Queue <<%.*s>> idle. Shoveller exiting\n", n.len, n.bytes); From 074fd181c90782adb634e5ea9dff2234d25c8e94 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 12 Jan 2011 12:32:42 -0500 Subject: [PATCH 113/122] Cope with extraneous whitespace in sexp reader --- java/hop/SexpReader.java | 56 ++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/java/hop/SexpReader.java b/java/hop/SexpReader.java index 6f73a64..0177fe7 100644 --- a/java/hop/SexpReader.java +++ b/java/hop/SexpReader.java @@ -81,31 +81,37 @@ public class SexpReader { } public Object _read(int c) throws IOException { - switch (c) { - case -1: - return null; - case '(': - return _readList(); - case ')': - throw new SexpSyntaxError("Unexpected close-paren"); - case '[': - byte[] hint = readSimpleString(); - switch (_input.read()) { - case -1: - throw new SexpSyntaxError("End-of-stream between display hint and body"); - case ']': - break; - default: - throw new SexpSyntaxError("Unexpected character after display hint"); - } - byte[] body = readSimpleString(); - return new SexpDisplayHint(hint, body); - default: - if (Character.isDigit(c)) { - return new SexpBytes(_readSimpleString(c - '0')); - } else { - throw new SexpSyntaxError("Unexpected character"); - } + while (true) { + switch (c) { + case -1: + return null; + case '(': + return _readList(); + case ')': + throw new SexpSyntaxError("Unexpected close-paren"); + case '[': + byte[] hint = readSimpleString(); + switch (_input.read()) { + case -1: + throw new SexpSyntaxError("End-of-stream between display hint and body"); + case ']': + break; + default: + throw new SexpSyntaxError("Unexpected character after display hint"); + } + byte[] body = readSimpleString(); + return new SexpDisplayHint(hint, body); + default: + if (Character.isDigit(c)) { + return new SexpBytes(_readSimpleString(c - '0')); + } else if (Character.isWhitespace(c)) { + // Skip harmless (?) whitespace + c = _input.read(); + continue; + } else { + throw new SexpSyntaxError("Unexpected character: " + c); + } + } } } From 717f933dffdc6ff8b7f80f4df33fc410c12333d2 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sat, 5 Feb 2011 23:42:01 -0500 Subject: [PATCH 114/122] Initial work on an AMQP codec --- amqp0-9-1.stripped.xml | 459 +++++++++++++++++++++++++++++++++++++++++ specgen.py | 229 ++++++++++++++++++++ 2 files changed, 688 insertions(+) create mode 100644 amqp0-9-1.stripped.xml create mode 100644 specgen.py diff --git a/amqp0-9-1.stripped.xml b/amqp0-9-1.stripped.xml new file mode 100644 index 0000000..762519b --- /dev/null +++ b/amqp0-9-1.stripped.xml @@ -0,0 +1,459 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/specgen.py b/specgen.py new file mode 100644 index 0000000..7e21969 --- /dev/null +++ b/specgen.py @@ -0,0 +1,229 @@ +import xml.dom.minidom + +def constify(s): + s = s.replace('-', '_') + s = s.replace(' ', '_') + s = s.upper() + return s + +def cify(s): + s = constify(s) + s = s.lower() + return s + +def camelify(s): + s = constify(s) + s = s.split('_') + s = [s[0].lower()] + [w.capitalize() for w in s[1:]] + s = ''.join(s) + return s + +ctypemap = { + 'shortstr': 'cmsg_bytes_t', + 'longstr': 'cmsg_bytes_t', + 'table': 'cmsg_bytes_t', # TODO fix + 'octet': 'uint8_t', + 'short': 'uint16_t', + 'long': 'uint32_t', + 'longlong': 'uint64_t', + 'bit': 'uint8_t', + 'timestamp': 'uint64_t', +} + +class Constant: + def __init__(self, e): + self.name = e.getAttribute('name') + self.value = e.getAttribute('value') + self.class_ = e.getAttribute('class') or None + + def getName(self): + if self.class_: + return 'CMSG_AMQP_ERROR_' + constify(self.name) + else: + return 'CMSG_AMQP_' + constify(self.name) + + def getValue(self): + return self.value + +class Field: + def __init__(self, e): + self.name = e.getAttribute('name') + self.domain = e.getAttribute('domain') or e.getAttribute('type') + # self.reserved = bool(e.getAttribute('reserved')) + self.type = resolveDomain(self.domain) + + def getName(self): + return cify(self.name) + + def ctype(self): + return ctypemap[str(self.type)] + +class Entity: + def __init__(self, parent, e): + self.parent = parent + self.name = e.getAttribute('name') + self.index = int(e.getAttribute('index')) + self.fields = [Field(ee) \ + for ee in e.getElementsByTagName('field') \ + if ee.parentNode is e] + + def getName(self): + if self.parent: + return self.parent.getName() + '_' + cify(self.name) + else: + return cify(self.name) + + def printStructDefExtras(self): + pass + + def printStructDef(self, suffix): + if self.fields: + print + print 'typedef struct cmsg_amqp_%s_%s_t_ {' % (self.getName(), suffix) + self.printStructDefExtras() + for f in self.fields: + print ' %s %s;' % (f.ctype(), f.getName()) + print '} cmsg_amqp_%s_%s_t;' % (self.getName(), suffix) + +class BitWriter: + def __init__(self): + self.bit_offset = 0 + + def flush(self): + if self.bit_offset: + print ' write_amqp_octet(bit_buffer);' + self.bit_offset = 0 + + def emit(self, valueExpr): + if self.bit_offset == 0: + print ' bit_buffer = 0;' + print ' if (%s) bit_buffer |= 0x%02x;' % (valueExpr, 1 << self.bit_offset) + self.bit_offset += 1 + if self.bit_offset == 8: + self.flush() + +class Method(Entity): + def __init__(self, parent, e): + Entity.__init__(self, parent, e) + self.has_content = bool(e.getAttribute('content')) + self.synchronous = bool(e.getAttribute('synchronous')) + self.responses = [ee.getAttribute('name') for ee in e.getElementsByTagName('response')] + + def methodId(self): + return self.parent.index << 16 | self.index + + def printParseClause(self): + bit_offset = 0 + print ' case 0x%08x: /* %s */ ' % (self.methodId(), self.getName()) + for f in self.fields: + if f.type == 'bit': + if bit_offset == 0: + print ' if (!parse_amqp_octet(&bit_buffer, &input, &offset))' + print ' return -CMSG_AMQP_ERROR_FRAME_ERROR;' + print ' output->body.%s.%s = (bit_buffer & 0x%02x) != 0;' % \ + (self.getName(), f.getName(), 1 << bit_offset) + bit_offset += 1 + if bit_offset == 8: bit_offset = 0 + else: + print ' if (!parse_amqp_%s(&output->body.%s.%s, &input, &offset))' % \ + (f.type, self.getName(), f.getName()) + print ' return -CMSG_AMQP_ERROR_FRAME_ERROR;' + print ' return 0;' + + def printWriteClause(self): + bw = BitWriter() + print ' case 0x%08x: /* %s */ ' % (self.methodId(), self.getName()) + for f in self.fields: + if f.type == 'bit': + bw.emit('output->body.%s.%s' % (self.getName(), f.getName())) + else: + bw.flush() + print ' write_amqp_%s(output->body.%s.%s);' % \ + (f.type, self.getName(), f.getName()) + bw.flush() + print ' break;' + +class Class(Entity): + def __init__(self, e): + Entity.__init__(self, None, e) + self.methods = [Method(self, ee) for ee in e.getElementsByTagName('method')] + + def printStructDefExtras(self): + print ' uint32_t _flags;' + +def resolveDomain(n): + while n in domainmap and domainmap[n] != n: + n = domainmap[n] + return n + +specxml = xml.dom.minidom.parse("amqp0-9-1.stripped.xml") +constants = [Constant(e) for e in specxml.getElementsByTagName('constant')] +domainmap = dict((e.getAttribute('name'), e.getAttribute('type')) \ + for e in specxml.getElementsByTagName('domain')) + +classes = [Class(e) for e in specxml.getElementsByTagName('class')] +classmap = dict((c.name, c) for c in classes) + +def header(): + print '/* TODO: put copyright etc */' + print '/* Generated from AMQP spec version %s.%s.%s */' % \ + (specxml.documentElement.getAttribute('major'), + specxml.documentElement.getAttribute('minor'), + specxml.documentElement.getAttribute('revision')) + + print + for c in constants: + print '#define %s %s' % (c.getName(), c.getValue()) + + for c in classes: + c.printStructDef('properties') + + for c in classes: + for m in c.methods: + m.printStructDef('method') + + print + print 'typedef struct cmsg_amqp_method_t_ {' + print ' uint32_t id;' + print ' union {' + for c in classes: + for m in c.methods: + if m.fields: + print ' cmsg_amqp_%s_method_t %s;' % (m.getName(), m.getName()) + print ' } body;' + print '} cmsg_amqp_method_t;' + + print + print 'int parse_amqp_method(' + print ' cmsg_bytes_t input,' + print ' cmsg_amqp_method_t *output) {' + print ' size_t offset = 0;' + print ' uint8_t bit_buffer = 0;' + print ' if (!parse_amqp_long(&output->id, &input, &offset))' + print ' return -CMSG_AMQP_ERROR_FRAME_ERROR;' + print ' switch (output->id) {' + for c in classes: + for m in c.methods: + m.printParseClause() + print ' default:' + print ' warn("Invalid AMQP method number 0x%%08x", output->id);' + print ' return -CMSG_AMQP_ERROR_NOT_IMPLEMENTED;' + print ' }' + print '}' + + print + print 'void write_amqp_method(' + print ' IOHandle *h,' + print ' cmsg_amqp_method_t *m) {' + print ' uint8_t bit_buffer = 0;' + print ' write_amqp_long(&m->id);' + print ' switch (m->id) {' + for c in classes: + for m in c.methods: + m.printWriteClause() + print ' default:' + print ' die("Invalid AMQP method number 0x%%08x", m->id);' + print ' }' + print '}' + +header() From e7fb92e33a10519dd9d3abccb67264b1228e2f43 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 27 Mar 2011 12:33:05 -0400 Subject: [PATCH 115/122] Add msgcount option to test3_latency --- server/test3_latency.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/test3_latency.c b/server/test3_latency.c index 36a17a7..8cb2fcb 100644 --- a/server/test3_latency.c +++ b/server/test3_latency.c @@ -54,11 +54,12 @@ int main(int argc, char *argv[]) { long bytecount = 0; int i; unsigned long hz_limit = 1000000; + unsigned long msgcount = 10000000; assert(sizeof(uint32_t) == 4); if (argc < 2) { - fprintf(stderr, "Usage: test1 []\n"); + fprintf(stderr, "Usage: test1 [ []]\n"); exit(1); } @@ -67,6 +68,11 @@ int main(int argc, char *argv[]) { } printf("hz_limit = %lu\n", hz_limit); + if (argc > 3) { + msgcount = strtoul(argv[3], NULL, 0); + } + printf("msgcount = %lu\n", msgcount); + { struct hostent *h = gethostbyname(argv[1]); if (h == NULL) { @@ -99,7 +105,7 @@ int main(int argc, char *argv[]) { gettimeofday(&start_time, NULL); - for (i = 0; i < 10000000; i++) { + for (i = 0; i < msgcount; i++) { char message[1024]; size_t msglen; while (1) { From d9d4b0200218ea299a8a673126721eafb9a0a1b2 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 27 Mar 2011 13:23:05 -0400 Subject: [PATCH 116/122] Avoid race condition between nap()'s timeout expiring and incoming work calling resume(). --- server/harness.c | 10 +++++++--- server/harness.h | 2 +- server/queue.c | 24 ++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/server/harness.c b/server/harness.c index ea222b7..2cf39e5 100644 --- a/server/harness.c +++ b/server/harness.c @@ -84,9 +84,13 @@ void suspend(void) { schedule(); } -void resume(Process *p) { - assert(p->state == PROCESS_WAITING); - enqueue_runlist(p); +int resume(Process *p) { + if (p->state == PROCESS_WAITING) { + enqueue_runlist(p); + return 0; + } else { + return -1; + } } static void driver(void (*f)(void *), void *arg) { diff --git a/server/harness.h b/server/harness.h index bd54ef0..d4e612b 100644 --- a/server/harness.h +++ b/server/harness.h @@ -40,7 +40,7 @@ extern Process *spawn(process_main_t f, void *arg); extern int nap(long millis); /* 1 for timeout expired; 0 for resumed early */ extern void suspend(void); -extern void resume(Process *p); +extern int resume(Process *p); extern IOHandle *new_iohandle(int fd); extern void delete_iohandle(IOHandle *h); diff --git a/server/queue.c b/server/queue.c index 721ad77..30305ae 100644 --- a/server/queue.c +++ b/server/queue.c @@ -158,12 +158,32 @@ static void shoveller(void *qv) { } static void throck_shovel(queue_extension_t *q) { + //int counter = 0; + retry: + //printf("throck %d %d %p\n", counter++, q->shovel_awake, q->shovel); if (!q->shovel_awake) { - q->shovel_awake = 1; if (!q->shovel) { + q->shovel_awake = 1; q->shovel = spawn(shoveller, q); } else { - resume(q->shovel); + if (resume(q->shovel) == -1) { + /* The nap() in the shoveller returned and scheduled the + shoveller *just* before we got to it, but the shoveller + hasn't had a chance to run yet, so hasn't been able to + clear q->shovel and exit. The resume() attempt failed + because q->shovel's state is PROCESS_RUNNING, now that it + has been scheduled by the return of nap(), so we know that + we should back off and try again from the top. */ + yield(); + goto retry; + } else { + /* The resume() was successful, i.e. the nap() hadn't returned + before we tried to resume(). We know that nap() will return + zero (since the timeout didn't fire before the process was + resumed), and so the existing shoveller will continue + running. */ + q->shovel_awake = 1; + } } } } From bd9f124a6246652baa07f66966f48bf424cb3781 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 27 Jun 2011 12:33:51 -0400 Subject: [PATCH 117/122] Simple multi-connection testing + stats printing --- java/hop/TestScale.java | 69 +++++++++++++++++++++++++++++++++++++++++ server/main.c | 2 ++ server/relay.c | 16 ++++++++++ server/relay.h | 2 ++ 4 files changed, 89 insertions(+) create mode 100644 java/hop/TestScale.java diff --git a/java/hop/TestScale.java b/java/hop/TestScale.java new file mode 100644 index 0000000..b921299 --- /dev/null +++ b/java/hop/TestScale.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. + */ + +package hop; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; + +/** + */ +public class TestScale { + static AtomicLong counter = new AtomicLong(); + + public static void main(final String[] args) { + try { + final String hostname = args[0]; + int count = Integer.parseInt(args[1]); + System.out.println("Hostname: " + hostname); + for (int i = 0; i < count; i++) { + new Thread(new Runnable() { public void run() { + try { + runConnection(hostname); + } catch (Exception e) { + e.printStackTrace(); + } + } }).start(); + Thread.sleep(100); + } + while (true) { + long startTime = System.currentTimeMillis(); + long startCount = counter.longValue(); + Thread.sleep(1000); + long now = System.currentTimeMillis(); + long countNow = counter.longValue(); + report(startTime, startCount, now, countNow); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void report(long t0, long c0, long t1, long c1) { + double dc = c1 - c0; + double dt = (t1 - t0) / 1000.0; + double rate = dc / dt; + System.out.println(dc + " messages in " + dt + "s = " + rate + " Hz"); + } + + public static void runConnection(String hostname) throws IOException, InterruptedException { + NodeContainer nc = new NodeContainer(); + String qName = nc.getName() + "q"; + System.out.println("Queue: " + qName); + + Relay r = new Relay(nc, hostname); + ServerApi api = new ServerApi(nc, r.getRemoteName()); + + api.createQueue(qName); + Subscription sub = api.subscribe(qName, null); + while (true) { + Object in = "a"; + api.post(qName, "", in, null); + api.flush(); + Object out = sub.getQueue().take(); + assert in.equals(out); + counter.incrementAndGet(); + } + } +} diff --git a/server/main.c b/server/main.c index 5bae2b2..4a1cc59 100644 --- a/server/main.c +++ b/server/main.c @@ -23,6 +23,7 @@ typedef unsigned char u_char; #include "queue.h" #include "direct.h" #include "fanout.h" +#include "relay.h" #include "meta.h" #include "messages.h" #include "sexpio.h" @@ -97,6 +98,7 @@ int main(int argc, char *argv[]) { init_queue(); init_direct(); init_fanout(); + init_relay(); init_meta(); #if WANT_CONSOLE_LISTENER spawn(console_listener, NULL); diff --git a/server/relay.c b/server/relay.c index 35c6469..8092d79 100644 --- a/server/relay.c +++ b/server/relay.c @@ -42,6 +42,19 @@ typedef struct relay_extension_t_ { IOHandle *outh; } relay_extension_t; +static long connection_count = 0; + +static void stats_printer(void *arg) { + while (1) { + info("%ld connections active\n", connection_count); + nap(1000); + } +} + +void init_relay(void) { + spawn(stats_printer, NULL); +} + static sexp_t *relay_extend(node_t *n, sexp_t *args) { /* TODO: outbound connections; args==NULL -> server relay, nonNULL -> outbound. */ n->extension = calloc(1, sizeof(relay_extension_t)); @@ -105,6 +118,7 @@ static void relay_main(node_t *n) { drop ours on our death */ info("Accepted connection from %s on fd %d\n", r->peername_str, r->fd); + connection_count++; iohandle_write(r->outh, cmsg_cstring_bytes("(3:hop1:0)")); ICHECK(iohandle_flush(r->outh), "iohandle_flush greeting"); @@ -189,6 +203,8 @@ static void relay_main(node_t *n) { delete_iohandle(inh); unbind_all_names_for_node(n); DECREF(n, node_destructor); + + connection_count--; } void start_relay(struct sockaddr_in const *peername, int fd) { diff --git a/server/relay.h b/server/relay.h index 01e60bc..b5b69f8 100644 --- a/server/relay.h +++ b/server/relay.h @@ -3,6 +3,8 @@ #ifndef cmsg_relay_h #define cmsg_relay_h +extern void init_relay(void); + extern void start_relay(struct sockaddr_in const *peername, int fd); #endif From 1e1b70994d4bcc196900c9cca8f5657f51a75adf Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 13 Oct 2011 18:51:13 -0400 Subject: [PATCH 118/122] My First Common-Lisp Program --- lisp/main.lisp | 33 +++++++++++ lisp/network.lisp | 47 +++++++++++++++ lisp/packages.lisp | 33 +++++++++++ lisp/sexp.lisp | 142 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 lisp/main.lisp create mode 100644 lisp/network.lisp create mode 100644 lisp/packages.lisp create mode 100644 lisp/sexp.lisp diff --git a/lisp/main.lisp b/lisp/main.lisp new file mode 100644 index 0000000..b8a3325 --- /dev/null +++ b/lisp/main.lisp @@ -0,0 +1,33 @@ +(ql:quickload "flexi-streams") +;(ql:quickload "babel") +(ql:quickload "usocket") +(ql:quickload "cl-match") + +(ql:quickload "gbbopen") +(require :portable-threads) + +(load "packages.lisp") +(load "sexp.lisp") +(load "network.lisp") + +(in-package :cl-user) + +;; (defun handle-connection (stream) +;; (spki-sexp:write-sexp (spki-sexp:read-sexp stream) stream)) + +;; (defun start-server (port) +;; (usocket:socket-server "localhost" port 'handle-connection '() +;; :in-new-thread t +;; :multi-threading t +;; :reuse-address t +;; :element-type '(unsigned-byte 8))) + +;; (start-server 5671) + +(smsg-network:serve-on-port 5671) + +;; (let ((server-socket (socket-listen "localhost" 5671 +;; :reuse-address t +;; :element-type unsigned-integer))) +;; (loop for conn = (socket-accept server-socket) +;; do (handle-connection conn))) diff --git a/lisp/network.lisp b/lisp/network.lisp new file mode 100644 index 0000000..e36803e --- /dev/null +++ b/lisp/network.lisp @@ -0,0 +1,47 @@ +(in-package :smsg-network) + +(defun command-loop (in out route) + (loop (let ((command (read-sexp in))) + (when (not (handle-inbound-command command in out route)) + (return))))) + +(defun handle-inbound-command (command in out route) + (ematch-sexp command + (("subscribe" filter sink name reply-sink reply-name) + (if (rebind-node filter nil route) + (when (plusp (length reply-sink)) + (post reply-sink reply-name (sexp-build ("subscribe-ok" (= filter))))) + (report! `(rebind-failed ,command)))) + (("unsubscribe" id) + (when (not (rebind-node id route nil)) + (report! `(rebind-failed ,command)))) + (("post" name body token) + (send name body)))) + +(defun relay (in out localname servermode) + (flet ((route (message) + (write-sexp message out) + (write-byte 13 out) + (write-byte 10 out))) + (if servermode + (route (sexp-quote ("hop" "0"))) + (ematch-sexp (read-sexp in) + (("hop" "0") t))) + (force-output out) + (route (sexp-build ("subscribe" (= localname) "" "" "" ""))) + (command-loop in out #'route))) + +(defun handle-connection (stream) + (relay stream stream (sexp-quote "smsg") t)) + +(defun serve-on-port (port) + (usocket:socket-server "localhost" port 'handle-connection '() + :in-new-thread t + :multi-threading t + :reuse-address t + :element-type '(unsigned-byte 8))) + +(defun client (localname hostname portnumber) + (let ((s (usocket:socket-stream + (usocket:socket-connect hostname portnumber :element-type '(unsigned-byte 8))))) + (relay s s localname nil))) diff --git a/lisp/packages.lisp b/lisp/packages.lisp new file mode 100644 index 0000000..20d0e8b --- /dev/null +++ b/lisp/packages.lisp @@ -0,0 +1,33 @@ +(defpackage :spki-sexp + (:use :cl :flexi-streams :cl-match) + (:shadow :read-from-string) + + (:export :read-sexp :write-sexp + + :display-hint + :make-display-hint + :display-hint-p + :copy-display-hint + :display-hint-hint + :display-hint-body + + :syntax-error + :bad-length-prefix + :bad-display-hint + :bad-input-character + :unexpected-close-paren + + :match-failure + + :convert-sexp + :sexp-quote + :sexp-build + + :match-sexp + :ematch-sexp)) + +(defpackage :smsg-network + (:use :cl :flexi-streams :spki-sexp) + (:export :relay + :serve-on-port + :client)) diff --git a/lisp/sexp.lisp b/lisp/sexp.lisp new file mode 100644 index 0000000..93d1d9e --- /dev/null +++ b/lisp/sexp.lisp @@ -0,0 +1,142 @@ +;; SPKI SEXPs for Common Lisp + +(in-package :spki-sexp) + +(define-condition syntax-error (error) ()) +(define-condition bad-length-prefix (syntax-error) ()) +(define-condition bad-display-hint (syntax-error) ()) +(define-condition bad-input-character (syntax-error) ()) +(define-condition unexpected-close-paren (syntax-error) ()) + +(define-condition match-failure (error) ()) + +(defstruct display-hint hint body) + +(defun write-integer (n output-stream) + (labels ((w (n) + (when (plusp n) + (multiple-value-bind (top-half lower-digit) + (floor n 10) + (w top-half) + (write-byte (+ lower-digit 48) output-stream))))) + (if (zerop n) + (write-byte 48 output-stream) + (w n)))) + +(defun write-sexp (sexp &optional (output-stream *standard-output*)) + (etypecase sexp + ((array (unsigned-byte 8)) + (write-integer (length sexp) output-stream) + (write-byte 58 output-stream) ;; #\: + (write-sequence sexp output-stream)) + (cons + (write-byte 40 output-stream) ;; #\( + (loop for v in sexp do (write-sexp v output-stream)) + (write-byte 41 output-stream)) ;; #\) + (display-hint + (write-byte 91 output-stream) + (write-sexp (display-hint-hint sexp) output-stream) + (write-byte 93 output-stream) + (write-sexp (display-hint-body sexp) output-stream)) + (string + (write-sexp (flexi-streams:string-to-octets sexp :external-format :utf-8) output-stream)))) + +(defun read-simple-string (input-stream &optional (len 0)) + (loop (let ((c (read-byte input-stream))) + (if (eql c 58) ;; #\: + (let ((buf (make-array len :element-type '(unsigned-byte 8)))) + (read-sequence buf input-stream) + (return buf)) + (let ((v (digit-char-p c))) + (if v + (setq len (+ (* len 10) v)) + (error 'bad-length-prefix))))))) + +(defun read-sexp-list (input-stream) + (loop for v = (read-sexp-inner input-stream) + until (eq v 'end-of-list-marker) + collect v)) + +(defun read-sexp-inner (input-stream) + (let (result) + (tagbody :retry + (setq result + (let ((c (read-byte input-stream))) + (cond + ((eql c 40) (read-sexp-list input-stream)) ;; #\( + ((eql c 41) 'end-of-list-marker) ;; #\) + ((eql c 91) ;; #\[ + (let ((hint (read-simple-string input-stream))) + (when (not (eql (read-byte input-stream) 93)) ;; #\] + (error 'bad-display-hint)) + (make-display-hint :hint hint :body (read-simple-string input-stream)))) + ((<= 48 c 57) (read-simple-string input-stream (- c 48))) ;; digits + ((<= c 32) ;; whitespace - convenience for testing + (go :retry)) + (t (error 'bad-input-character)))))) + result)) + +(defun read-sexp (&optional (input-stream *standard-input*)) + (let ((v (read-sexp-inner input-stream))) + (if (eq v 'end-of-list-marker) + (error 'unexpected-close-paren) + v))) + +(defun convert-sexp (val) + (etypecase val + ((array (unsigned-byte 8)) val) + (cons (cons (convert-sexp (car val)) + (convert-sexp (cdr val)))) + (null nil) + (display-hint (make-display-hint + :hint (convert-sexp (display-hint-hint val)) + :body (convert-sexp (display-hint-body val)))) + (string (flexi-streams:string-to-octets val :external-format :utf-8)))) + +(defmacro sexp-quote (val) + `(quote ,(convert-sexp val))) + +(defun build-sexp (stx) + (etypecase stx + ((array (unsigned-byte 8)) stx) + (cons (if (eq (car stx) '=) + (cadr stx) + `(cons ,(build-sexp (car stx)) + ,(build-sexp (cdr stx))))) + (null 'nil) + (display-hint `(make-display-hint + :hint ,(build-sexp (display-hint-hint stx)) + :body ,(build-sexp (display-hint-body stx)))) + (string (flexi-streams:string-to-octets stx :external-format :utf-8)))) + +(defmacro sexp-build (template) + (build-sexp template)) + +(defun convert-match-pattern (pattern) + (etypecase pattern + ((array (unsigned-byte 8)) `(array (1 (unsigned-byte 8)) ,(coerce pattern 'list))) + (cons `(cons ,(convert-match-pattern (car pattern)) + ,(convert-match-pattern (cdr pattern)))) + (null 'nil) + (display-hint `(struct display-hint + (:hint ,(convert-match-pattern (display-hint-hint pattern))) + (:body ,(convert-match-pattern (display-hint-body pattern))))) + (string (convert-match-pattern + (flexi-streams:string-to-octets pattern :external-format :utf-8))) + (symbol pattern))) + +(defmacro match-sexp (val &rest clauses) + `(cl-match:match ,val + ,@(mapcar (lambda (clause) + `(,(convert-match-pattern (car clause)) ,@(cdr clause))) + clauses))) + +(defmacro ematch-sexp (val &rest clauses) + `(match-sexp ,val ,@clauses (_ (error 'match-failure)))) + +;; Useful for testing +(defun read-from-string (str &optional (external-format :utf-8)) + (read-sexp (flexi-streams:make-flexi-stream + (flexi-streams:make-in-memory-input-stream + (flexi-streams:string-to-octets str :external-format external-format)) + :external-format external-format))) From 17af172e3d072b71282d8a4340611e3776d0ac46 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 6 Mar 2012 16:37:36 -0500 Subject: [PATCH 119/122] Catch up with ocamlmsg definitions --- server/main.c | 2 +- server/messages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/main.c b/server/main.c index 4a1cc59..9a36f79 100644 --- a/server/main.c +++ b/server/main.c @@ -45,7 +45,7 @@ static void factory_handle_message(node_t *n, sexp_t *m) { sexp_t *error = NULL; sexp_t *reply; if (new_node(nc, p.create.arg, &error) != NULL) { - reply = message_create_ok(); + reply = message_create_ok(NULL); } else { reply = message_create_failed(error); } diff --git a/server/messages.json b/server/messages.json index 163aab7..0397e1d 100644 --- a/server/messages.json +++ b/server/messages.json @@ -5,7 +5,7 @@ }, { "selector": "create-ok", - "args": [] + "args": ["info"] }, { "selector": "create-failed", From c539cfd526ab50b1d271f1675779edb44273a9fd Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 6 Mar 2012 18:09:14 -0500 Subject: [PATCH 120/122] Liberate hop from cmsg --- .gitignore | 4 - java/build.xml | 24 ----- java/hop/HalfQueue.java | 26 ----- java/hop/InvalidGreetingException.java | 17 --- java/hop/Node.java | 11 -- java/hop/NodeContainer.java | 102 ------------------ java/hop/Relay.java | 135 ------------------------ java/hop/ServerApi.java | 88 ---------------- java/hop/SexpBytes.java | 44 -------- java/hop/SexpDisplayHint.java | 30 ------ java/hop/SexpList.java | 40 -------- java/hop/SexpMessage.java | 61 ----------- java/hop/SexpReader.java | 137 ------------------------- java/hop/SexpSyntaxError.java | 16 --- java/hop/SexpWriter.java | 68 ------------ java/hop/Subscription.java | 38 ------- java/hop/Test1.java | 46 --------- java/hop/Test3.java | 44 -------- java/hop/TestPingPong.java | 71 ------------- java/hop/TestScale.java | 69 ------------- java/hop/TestSexpIO.java | 82 --------------- java/lib/junit-4.8.2.jar | Bin 237344 -> 0 bytes 22 files changed, 1153 deletions(-) delete mode 100644 java/build.xml delete mode 100644 java/hop/HalfQueue.java delete mode 100644 java/hop/InvalidGreetingException.java delete mode 100644 java/hop/Node.java delete mode 100644 java/hop/NodeContainer.java delete mode 100644 java/hop/Relay.java delete mode 100644 java/hop/ServerApi.java delete mode 100644 java/hop/SexpBytes.java delete mode 100644 java/hop/SexpDisplayHint.java delete mode 100644 java/hop/SexpList.java delete mode 100644 java/hop/SexpMessage.java delete mode 100644 java/hop/SexpReader.java delete mode 100644 java/hop/SexpSyntaxError.java delete mode 100644 java/hop/SexpWriter.java delete mode 100644 java/hop/Subscription.java delete mode 100644 java/hop/Test1.java delete mode 100644 java/hop/Test3.java delete mode 100644 java/hop/TestPingPong.java delete mode 100644 java/hop/TestScale.java delete mode 100644 java/hop/TestSexpIO.java delete mode 100644 java/lib/junit-4.8.2.jar diff --git a/.gitignore b/.gitignore index c235ef9..1550b3b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,3 @@ server/test3 server/test1_latency server/test3_latency depend.mk -java/out -java/build -*.iml -.idea diff --git a/java/build.xml b/java/build.xml deleted file mode 100644 index 3b8a7e3..0000000 --- a/java/build.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/hop/HalfQueue.java b/java/hop/HalfQueue.java deleted file mode 100644 index 03a5893..0000000 --- a/java/hop/HalfQueue.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -/** - */ -public class HalfQueue implements Node { - public BlockingQueue _q; - - public HalfQueue() { - _q = new LinkedBlockingQueue(); - } - - public void handle(Object message) { - _q.add(message); - } - - public BlockingQueue getQueue() { - return _q; - } -} diff --git a/java/hop/InvalidGreetingException.java b/java/hop/InvalidGreetingException.java deleted file mode 100644 index 420e46a..0000000 --- a/java/hop/InvalidGreetingException.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; - -/** - */ -public class InvalidGreetingException extends IOException { - Object _greeting; - - public InvalidGreetingException(Object greeting) { - _greeting = greeting; - } -} diff --git a/java/hop/Node.java b/java/hop/Node.java deleted file mode 100644 index e4e9ac0..0000000 --- a/java/hop/Node.java +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -/** - */ -public interface Node { - void handle(Object message); -} diff --git a/java/hop/NodeContainer.java b/java/hop/NodeContainer.java deleted file mode 100644 index 789201f..0000000 --- a/java/hop/NodeContainer.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.Flushable; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.*; - -/** - */ -public class NodeContainer implements Flushable { - public String _name; - public Map> _directory; - - public NodeContainer() { - this(UUID.randomUUID().toString()); - } - - public NodeContainer(String name) { - _name = name; - _directory = new Hashtable>(); - } - - public String getName() { - return _name; - } - - public synchronized boolean bind(String name, Node n) { - WeakReference ref = _directory.get(name); - if (ref != null && ref.get() != null) { - return false; - } - ref = new WeakReference(n); - _directory.put(name, ref); - return true; - } - - public synchronized boolean unbind(String name) { - if (!_directory.containsKey(name)) - return false; - _directory.remove(name); - return true; - } - - public void flush() throws IOException { - ArrayList fs = new ArrayList(); - synchronized (this) { - for (Map.Entry> e : _directory.entrySet()) { - Node n = e.getValue().get(); - if (n instanceof Flushable) { - fs.add((Flushable) n); - } - } - } - for (Flushable f : fs) { - f.flush(); - } - } - - public void flush(String name) throws IOException { - Flushable f; - synchronized (this) { - WeakReference ref = _directory.get(name); - if (ref == null) return; - Node n = ref.get(); - if (n == null) return; - if (!(n instanceof Flushable)) return; - f = ((Flushable) n); - } - f.flush(); - } - - public synchronized void unbindReferencesTo(Node n) { - for (Map.Entry> e : _directory.entrySet()) { - if (e.getValue().get() == n) { - _directory.remove(e.getKey()); - } - } - } - - public synchronized Node lookup(String name) { - WeakReference r = _directory.get(name); - return (r == null) ? null : r.get(); - } - - public boolean post(String sink, Object name, Object message, Object token) { - return send(sink, SexpMessage.post(name, message, token)); - } - - public boolean send(String name, Object message) { - Node n = lookup(name); - if (n == null) { - System.err.println("Warning: sending to nonexistent node " + name + "; message " + message); - return false; - } - n.handle(message); - return true; - } -} diff --git a/java/hop/Relay.java b/java/hop/Relay.java deleted file mode 100644 index 698be96..0000000 --- a/java/hop/Relay.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.*; -import java.net.Socket; - -/** - */ -public class Relay implements Runnable, Node, Flushable { - NodeContainer _container; - String _remoteName; - Socket _sock; - String _hostname; - int _port; - SexpReader _r; - OutputStream _output; - SexpWriter _w; - - public Relay(NodeContainer container, String hostname) throws IOException, InterruptedException { - this(container, hostname, 5671); - } - - public Relay(NodeContainer container, String hostname, int port) throws IOException, InterruptedException { - _container = container; - _remoteName = null; - _hostname = hostname; - _port = port; - _connect(); - } - - public String getRemoteName() { - return _remoteName; - } - - public void _connect() throws IOException, InterruptedException { - _sock = new Socket(_hostname, _port); - _sock.setTcpNoDelay(true); - _r = new SexpReader(new BufferedInputStream(_sock.getInputStream())); - _output = new BufferedOutputStream(_sock.getOutputStream()); - _w = new SexpWriter(_output); - _login(); - new Thread(this).start(); - synchronized (this) { - while (_remoteName == null) { - this.wait(); - } - } - } - - public void _login() throws IOException { - SexpList greeting = _r.readList(); - if (!greeting.getBytes(0).getDataString().equals("hop")) { - throw new InvalidGreetingException(greeting); - } - - _w.write(SexpMessage.subscribe(_container.getName(), null, null, null, null)); - } - - public void handle(Object message) { - try { - _w.write(message); - } catch (IOException ioe) { - ioe.printStackTrace(); - System.err.print("Message to be written was: "); - try { - SexpWriter.write(System.err, message); - } catch (IOException ioe2) { - ioe2.printStackTrace(); - } - System.err.println(); - } - } - - public void flush() throws IOException { - _output.flush(); - } - - public void run() { - SexpList m = null; - try { - while (true) { - m = _r.readList(); - if (m == null) { - break; - } - //System.err.println("Received: " + m); - String selector = m.getBytes(0).getDataString(); - if (selector.equals("post") && m.size() == 4) { - _container.send(m.getBytes(1).getDataString(), m.get(2)); - } else if (selector.equals("subscribe") && m.size() == 6) { - if (_remoteName != null) { - System.err.println("Double bind attempted"); - } else { - _remoteName = m.getBytes(1).getDataString(); - synchronized (this) { - this.notifyAll(); - } - if (_container.bind(_remoteName, this)) { - String replySink = m.getBytes(4).getDataString(); - if (replySink.length() > 0) { - _container.post(replySink, m.get(5), SexpMessage.subscribe_ok(_remoteName), null); - } - } else { - System.err.println("Bind failed: " + _remoteName); - } - } - } else if (selector.equals("unsubscribe") && m.size() == 2) { - if (!m.getBytes(1).getDataString().equals(_remoteName)) { - System.err.println("Unknown unbind attempted"); - } else { - if (!_container.unbind(m.getBytes(1).getDataString())) { - System.err.println("Unbind failed: " + m.get(1)); - } - } - } else { - System.err.print("Unknown message: "); - SexpWriter.write(System.err, m); - System.err.println(); - } - } - } catch (IOException ioe) { - ioe.printStackTrace(); - System.err.print("Most recent received message: "); - try { - SexpWriter.write(System.err, m); - } catch (IOException ioe2) { - ioe2.printStackTrace(); - } - System.err.println(); - } - } -} diff --git a/java/hop/ServerApi.java b/java/hop/ServerApi.java deleted file mode 100644 index bf72c6a..0000000 --- a/java/hop/ServerApi.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.Flushable; -import java.io.IOException; -import java.util.UUID; - -/** - */ -public class ServerApi implements Flushable { - public NodeContainer _container; - public String _serverName; - public String _kName; - public HalfQueue _k; - - public ServerApi(NodeContainer container, String serverName) { - _container = container; - _serverName = serverName; - _kName = UUID.randomUUID().toString(); - _k = new HalfQueue(); - _container.bind(_kName, _k); - } - - public SexpList _nextReply() throws InterruptedException, SexpSyntaxError { - Object x = _k.getQueue().take(); - if (x instanceof SexpList) return (SexpList) x; - throw new SexpSyntaxError("Unexpected non-list"); - } - - public void post(String sink, Object name, Object message, Object token) { - _container.post(_serverName, sink, SexpMessage.post(name, message, token), null); - } - - public void send(String sink, Object message) { - _container.post(_serverName, sink, message, null); - } - - public void flush() throws IOException { - _container.flush(_serverName); - } - - public synchronized Object subscribe(String source, Object filter, String sink, String name) throws InterruptedException, IOException { - send(source, SexpMessage.subscribe(filter, sink, name, _container.getName(), _kName)); - flush(); - SexpList reply = _nextReply(); - assert reply.getBytes(0).getDataString().equals(SexpMessage._subscribe_ok); - return reply.get(1); - } - - public synchronized Object subscribe(String source, Object filter, String name) throws InterruptedException, IOException { - return subscribe(source, filter, _container.getName(), name); - } - - public Subscription subscribe(String source, Object filter) throws InterruptedException, IOException { - return new Subscription(this, source, filter); - } - - public void unsubscribe(String source, Object token) throws IOException { - send(source, SexpMessage.unsubscribe(token)); - flush(); - /* TODO: optional synchronous reply? */ - } - - public synchronized Object create(String nodeClassName, Object arg) throws InterruptedException, IOException { - send("factory", SexpMessage.create(nodeClassName, arg, _container.getName(), _kName)); - flush(); - SexpList reply = _nextReply(); - SexpBytes selector = reply.getBytes(0); - if (selector.equals(SexpMessage._create_ok)) return null; - assert selector.equals(SexpMessage._create_failed); - return reply.get(1); - } - - public Object createQueue(String name) throws InterruptedException, IOException { - return create("queue", SexpList.with(name)); - } - - public Object createFanout(String name) throws InterruptedException, IOException { - return create("fanout", SexpList.with(name)); - } - - public Object createDirect(String name) throws InterruptedException, IOException { - return create("direct", SexpList.with(name)); - } -} diff --git a/java/hop/SexpBytes.java b/java/hop/SexpBytes.java deleted file mode 100644 index 1385e56..0000000 --- a/java/hop/SexpBytes.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Arrays; - -/** - */ -public class SexpBytes { - public byte[] _bytes; - - public SexpBytes(byte[] bytes) { - _bytes = bytes; - } - - public byte[] getData() { - return _bytes; - } - - public String getDataString() { - return new String(getData()); - } - - public void writeTo(OutputStream stream) throws IOException { - SexpWriter.writeSimpleString(stream, _bytes); - } - - public String toString() { - return SexpWriter.writeString(this); - } - - public boolean equals(Object other) { - return (other instanceof SexpBytes) && - Arrays.equals(_bytes, ((SexpBytes) other).getData()); - } - - public int hashCode() { - return Arrays.hashCode(_bytes); - } -} diff --git a/java/hop/SexpDisplayHint.java b/java/hop/SexpDisplayHint.java deleted file mode 100644 index 0ce41b1..0000000 --- a/java/hop/SexpDisplayHint.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; -import java.io.OutputStream; - -/** - */ -public class SexpDisplayHint extends SexpBytes { - public byte[] _hint; - - public SexpDisplayHint(byte[] hint, byte[] body) { - super(body); - _hint = hint; - } - - public byte[] getHint() { - return _hint; - } - - public void writeTo(OutputStream stream) throws IOException { - stream.write('['); - SexpWriter.writeSimpleString(stream, _hint); - stream.write(']'); - super.writeTo(stream); - } -} diff --git a/java/hop/SexpList.java b/java/hop/SexpList.java deleted file mode 100644 index 11d11c5..0000000 --- a/java/hop/SexpList.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.util.ArrayList; - -/** - */ -public class SexpList extends ArrayList { - public SexpBytes getBytes(int index) throws SexpSyntaxError { - Object x = get(index); - if (x != null && !(x instanceof SexpBytes)) { - throw new SexpSyntaxError("Unexpected non-bytes"); - } - return (SexpBytes) get(index); - } - - public SexpList getList(int index) throws SexpSyntaxError { - Object x = get(index); - if (x != null && !(x instanceof SexpList)) { - throw new SexpSyntaxError("Unexpected non-list"); - } - return (SexpList) get(index); - } - - public static SexpList empty() { - return new SexpList(); - } - - public static SexpList with(Object x) { - return empty().and(x); - } - - public SexpList and(Object x) { - this.add(x); - return this; - } -} diff --git a/java/hop/SexpMessage.java b/java/hop/SexpMessage.java deleted file mode 100644 index 32c3a42..0000000 --- a/java/hop/SexpMessage.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -/** - */ -public class SexpMessage { - public static SexpBytes _post = new SexpBytes("post".getBytes()); - public static SexpBytes _subscribe = new SexpBytes("subscribe".getBytes()); - public static SexpBytes _unsubscribe = new SexpBytes("unsubscribe".getBytes()); - public static SexpBytes _subscribe_ok = new SexpBytes("subscribe-ok".getBytes()); - public static SexpBytes _create = new SexpBytes("create".getBytes()); - public static SexpBytes _create_ok = new SexpBytes("create-ok".getBytes()); - public static SexpBytes _create_failed = new SexpBytes("create-failed".getBytes()); - - public static SexpList post(Object name, Object message, Object token) { - SexpList m = new SexpList(); - m.add(_post); - m.add(name); - m.add(message); - m.add(token); - return m; - } - - public static SexpList subscribe(Object filter, String sink, Object name, String replySink, Object replyName) { - SexpList m = new SexpList(); - m.add(_subscribe); - m.add(filter); - m.add(sink); - m.add(name); - m.add(replySink); - m.add(replyName); - return m; - } - - public static SexpList subscribe_ok(Object token) { - SexpList m = new SexpList(); - m.add(_subscribe_ok); - m.add(token); - return m; - } - - public static SexpList unsubscribe(Object token) { - SexpList m = new SexpList(); - m.add(_unsubscribe); - m.add(token); - return m; - } - - public static SexpList create(String nodeClassName, Object arg, String replySink, Object replyName) { - SexpList m = new SexpList(); - m.add(_create); - m.add(nodeClassName); - m.add(arg); - m.add(replySink); - m.add(replyName); - return m; - } -} diff --git a/java/hop/SexpReader.java b/java/hop/SexpReader.java deleted file mode 100644 index 0177fe7..0000000 --- a/java/hop/SexpReader.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; -import java.io.InputStream; - -public class SexpReader { - public InputStream _input; - - public SexpReader(InputStream input) { - _input = input; - } - - /** - * Reads a sexp length-prefix from _input. - * @return The read length, or -1 if the end of stream is reached - * @throws IOException - * @throws SexpSyntaxError - */ - public int _readLength(int lengthSoFar) throws IOException { - int length = lengthSoFar; - - while (true) { - int c = _input.read(); - if (c == -1) return -1; - if (c == ':') { - return length; - } - if (!Character.isDigit(c)) { - throw new SexpSyntaxError("Invalid length prefix"); - } - length = length * 10 + (c - '0'); - } - } - - /** - * Reads a simple length-prefixed string from _input, given either zero or the value - * of the first digit of the length-prefix being read. - * @param lengthSoFar either zero or the first digit of the length prefix to use - * @return the read string - * @throws IOException - * @throws SexpSyntaxError - */ - public byte[] _readSimpleString(int lengthSoFar) throws IOException { - int length = _readLength(lengthSoFar); - if (length == -1) return null; - byte[] buf = new byte[length]; - int offset = 0; - while (length > 0) { - int count = _input.read(buf, offset, length); - if (count == -1) { - throw new SexpSyntaxError("End-of-stream in the middle of a simple string"); - } - offset += count; - length -= count; - } - return buf; - } - - public byte[] readSimpleString() throws IOException { - return _readSimpleString(0); - } - - public SexpList _readList() throws IOException { - SexpList list = new SexpList(); - while (true) { - int c = _input.read(); - switch (c) { - case -1: - throw new SexpSyntaxError("Unclosed list"); - case ')': - return list; - default: - list.add(_read(c)); - break; - } - } - } - - public Object _read(int c) throws IOException { - while (true) { - switch (c) { - case -1: - return null; - case '(': - return _readList(); - case ')': - throw new SexpSyntaxError("Unexpected close-paren"); - case '[': - byte[] hint = readSimpleString(); - switch (_input.read()) { - case -1: - throw new SexpSyntaxError("End-of-stream between display hint and body"); - case ']': - break; - default: - throw new SexpSyntaxError("Unexpected character after display hint"); - } - byte[] body = readSimpleString(); - return new SexpDisplayHint(hint, body); - default: - if (Character.isDigit(c)) { - return new SexpBytes(_readSimpleString(c - '0')); - } else if (Character.isWhitespace(c)) { - // Skip harmless (?) whitespace - c = _input.read(); - continue; - } else { - throw new SexpSyntaxError("Unexpected character: " + c); - } - } - } - } - - public Object read() throws IOException { - return _read(_input.read()); - } - - public SexpList readList() throws IOException { - Object x = read(); - if (x != null && !(x instanceof SexpList)) { - throw new SexpSyntaxError("Unexpected non-list"); - } - return (SexpList) x; - } - - public SexpBytes readBytes() throws IOException { - Object x = read(); - if (x != null && !(x instanceof SexpBytes)) { - throw new SexpSyntaxError("Unexpected non-bytes"); - } - return (SexpBytes) x; - } -} \ No newline at end of file diff --git a/java/hop/SexpSyntaxError.java b/java/hop/SexpSyntaxError.java deleted file mode 100644 index 14c335c..0000000 --- a/java/hop/SexpSyntaxError.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; - -/** - * Reports on a syntax problem reading an S-expression. - */ -public class SexpSyntaxError extends IOException { - public SexpSyntaxError(String s) { - super(s); - } -} diff --git a/java/hop/SexpWriter.java b/java/hop/SexpWriter.java deleted file mode 100644 index 91e1db0..0000000 --- a/java/hop/SexpWriter.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; - -/** - */ -public class SexpWriter { - public OutputStream _output; - - public SexpWriter(OutputStream output) { - _output = output; - } - - public static void writeSimpleString(OutputStream stream, byte[] buf) throws IOException { - stream.write(Integer.toString(buf.length).getBytes()); - stream.write(':'); - stream.write(buf); - } - - public void write(Object x) throws IOException { - if (x instanceof String) { - writeSimpleString(_output, ((String) x).getBytes()); - return; - } - if (x instanceof byte[]) { - writeSimpleString(_output, ((byte[]) x)); - return; - } - if (x instanceof SexpBytes) { - ((SexpBytes) x).writeTo(_output); - return; - } - if (x instanceof List) { - _output.write('('); - for (Object v : ((List) x)) { - write(v); - } - _output.write(')'); - return; - } - if (x == null) { - _output.write("0:".getBytes()); - return; - } - throw new SexpSyntaxError("Unsupported sexp object type"); - } - - public static void write(OutputStream output, Object x) throws IOException { - new SexpWriter(output).write(x); - } - - public static String writeString(Object x) { - try { - ByteArrayOutputStream o = new ByteArrayOutputStream(); - write(o, x); - return new String(o.toByteArray()); - } catch (IOException ioe) { - return x.toString(); - } - } -} diff --git a/java/hop/Subscription.java b/java/hop/Subscription.java deleted file mode 100644 index 0f09010..0000000 --- a/java/hop/Subscription.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; -import java.util.UUID; -import java.util.concurrent.BlockingQueue; - -/** - */ -public class Subscription { - public ServerApi _api; - public String _source; - public Object _filter; - public String _consumerName; - public HalfQueue _consumer; - public Object _subscriptionToken; - - public Subscription(ServerApi api, String source, Object filter) throws InterruptedException, IOException { - _api = api; - _source = source; - _filter = filter; - _consumerName = UUID.randomUUID().toString(); - _consumer = new HalfQueue(); - _api._container.bind(_consumerName, _consumer); - _subscriptionToken = _api.subscribe(source, filter, _consumerName); - } - - public BlockingQueue getQueue() { - return _consumer.getQueue(); - } - - public void unsubscribe() throws IOException { - _api.unsubscribe(_source, _subscriptionToken); - } -} diff --git a/java/hop/Test1.java b/java/hop/Test1.java deleted file mode 100644 index 45e3332..0000000 --- a/java/hop/Test1.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; - -/** - */ -public class Test1 { - public static void main(String[] args) { - try { - run(args[0]); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void run(String hostname) throws IOException, InterruptedException { - NodeContainer nc = new NodeContainer(); - - System.out.println("Hostname: " + hostname); - System.out.println("Container: " + nc.getName()); - - Relay r = new Relay(nc, hostname); - ServerApi api = new ServerApi(nc, r.getRemoteName()); - - api.createQueue("q1"); - Subscription sub = api.subscribe("q1", null); - long startTime = 0; - int count = 0; - while (true) { - Object x = sub.getQueue().take(); - if (startTime == 0) { - startTime = System.currentTimeMillis(); - } - count++; - if ((count % 100000) == 0) { - long now = System.currentTimeMillis(); - double delta = (now - startTime) / 1000.0; - System.out.println("Received "+count+" messages in "+delta+" seconds, rate = " + (count / delta) + " Hz"); - } - } - } -} diff --git a/java/hop/Test3.java b/java/hop/Test3.java deleted file mode 100644 index 63512ef..0000000 --- a/java/hop/Test3.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; - -/** - */ -public class Test3 { - public static void main(String[] args) { - try { - run(args[0]); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void run(String hostname) throws IOException, InterruptedException { - NodeContainer nc = new NodeContainer(); - - System.out.println("Hostname: " + hostname); - - System.out.println("Container: " + nc.getName()); - - Relay r = new Relay(nc, hostname); - ServerApi api = new ServerApi(nc, r.getRemoteName()); - - api.createQueue("q1"); - long startTime = System.currentTimeMillis(); - int count = 0; - for (int i = 0; i < 10000000; i++) { - api.post("q1", null, Integer.toString(i), null); - count++; - if ((count % 100000) == 0) { - api.flush(); - long now = System.currentTimeMillis(); - double delta = (now - startTime) / 1000.0; - System.out.println("Sent "+count+" messages in "+delta+" seconds, rate = " + (count / delta) + " Hz"); - } - } - } -} diff --git a/java/hop/TestPingPong.java b/java/hop/TestPingPong.java deleted file mode 100644 index d8de0b6..0000000 --- a/java/hop/TestPingPong.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; - -/** - */ -public class TestPingPong { - public static void main(final String[] args) { - try { - new Thread(new Runnable() { public void run() { - try { - run1(args[0]); - } catch (Exception e) { - e.printStackTrace(); - } - } }).start(); - run2(args[0]); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void run1(String hostname) throws IOException, InterruptedException { - NodeContainer nc = new NodeContainer(); - - System.out.println("Hostname: " + hostname); - System.out.println("Container: " + nc.getName()); - - Relay r = new Relay(nc, hostname); - ServerApi api = new ServerApi(nc, r.getRemoteName()); - - api.createQueue("req"); - Subscription sub = api.subscribe("req", null); - while (true) { - Object x = sub.getQueue().take(); - //System.out.println("Message: " + x); - api.post("rep", "reply", SexpList.with("ok").and(x), null); - api.flush(); - } - } - - public static void run2(String hostname) throws IOException, InterruptedException { - NodeContainer nc = new NodeContainer(); - - System.out.println("Hostname: " + hostname); - System.out.println("Container: " + nc.getName()); - - Relay r = new Relay(nc, hostname); - ServerApi api = new ServerApi(nc, r.getRemoteName()); - - api.createQueue("req"); - api.createQueue("rep"); - Subscription sub = api.subscribe("rep", null); - long startTime = System.currentTimeMillis(); - for (int i = 0; i < 100000; i++) { - api.post("req", "request", Integer.toString(i), null); - api.flush(); - sub.getQueue().take(); - int j = i + 1; - if ((j % 100) == 0) { - long now = System.currentTimeMillis(); - double delta = (now - startTime) / 1000.0; - System.out.println("Message " + j + ": " + (j / delta) + " Hz"); - } - } - } -} diff --git a/java/hop/TestScale.java b/java/hop/TestScale.java deleted file mode 100644 index b921299..0000000 --- a/java/hop/TestScale.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicLong; - -/** - */ -public class TestScale { - static AtomicLong counter = new AtomicLong(); - - public static void main(final String[] args) { - try { - final String hostname = args[0]; - int count = Integer.parseInt(args[1]); - System.out.println("Hostname: " + hostname); - for (int i = 0; i < count; i++) { - new Thread(new Runnable() { public void run() { - try { - runConnection(hostname); - } catch (Exception e) { - e.printStackTrace(); - } - } }).start(); - Thread.sleep(100); - } - while (true) { - long startTime = System.currentTimeMillis(); - long startCount = counter.longValue(); - Thread.sleep(1000); - long now = System.currentTimeMillis(); - long countNow = counter.longValue(); - report(startTime, startCount, now, countNow); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - public static void report(long t0, long c0, long t1, long c1) { - double dc = c1 - c0; - double dt = (t1 - t0) / 1000.0; - double rate = dc / dt; - System.out.println(dc + " messages in " + dt + "s = " + rate + " Hz"); - } - - public static void runConnection(String hostname) throws IOException, InterruptedException { - NodeContainer nc = new NodeContainer(); - String qName = nc.getName() + "q"; - System.out.println("Queue: " + qName); - - Relay r = new Relay(nc, hostname); - ServerApi api = new ServerApi(nc, r.getRemoteName()); - - api.createQueue(qName); - Subscription sub = api.subscribe(qName, null); - while (true) { - Object in = "a"; - api.post(qName, "", in, null); - api.flush(); - Object out = sub.getQueue().take(); - assert in.equals(out); - counter.incrementAndGet(); - } - } -} diff --git a/java/hop/TestSexpIO.java b/java/hop/TestSexpIO.java deleted file mode 100644 index 3acd4a6..0000000 --- a/java/hop/TestSexpIO.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2011 Tony Garnock-Jones. All rights reserved. - */ - -package hop; - -import junit.framework.TestCase; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - */ -public class TestSexpIO extends TestCase { - public Object read(String s) throws IOException { - return new SexpReader(new ByteArrayInputStream(s.getBytes())).read(); - } - - public byte[] write(Object x) throws IOException { - ByteArrayOutputStream o = new ByteArrayOutputStream(); - new SexpWriter(o).write(x); - return o.toByteArray(); - } - - public void assertBytesEqual(String expected, byte[] actual) { - assertBytesEqual(expected.getBytes(), actual); - } - - public void assertBytesEqual(byte[] expected, byte[] actual) { - assertEquals(expected.length, actual.length); - for (int i = 0; i < expected.length; i++) { - assertEquals(expected[i], actual[i]); - } - } - - public void testEndOfStream() throws IOException { - assertNull(read("")); - } - - public void testEmptyList() throws IOException { - assertEquals(new ArrayList(), read("()")); - } - - public void testSimpleString() throws IOException { - assertBytesEqual("hello", ((SexpBytes) read("5:hello")).getData()); - } - - public void testDisplayHint() throws IOException { - SexpDisplayHint v = (SexpDisplayHint) read("[1:h]1:b"); - assertBytesEqual("b", v.getData()); - assertBytesEqual("h", v.getHint()); - assertBytesEqual("[1:h]1:b", write(v)); - } - - public void testSimpleList() throws IOException { - List l = (List) read("(1:a1:b1:c)"); - assertEquals(3, l.size()); - assertBytesEqual("a", ((SexpBytes) l.get(0)).getData()); - assertBytesEqual("b", ((SexpBytes) l.get(1)).getData()); - assertBytesEqual("c", ((SexpBytes) l.get(2)).getData()); - } - - public void testNestedList() throws IOException { - List l = (List) read("(1:a(1:b1:c)())"); - assertEquals(3, l.size()); - assertBytesEqual("a", ((SexpBytes) l.get(0)).getData()); - List k = (List) l.get(1); - assertEquals(2, k.size()); - assertBytesEqual("b", ((SexpBytes) k.get(0)).getData()); - assertBytesEqual("c", ((SexpBytes) k.get(1)).getData()); - assertEquals(new ArrayList(), l.get(2)); - assertBytesEqual("(1:b1:c)", write(k)); - assertBytesEqual("(1:a(1:b1:c)())", write(l)); - } - - public void testNullWrite() throws IOException { - assertBytesEqual("0:", write(null)); - } -} diff --git a/java/lib/junit-4.8.2.jar b/java/lib/junit-4.8.2.jar deleted file mode 100644 index 5b4bb849af9583fec1ea0a0ccc0d571ef49aa8ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237344 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 Date: Fri, 11 May 2012 08:53:27 -0400 Subject: [PATCH 121/122] Move to subdirectory in preparation for repo merge. --- .gitignore | 10 - amqp0-9-1.stripped.xml | 459 ------------ doc/Sexp.txt | 699 ------------------ experiments/cmsg/.gitignore | 5 + {server => experiments/cmsg}/Makefile | 5 +- TODO => experiments/cmsg/TODO | 0 {server => experiments/cmsg}/cmsg_private.h | 0 {server => experiments/cmsg}/codegen.py | 4 +- {server => experiments/cmsg}/dataq.c | 0 {server => experiments/cmsg}/dataq.h | 0 {server => experiments/cmsg}/direct.c | 0 {server => experiments/cmsg}/direct.h | 0 {server => experiments/cmsg}/fanout.c | 0 {server => experiments/cmsg}/fanout.h | 0 {server => experiments/cmsg}/harness.c | 0 {server => experiments/cmsg}/harness.h | 0 {server => experiments/cmsg}/hashtable.c | 0 {server => experiments/cmsg}/hashtable.h | 0 {server => experiments/cmsg}/main.c | 0 {server => experiments/cmsg}/meta.c | 0 {server => experiments/cmsg}/meta.h | 0 .../{ => cmsg/misc}/direct_scheduling.patch | 0 t0 => experiments/cmsg/misc/t0 | 0 t1 => experiments/cmsg/misc/t1 | 0 t2 => experiments/cmsg/misc/t2 | 0 t4 => experiments/cmsg/misc/t4 | 0 t5 => experiments/cmsg/misc/t5 | 0 t6 => experiments/cmsg/misc/t6 | 0 {server => experiments/cmsg}/net.c | 0 {server => experiments/cmsg}/net.h | 0 {server => experiments/cmsg}/node.c | 0 {server => experiments/cmsg}/node.h | 0 {server => experiments/cmsg}/queue.c | 0 {server => experiments/cmsg}/queue.h | 0 {server => experiments/cmsg}/ref.h | 0 {server => experiments/cmsg}/relay.c | 0 {server => experiments/cmsg}/relay.h | 0 {server => experiments/cmsg}/sexp.c | 0 {server => experiments/cmsg}/sexp.h | 0 {server => experiments/cmsg}/sexpio.c | 0 {server => experiments/cmsg}/sexpio.h | 0 {server => experiments/cmsg}/subscription.c | 0 {server => experiments/cmsg}/subscription.h | 0 {server => experiments/cmsg}/util.c | 0 {lisp => experiments/lisp}/main.lisp | 0 {lisp => experiments/lisp}/network.lisp | 0 {lisp => experiments/lisp}/packages.lisp | 0 {lisp => experiments/lisp}/sexp.lisp | 0 server/messages.json | 42 -- server/test1.c | 93 --- server/test1_latency.c | 150 ---- server/test3.c | 92 --- server/test3_latency.c | 149 ---- specgen.py | 229 ------ 54 files changed, 9 insertions(+), 1928 deletions(-) delete mode 100644 .gitignore delete mode 100644 amqp0-9-1.stripped.xml delete mode 100644 doc/Sexp.txt create mode 100644 experiments/cmsg/.gitignore rename {server => experiments/cmsg}/Makefile (85%) rename TODO => experiments/cmsg/TODO (100%) rename {server => experiments/cmsg}/cmsg_private.h (100%) rename {server => experiments/cmsg}/codegen.py (96%) rename {server => experiments/cmsg}/dataq.c (100%) rename {server => experiments/cmsg}/dataq.h (100%) rename {server => experiments/cmsg}/direct.c (100%) rename {server => experiments/cmsg}/direct.h (100%) rename {server => experiments/cmsg}/fanout.c (100%) rename {server => experiments/cmsg}/fanout.h (100%) rename {server => experiments/cmsg}/harness.c (100%) rename {server => experiments/cmsg}/harness.h (100%) rename {server => experiments/cmsg}/hashtable.c (100%) rename {server => experiments/cmsg}/hashtable.h (100%) rename {server => experiments/cmsg}/main.c (100%) rename {server => experiments/cmsg}/meta.c (100%) rename {server => experiments/cmsg}/meta.h (100%) rename experiments/{ => cmsg/misc}/direct_scheduling.patch (100%) rename t0 => experiments/cmsg/misc/t0 (100%) rename t1 => experiments/cmsg/misc/t1 (100%) rename t2 => experiments/cmsg/misc/t2 (100%) rename t4 => experiments/cmsg/misc/t4 (100%) rename t5 => experiments/cmsg/misc/t5 (100%) rename t6 => experiments/cmsg/misc/t6 (100%) rename {server => experiments/cmsg}/net.c (100%) rename {server => experiments/cmsg}/net.h (100%) rename {server => experiments/cmsg}/node.c (100%) rename {server => experiments/cmsg}/node.h (100%) rename {server => experiments/cmsg}/queue.c (100%) rename {server => experiments/cmsg}/queue.h (100%) rename {server => experiments/cmsg}/ref.h (100%) rename {server => experiments/cmsg}/relay.c (100%) rename {server => experiments/cmsg}/relay.h (100%) rename {server => experiments/cmsg}/sexp.c (100%) rename {server => experiments/cmsg}/sexp.h (100%) rename {server => experiments/cmsg}/sexpio.c (100%) rename {server => experiments/cmsg}/sexpio.h (100%) rename {server => experiments/cmsg}/subscription.c (100%) rename {server => experiments/cmsg}/subscription.h (100%) rename {server => experiments/cmsg}/util.c (100%) rename {lisp => experiments/lisp}/main.lisp (100%) rename {lisp => experiments/lisp}/network.lisp (100%) rename {lisp => experiments/lisp}/packages.lisp (100%) rename {lisp => experiments/lisp}/sexp.lisp (100%) delete mode 100644 server/messages.json delete mode 100644 server/test1.c delete mode 100644 server/test1_latency.c delete mode 100644 server/test3.c delete mode 100644 server/test3_latency.c delete mode 100644 specgen.py diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1550b3b..0000000 --- a/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -scratch/ -*.o -server/messages.h -server/messages.c -server/cmsg -server/test1 -server/test3 -server/test1_latency -server/test3_latency -depend.mk diff --git a/amqp0-9-1.stripped.xml b/amqp0-9-1.stripped.xml deleted file mode 100644 index 762519b..0000000 --- a/amqp0-9-1.stripped.xml +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/doc/Sexp.txt b/doc/Sexp.txt deleted file mode 100644 index caf4542..0000000 --- a/doc/Sexp.txt +++ /dev/null @@ -1,699 +0,0 @@ -Network Working Group R. Rivest -Internet Draft May 4, 1997 -Expires November 4, 1997 - - - S-Expressions - draft-rivest-sexp-00.txt - - -Status of this Memo - - Distribution of this memo is unlimited. - - This document is an Internet-Draft. Internet Drafts are working - documents of the Internet Engineering Task Force (IETF), its Areas, - and its Working Groups. Note that other groups may also distribute - working documents as Internet Drafts. - - Internet Drafts are draft documents valid for a maximum of six - months, and may be updated, replaced, or obsoleted by other documents - at any time. It is not appropriate to use Internet Drafts as - reference material, or to cite them other than as a ``working draft'' - or ``work in progress.'' - - To learn the current status of any Internet-Draft, please check the - ``1id-abstracts.txt'' listing contained in the internet-drafts Shadow - Directories on: ftp.is.co.za (Africa), nic.nordu.net (Europe), - ds.internic.net (US East Coast), ftp.isi.edu (US West Coast), - or munnari.oz.au (Pacific Rim) - - -Abstract - -This memo describes a data structure called "S-expressions" that are -suitable for representing arbitrary complex data structures. We make -precise the encodings of S-expressions: we give a "canonical form" for -S-expressions, described two "transport" representations, and also -describe an "advanced" format for display to people. - - - -1. Introduction - -S-expressions are data structures for representing complex data. They -are either byte-strings ("octet-strings") or lists of simpler -S-expressions. Here is a sample S-expression: - - (snicker "abc" (#03# |YWJj|)) - -It is a list of length three: - - -- the octet-string "snicker" - - -- the octet-string "abc" - - -- a sub-list containing two elements: - - the hexadecimal constant #03# - - the base-64 constant |YWJj| (which is the same as "abc") - -This note gives a specific proposal for constructing and utilizing -S-expressions. The proposal is independent of any particular application. - -Here are the design goals for S-expressions: - - -- generality: S-expressions should be good at representing arbitrary - data. - - -- readability: it should be easy for someone to examine and - understand the structure of an S-expression. - - -- economy: S-expressions should represent data compactly. - - -- tranportability: S-expressions should be easy to transport - over communication media (such as email) that are known to be - less than perfect. - - -- flexibility: S-expressions should make it relatively simple to - modify and extend data structures. - - -- canonicalization: it should be easy to produce a unique - "canonical" form of an S-expression, for digital signature purposes. - - -- efficiency: S-expressions should admit in-memory representations - that allow efficient processing. - - -Section 2 gives an introduction to S-expressions. -Section 3 discusses the character sets used. -Section 4 presents the various representations of octet-strings. -Section 5 describes how to represent lists. -Section 6 discusses how S-expressions are represented for various uses. -Section 7 gives a BNF syntax for S-expressions. -Section 8 talks about how S-expressions might be represented in memory. -Section 9 briefly describes implementations for handling S-expressions. -Section 10 discusses how applications might utilize S-expressions. -Section 11 gives historical notes on S-expressions. -Section 12 gives references. - -2. S-expressions -- informal introduction - -Informally, an S-expression is either: - -- an octet-string, or - -- a finite list of simpler S-expressions. - -An octet-string is a finite sequence of eight-bit octets. There may be -many different but equivalent ways of representing an octet-string - - abc -- as a token - - "abc" -- as a quoted string - - #616263# -- as a hexadecimal string - - 3:abc -- as a length-prefixed "verbatim" encoding - - {MzphYmM=} -- as a base-64 encoding of the verbatim encoding - (that is, an encoding of "3:abc") - - |YWJj| -- as a base-64 encoding of the octet-string "abc" - -These encodings are all equivalent; they all denote the same octet string. - -We will give details of these encodings later on, and also describe how to -give a "display type" to a byte string. - -A list is a finite sequence of zero or more simpler S-expressions. A list -may be represented by using parentheses to surround the sequence of encodings -of its elements, as in: - - (abc (de #6667#) "ghi jkl") - -As we see, there is variability possible in the encoding of an -S-expression. In some cases, it is desirable to standardize or -restrict the encodings; in other cases it is desirable to have no -restrictions. The following are the target cases we aim to handle: - - -- a "transport" encoding for transporting the S-expression between - computers. - - -- a "canonical" encoding, used when signing the S-expression. - - -- an "advanced" encoding used for input/output to people. - - -- an "in-memory" encoding used for processing the S-expression in - the computer. - -These need not be different; in this proposal the canonical encoding -is the same as the transport encoding, for example. In this note we -propose (related) encoding techniques for each of these uses. - -3. Character set - -We will be describing encodings of S-expressions. Except when giving -"verbatim" encodings, the character set used is limited to the following -characters in US-ASCII: - Alphabetic: A B ... Z a b ... z - numeric: 0 1 ... 9 - whitespace: space, horizontal tab, vertical tab, form-feed - carriage-return, line-feed - The following graphics characters, which we call "pseudo-alphabetic": - - hyphen or minus - . period - / slash - _ underscore - : colon - * asterisk - + plus - = equal - The following graphics characters, which are "reserved punctuation": - ( left parenthesis - ) right parenthesis - [ left bracket - ] right bracket - { left brace - } right brace - | vertical bar - # number sign - " double quote - & ampersand - \ backslash - The following characters are unused and unavailable, except in - "verbatim" encodings: - ! exclamation point - % percent - ^ circumflex - ~ tilde - ; semicolon - ' apostrophe - , comma - < less than - > greater than - ? question mark - - -4. Octet string representations - -This section describes in detail the ways in which an octet-string may -be represented. - -We recall that an octet-string is any finite sequence of octets, and -that the octet-string may have length zero. - - -4.1 Verbatim representation - -A verbatim encoding of an octet string consists of four parts: - - -- the length (number of octets) of the octet-string, - given in decimal most significant digit first, with - no leading zeros. - - -- a colon ":" - - -- the octet string itself, verbatim. - -There are no blanks or whitespace separating the parts. No "escape -sequences" are interpreted in the octet string. This encoding is also -called a "binary" or "raw" encoding. - -Here are some sample verbatim encodings: - - 3:abc - 7:subject - 4::::: - 12:hello world! - 10:abcdefghij - 0: - -4.2 Quoted-string representation - -The quoted-string representation of an octet-string consists of: - - -- an optional decimal length field - - -- an initial double-quote (") - - -- the octet string with "C" escape conventions (\n,etc) - - -- a final double-quote (") - -The specified length is the length of the resulting string after any -escape sequences have been handled. The string does not have any -"terminating NULL" that C includes, and the length does not count such -a character. - -The length is optional. - -The escape conventions within the quoted string are as follows (these follow -the "C" programming language conventions, with an extension for -ignoring line terminators of just LF or CRLF): - \b -- backspace - \t -- horizontal tab - \v -- vertical tab - \n -- new-line - \f -- form-feed - \r -- carriage-return - \" -- double-quote - \' -- single-quote - \\ -- back-slash - \ooo -- character with octal value ooo (all three digits - must be present) - \xhh -- character with hexadecimal value hh (both digits - must be present) - \ -- causes carriage-return to be ignored. - \ -- causes linefeed to be ignored - \ -- causes CRLF to be ignored. - \ -- causes LFCR to be ignored. - -Here are some examples of quoted-string encodings: - - "subject" - "hi there" - 7"subject" - 3"\n\n\n" - "This has\n two lines." - "This has\ - one." - "" - -4.3 Token representation - -An octet string that meets the following conditions may be given -directly as a "token". - - -- it does not begin with a digit - - -- it contains only characters that are - -- alphabetic (upper or lower case), - -- numeric, or - -- one of the eight "pseudo-alphabetic" punctuation marks: - - . / _ : * + = - (Note: upper and lower case are not equivalent.) - (Note: A token may begin with punctuation, including ":"). - -Here are some examples of token representations: - - subject - not-before - class-of-1997 - //microsoft.com/names/smith - * - - -4.4 Hexadecimal representation - -An octet-string may be represented with a hexadecimal encoding consisting of: - - -- an (optional) decimal length of the octet string - - -- a sharp-sign "#" - - -- a hexadecimal encoding of the octet string, with each octet - represented with two hexadecimal digits, most significant - digit first. - - -- a sharp-sign "#" - -There may be whitespace inserted in the midst of the hexadecimal -encoding arbitrarily; it is ignored. It is an error to have -characters other than whitespace and hexadecimal digits. - -Here are some examples of hexadecimal encodings: - - #616263# -- represents "abc" - 3#616263# -- also represents "abc" - # 616 - 263 # -- also represents "abc" - - -4.5 Base-64 representation - -An octet-string may be represented in a base-64 coding consisting of: - - -- an (optional) decimal length of the octet string - - -- a vertical bar "|" - - -- the rfc 1521 base-64 encoding of the octet string. - - -- a final vertical bar "|" - -The base-64 encoding uses only the characters - A-Z a-z 0-9 + / = -It produces four characters of output for each three octets of input. -If the input has one or two left-over octets of input, it produces an -output block of length four ending in two or one equals signs, respectively. -Output routines compliant with this standard MUST output the equals signs -as specified. Input routines MAY accept inputs where the equals signs are -dropped. - -There may be whitespace inserted in the midst of the base-64 encoding -arbitrarily; it is ignored. It is an error to have characters other -than whitespace and base-64 characters. - -Here are some examples of base-64 encodings: - - |YWJj| -- represents "abc" - | Y W - J j | -- also represents "abc" - 3|YWJj| -- also represents "abc" - |YWJjZA==| -- represents "abcd" - |YWJjZA| -- also represents "abcd" - - -4.6 Display hint - -Any octet string may be preceded by a single "display hint". - -The purposes of the display hint is to provide information on how -to display the octet string to a user. It has no other function. -Many of the MIME types work here. - -A display-hint is an octet string surrounded by square brackets. -There may be whitespace separating the octet string from the -surrounding brackets. Any of the legal formats may be used for the -octet string. - -Here are some examples of display-hints: - - [image/gif] - [URI] - [charset=unicode-1-1] - [text/richtext] - [application/postscript] - [audio/basic] - ["http://abc.com/display-types/funky.html"] - -In applications an octet-string that is untyped may be considered to have -a pre-specified "default" mime type. The mime type - "text/plain; charset=iso-8859-1" -is the standard default. - - -4.7 Equality of octet-strings - -Two octet strings are considered to be "equal" if and only if they -have the same display hint and the same data octet strings. - -Note that octet-strings are "case-sensitive"; the octet-string "abc" -is not equal to the octet-string "ABC". - -An untyped octet-string can be compared to another octet-string (typed -or not) by considering it as a typed octet-string with the default -mime-type. - - -5. Lists - -Just as with octet-strings, there are several ways to represent an -S-expression. Whitespace may be used to separate list elements, but -they are only required to separate two octet strings when otherwise -the two octet strings might be interpreted as one, as when one token -follows another. Also, whitespace may follow the initial left -parenthesis, or precede the final right parenthesis. - -Here are some examples of encodings of lists: - - (a b c) - - ( a ( b c ) ( ( d e ) ( e f ) ) ) - - (11:certificate(6:issuer3:bob)(7:subject5:alice)) - - ({3Rt=} "1997" murphy 3:{XC++}) - - -6. Representation types - -There are three "types" of representations: - - -- canonical - - -- basic transport - - -- advanced transport - -The first two MUST be supported by any implementation; the last is -optional. - - -6.1 Canonical representation - -This canonical representation is used for digital signature purposes, -transmission, etc. It is uniquely defined for each S-expression. It -is not particularly readable, but that is not the point. It is -intended to be very easy to parse, to be reasonably economical, and to -be unique for any S-expression. - -The "canonical" form of an S-expression represents each octet-string -in verbatim mode, and represents each list with no blanks separating -elements from each other or from the surrounding parentheses. - -Here are some examples of canonical representations of S-expressions: - - (6:issuer3:bob) - - (4:icon[12:image/bitmap]9:xxxxxxxxx) - - (7:subject(3:ref5:alice6:mother)) - - -6.2 Basic transport representation - -There are two forms of the "basic transport" representation: - - -- the canonical representation - - -- an rfc-2045 base-64 representation of the canonical representation, - surrounded by braces. - -The transport mechanism is intended to provide a universal means of -representing S-expressions for transport from one machine to another. - -Here are some examples of an S-expression represented in basic -transport mode: - - (1:a1:b1:c) - - {KDE6YTE6YjE6YykA} - - (this is the same S-expression encoded in base-64) - -There is a difference between the brace notation for base-64 used here -and the || notation for base-64'd octet-strings described above. Here -the base-64 contents are converted to octets, and then re-scanned as -if they were given originally as octets. With the || notation, the -contents are just turned into an octet-string. - - -6.3 Advanced transport representation - -The "advanced transport" representation is intended to provide more -flexible and readable notations for documentation, design, debugging, -and (in some cases) user interface. - -The advanced transport representation allows all of the representation -forms described above, include quoted strings, base-64 and hexadecimal -representation of strings, tokens, representations of strings with -omitted lengths, and so on. - - -7. BNF for syntax - -We give separate BNF's for canonical and advanced forms of S-expressions. -We use the following notation: - * means 0 or more occurrences of - + means 1 or more occurrences of - ? means 0 or 1 occurrences of - parentheses are used for grouping, as in ( | )* - -For canonical and basic transport: - - :: | - :: ? ; - :: ; - :: "[" "]" ; - :: ":" ; - :: + ; - -- decimal numbers should have no unnecessary leading zeros - -- any string of bytes, of the indicated length - :: "(" * ")" ; - :: "0" | ... | "9" ; - -For advanced transport: - - :: | - :: ? ; - :: | | | | - ; - :: "[" "]" ; - :: ":" ; - :: + ; - -- decimal numbers should have no unnecessary leading zeros - -- any string of bytes, of the indicated length - :: + ; - :: ? "|" ( | )* "|" ; - :: "#" ( | )* "#" ; - :: ? - :: "\"" "\"" - :: "(" ( | )* ")" ; - :: * ; - :: | | ; - :: | | ; - :: "a" | ... | "z" ; - :: "A" | ... | "Z" ; - :: "0" | ... | "9" ; - :: | "A" | ... | "F" | "a" | ... | "f" ; - :: "-" | "." | "/" | "_" | ":" | "*" | "+" | "=" ; - :: " " | "\t" | "\r" | "\n" ; - :: | | "+" | "/" | "=" ; - :: "" ; - -8. In-memory representations - -For processing, the S-expression would typically be parsed and represented -in memory in a more more amenable to efficient processing. We suggest -two alternatives: - - -- "list-structure" - - -- "array-layout" - -We only sketch these here, as they are only suggestive. The code referenced -below illustrates these styles in more detail. - - -8.1. List-structure memory representation - -Here there are separate records for simple-strings, strings, and -lists. An S-expression of the form ("abc" "de") would require two -records for the simple strings, two for the strings, and two for the -list elements. This is a fairly conventional representation, and -details are omitted here. - -8.2 Array-layout memory representation - -Here each S-expression is represented as a contiguous array of bytes. -The first byte codes the "type" of the S-expression: - - 01 octet-string - - 02 octet-string with display-hint - - 03 beginning of list (and 00 is used for "end of list") - -Each of the three types is immediately followed by a k-byte integer -indicating the size (in bytes) of the following representation. Here -k is an integer that depends on the implementation, it might be -anywhere from 2 to 8, but would be fixed for a given implementation; -it determines the size of the objects that can be handled. The transport -and canonical representations are independent of the choice of k made by -the implementation. - -Although the length of lists are not given in the usual S-expression -notations, it is easy to fill them in when parsing; when you reach a -right-parenthesis you know how long the list representation was, and -where to go back to fill in the missing length. - - -8.2.1 Octet string - -This is represented as follows: - - 01 - -For example (here k = 2) - - 01 0003 a b c - -8.2.2 Octet-string with display-hint - -This is represented as follows: - - 02 - 01 /* for display-type */ - 01 /* for octet-string */ - -For example, the S-expression - - [gif] #61626364# - -would be represented as (with k = 2) - - 02 000d - 01 0003 g i f - 01 0004 61 62 63 64 - -8.2.3 List - -This is represented as - - 03 ... 00 - -For example, the list (abc [d]ef (g)) is represented in memory as (with k=2) - - 03 001b - 01 0003 a b c - 02 0009 - 01 0001 d - 01 0002 e f - 03 0005 - 01 0001 g - 00 - 00 - -9. Code - -There is code available for reading and parsing the various -S-expression formats proposed here. - -See http://theory.lcs.mit.edu/~rivest/sexp.html - - -10. Utilization of S-expressions - -This note has described S-expressions in general form. Application writers -may wish to restrict their use of S-expressions in various ways. Here are -some possible restrictions that might be considered: - - -- no display-hints - -- no lengths on hexadecimal, quoted-strings, or base-64 encodings - -- no empty lists - -- no empty octet-strings - -- no lists having another list as its first element - -- no base-64 or hexadecimal encodings - -- fixed limits on the size of octet-strings - -11. Historical note - -The S-expression technology described here was originally developed -for ``SDSI'' (the Simple Distributed Security Infrastructure by -Lampson and Rivest [SDSI]) in 1996, although the origins clearly date -back to McCarthy's LISP programming language. It was further refined -and improved during the merger of SDSI and SPKI [SPKI] during the -first half of 1997. S-expressions are similar to, but more readable -and flexible than, Bernstein's "net-strings" [BERN]. - -12. References - -[SDSI] "A Simple Distributed Security Architecture", by - Butler Lampson, and Ronald L. Rivest - http://theory.lcs.mit.edu/~cis/sdsi.html - -[SPKI] SPKI--A - Simple Public Key Infrastructure - -[BERN] Dan Bernstein's "net-strings"; Internet Draft - draft-bernstein-netstrings-02.txt - -Author's Address - - Ronald L. Rivest - Room 324, 545 Technology Square - MIT Laboratory for Computer Science - Cambridge, MA 02139 - - rivest@theory.lcs.mit.edu - - diff --git a/experiments/cmsg/.gitignore b/experiments/cmsg/.gitignore new file mode 100644 index 0000000..8c0763f --- /dev/null +++ b/experiments/cmsg/.gitignore @@ -0,0 +1,5 @@ +*.o +messages.h +messages.c +cmsg +depend.mk diff --git a/server/Makefile b/experiments/cmsg/Makefile similarity index 85% rename from server/Makefile rename to experiments/cmsg/Makefile index dda4a5a..d225937 100644 --- a/server/Makefile +++ b/experiments/cmsg/Makefile @@ -25,10 +25,10 @@ $(TARGET): $(OBJECTS) %.o: %.c $(CC) $(CFLAGS) -c $< -messages.c: messages.json codegen.py +messages.c: ../../protocol/messages.json codegen.py python codegen.py body > $@ -messages.h: messages.json codegen.py +messages.h: ../../protocol/messages.json codegen.py python codegen.py header > $@ clean: @@ -36,7 +36,6 @@ clean: rm -f $(OBJECTS) rm -rf *.dSYM rm -f depend.mk messages.c messages.h - rm -f test1 test3 test1.o test3.o test1_latency test3_latency test1_latency.o test3_latency.o depend.mk: touch messages.h diff --git a/TODO b/experiments/cmsg/TODO similarity index 100% rename from TODO rename to experiments/cmsg/TODO diff --git a/server/cmsg_private.h b/experiments/cmsg/cmsg_private.h similarity index 100% rename from server/cmsg_private.h rename to experiments/cmsg/cmsg_private.h diff --git a/server/codegen.py b/experiments/cmsg/codegen.py similarity index 96% rename from server/codegen.py rename to experiments/cmsg/codegen.py index 0810bd1..f982fef 100644 --- a/server/codegen.py +++ b/experiments/cmsg/codegen.py @@ -21,8 +21,8 @@ class MessageType: def format_args(self, template, separator = ', '): return separator.join([template % (x,) for x in self.argnames]) -with file("messages.json") as f: - spec = map(MessageType, json.load(f)) +with file("../../protocol/messages.json") as f: + spec = map(MessageType, json.load(f)['definitions']) def entrypoint_header(): print copyright_stmt diff --git a/server/dataq.c b/experiments/cmsg/dataq.c similarity index 100% rename from server/dataq.c rename to experiments/cmsg/dataq.c diff --git a/server/dataq.h b/experiments/cmsg/dataq.h similarity index 100% rename from server/dataq.h rename to experiments/cmsg/dataq.h diff --git a/server/direct.c b/experiments/cmsg/direct.c similarity index 100% rename from server/direct.c rename to experiments/cmsg/direct.c diff --git a/server/direct.h b/experiments/cmsg/direct.h similarity index 100% rename from server/direct.h rename to experiments/cmsg/direct.h diff --git a/server/fanout.c b/experiments/cmsg/fanout.c similarity index 100% rename from server/fanout.c rename to experiments/cmsg/fanout.c diff --git a/server/fanout.h b/experiments/cmsg/fanout.h similarity index 100% rename from server/fanout.h rename to experiments/cmsg/fanout.h diff --git a/server/harness.c b/experiments/cmsg/harness.c similarity index 100% rename from server/harness.c rename to experiments/cmsg/harness.c diff --git a/server/harness.h b/experiments/cmsg/harness.h similarity index 100% rename from server/harness.h rename to experiments/cmsg/harness.h diff --git a/server/hashtable.c b/experiments/cmsg/hashtable.c similarity index 100% rename from server/hashtable.c rename to experiments/cmsg/hashtable.c diff --git a/server/hashtable.h b/experiments/cmsg/hashtable.h similarity index 100% rename from server/hashtable.h rename to experiments/cmsg/hashtable.h diff --git a/server/main.c b/experiments/cmsg/main.c similarity index 100% rename from server/main.c rename to experiments/cmsg/main.c diff --git a/server/meta.c b/experiments/cmsg/meta.c similarity index 100% rename from server/meta.c rename to experiments/cmsg/meta.c diff --git a/server/meta.h b/experiments/cmsg/meta.h similarity index 100% rename from server/meta.h rename to experiments/cmsg/meta.h diff --git a/experiments/direct_scheduling.patch b/experiments/cmsg/misc/direct_scheduling.patch similarity index 100% rename from experiments/direct_scheduling.patch rename to experiments/cmsg/misc/direct_scheduling.patch diff --git a/t0 b/experiments/cmsg/misc/t0 similarity index 100% rename from t0 rename to experiments/cmsg/misc/t0 diff --git a/t1 b/experiments/cmsg/misc/t1 similarity index 100% rename from t1 rename to experiments/cmsg/misc/t1 diff --git a/t2 b/experiments/cmsg/misc/t2 similarity index 100% rename from t2 rename to experiments/cmsg/misc/t2 diff --git a/t4 b/experiments/cmsg/misc/t4 similarity index 100% rename from t4 rename to experiments/cmsg/misc/t4 diff --git a/t5 b/experiments/cmsg/misc/t5 similarity index 100% rename from t5 rename to experiments/cmsg/misc/t5 diff --git a/t6 b/experiments/cmsg/misc/t6 similarity index 100% rename from t6 rename to experiments/cmsg/misc/t6 diff --git a/server/net.c b/experiments/cmsg/net.c similarity index 100% rename from server/net.c rename to experiments/cmsg/net.c diff --git a/server/net.h b/experiments/cmsg/net.h similarity index 100% rename from server/net.h rename to experiments/cmsg/net.h diff --git a/server/node.c b/experiments/cmsg/node.c similarity index 100% rename from server/node.c rename to experiments/cmsg/node.c diff --git a/server/node.h b/experiments/cmsg/node.h similarity index 100% rename from server/node.h rename to experiments/cmsg/node.h diff --git a/server/queue.c b/experiments/cmsg/queue.c similarity index 100% rename from server/queue.c rename to experiments/cmsg/queue.c diff --git a/server/queue.h b/experiments/cmsg/queue.h similarity index 100% rename from server/queue.h rename to experiments/cmsg/queue.h diff --git a/server/ref.h b/experiments/cmsg/ref.h similarity index 100% rename from server/ref.h rename to experiments/cmsg/ref.h diff --git a/server/relay.c b/experiments/cmsg/relay.c similarity index 100% rename from server/relay.c rename to experiments/cmsg/relay.c diff --git a/server/relay.h b/experiments/cmsg/relay.h similarity index 100% rename from server/relay.h rename to experiments/cmsg/relay.h diff --git a/server/sexp.c b/experiments/cmsg/sexp.c similarity index 100% rename from server/sexp.c rename to experiments/cmsg/sexp.c diff --git a/server/sexp.h b/experiments/cmsg/sexp.h similarity index 100% rename from server/sexp.h rename to experiments/cmsg/sexp.h diff --git a/server/sexpio.c b/experiments/cmsg/sexpio.c similarity index 100% rename from server/sexpio.c rename to experiments/cmsg/sexpio.c diff --git a/server/sexpio.h b/experiments/cmsg/sexpio.h similarity index 100% rename from server/sexpio.h rename to experiments/cmsg/sexpio.h diff --git a/server/subscription.c b/experiments/cmsg/subscription.c similarity index 100% rename from server/subscription.c rename to experiments/cmsg/subscription.c diff --git a/server/subscription.h b/experiments/cmsg/subscription.h similarity index 100% rename from server/subscription.h rename to experiments/cmsg/subscription.h diff --git a/server/util.c b/experiments/cmsg/util.c similarity index 100% rename from server/util.c rename to experiments/cmsg/util.c diff --git a/lisp/main.lisp b/experiments/lisp/main.lisp similarity index 100% rename from lisp/main.lisp rename to experiments/lisp/main.lisp diff --git a/lisp/network.lisp b/experiments/lisp/network.lisp similarity index 100% rename from lisp/network.lisp rename to experiments/lisp/network.lisp diff --git a/lisp/packages.lisp b/experiments/lisp/packages.lisp similarity index 100% rename from lisp/packages.lisp rename to experiments/lisp/packages.lisp diff --git a/lisp/sexp.lisp b/experiments/lisp/sexp.lisp similarity index 100% rename from lisp/sexp.lisp rename to experiments/lisp/sexp.lisp diff --git a/server/messages.json b/server/messages.json deleted file mode 100644 index 0397e1d..0000000 --- a/server/messages.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "selector": "create", - "args": ["classname", "arg", "reply-sink", "reply-name"] - }, - { - "selector": "create-ok", - "args": ["info"] - }, - { - "selector": "create-failed", - "args": ["reason"] - }, - { - "selector": "subscribed", - "args": ["source", "filter", "sink", "name"] - }, - { - "selector": "unsubscribed", - "args": ["source", "filter", "sink", "name"] - }, - { - "selector": "post", - "args": ["name", "body", "token"] - }, - { - "selector": "subscribe", - "args": ["filter", "sink", "name", "reply_sink", "reply_name"] - }, - { - "selector": "subscribe-ok", - "args": ["token"] - }, - { - "selector": "unsubscribe", - "args": ["token"] - }, - { - "selector": "error", - "args": ["message", "details"] - } -] diff --git a/server/test1.c b/server/test1.c deleted file mode 100644 index 0beab33..0000000 --- a/server/test1.c +++ /dev/null @@ -1,93 +0,0 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -int main(int argc, char *argv[]) { - int fd = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in s; - FILE *f; - struct timeval start_time; - long bytecount = -1; - long last_report_bytecount = 0; - - if (argc < 2) { - fprintf(stderr, "Usage: test1 \n"); - exit(1); - } - - { - struct hostent *h = gethostbyname(argv[1]); - if (h == NULL) { - fprintf(stderr, "serverhostname lookup: %d\n", h_errno); - exit(1); - } - s.sin_family = AF_INET; - s.sin_addr.s_addr = * (uint32_t *) h->h_addr_list[0]; - s.sin_port = htons(5671); - } - - if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; - - { - int i = 1; - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)); - } - - f = fdopen(fd, "a+"); - - fprintf(f, "(9:subscribe5:test10:0:5:test15:login)(4:post7:factory(6:create5:queue(2:q1)5:test11:k)0:)(4:post2:q1(9:subscribe0:5:test18:consumer5:test11:k)0:)\n"); - fflush(f); - -#define MESSAGESIZE 65 - - while (1) { - char buf[1024]; - size_t n = read(fd, buf, sizeof(buf)); - if (n == 0) break; - if (n >= 16) { - if (!memcmp(buf, "(4:post8:consumer", 16)) { - if (bytecount == -1) { - printf("Buffer at start: <<%.*s>>\n", (int) n, buf); - printf("Starting.\n"); - bytecount = 0; - gettimeofday(&start_time, NULL); - } - } - } - if (bytecount >= 0) { - bytecount += n; - if ((bytecount - last_report_bytecount) > (100000 * MESSAGESIZE)) { - struct timeval now; - double delta; - gettimeofday(&now, NULL); - delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; - printf("So far received %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", - bytecount, - delta, - bytecount / delta, - bytecount / (delta * MESSAGESIZE)); - fflush(stdout); - last_report_bytecount = bytecount; - } - } - } - - return 0; -} diff --git a/server/test1_latency.c b/server/test1_latency.c deleted file mode 100644 index fb7d2f9..0000000 --- a/server/test1_latency.c +++ /dev/null @@ -1,150 +0,0 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define EXPECTEDPREFIX "(4:post8:consumer8:" - -static size_t hunt_for_latencies_in(char *buf, size_t count) { - struct timeval now; - char *pos = buf; - char *sentinel = buf + count; - size_t msgsize = 0; - - gettimeofday(&now, NULL); - - while (1) { - char *openptr = memchr(pos, '(', sentinel - pos); - char *closeptr; - uint32_t s, us; - - if (openptr == NULL) break; - - closeptr = memchr(openptr + 1, ')', sentinel - (openptr + 1)); - if (closeptr == NULL) break; - - memcpy(&s, openptr + strlen(EXPECTEDPREFIX), sizeof(uint32_t)); - memcpy(&us, openptr + strlen(EXPECTEDPREFIX) + sizeof(uint32_t), sizeof(uint32_t)); - s = ntohl(s); - us = ntohl(us); - - if (s != 0 || us != 0) { - double delta = (now.tv_sec - s) * 1000000.0 + (now.tv_usec - us); - printf("Latency %g microseconds (%g milliseconds)\n", delta, delta / 1000.0); - } - - msgsize = closeptr + 1 - openptr; - - pos = closeptr + 1; - } - - return msgsize; -} - -int main(int argc, char *argv[]) { - int fd = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in s; - FILE *f; - struct timeval start_time; - long bytecount = -1; - size_t message_size = 0; - long last_report_bytecount = 0; - char idchar = '1'; - char *qclass = "queue"; - - if (argc < 2) { - fprintf(stderr, "Usage: test1 [ []]\n"); - exit(1); - } - - if (argc > 2) { - idchar = argv[2][0]; - } - printf("Idchar: '%c'\n", idchar); - - if (argc > 3) { - qclass = argv[3]; - } - printf("Qclass: %s\n", qclass); - - { - struct hostent *h = gethostbyname(argv[1]); - if (h == NULL) { - fprintf(stderr, "serverhostname lookup: %d\n", h_errno); - exit(1); - } - s.sin_family = AF_INET; - s.sin_addr.s_addr = * (uint32_t *) h->h_addr_list[0]; - s.sin_port = htons(5671); - } - - if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; - - { - int i = 1; - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)); - } - - f = fdopen(fd, "a+"); - - fprintf(f, "(9:subscribe5:test%c0:0:5:test%c5:login)(4:post7:factory(6:create%d:%s(2:q1)5:test%c1:k)0:)(4:post2:q1(9:subscribe0:5:test%c8:consumer5:test%c1:k)0:)\n", - idchar, idchar, (int) strlen(qclass), qclass, idchar, idchar, idchar); - fflush(f); - - while (1) { - char buf[1024]; - size_t n = read(fd, buf, sizeof(buf)); - if (n == 0) break; - if (n >= strlen(EXPECTEDPREFIX)) { - if (!memcmp(buf, EXPECTEDPREFIX, strlen(EXPECTEDPREFIX))) { - if (bytecount == -1) { - printf("Buffer at start: <<%.*s>>\n", (int) n, buf); - printf("Starting.\n"); - bytecount = 0; - gettimeofday(&start_time, NULL); - } - } - } - if (bytecount >= 0) { - size_t detected_msgsize = hunt_for_latencies_in(buf, n); - bytecount += n; - if (detected_msgsize != 0 && message_size == 0) { - message_size = detected_msgsize; - printf("message_size = %lu\n", message_size); - } - if (message_size != 0) { - if ((bytecount - last_report_bytecount) > (100000 * message_size)) { - struct timeval now; - double delta; - gettimeofday(&now, NULL); - delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; - printf("So far received %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", - bytecount, - delta, - bytecount / delta, - bytecount / (delta * message_size)); - fflush(stdout); - last_report_bytecount = bytecount; - } - } - } - } - - return 0; -} diff --git a/server/test3.c b/server/test3.c deleted file mode 100644 index 48a2c59..0000000 --- a/server/test3.c +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -int main(int argc, char *argv[]) { - int fd = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in s; - FILE *f; - struct timeval start_time; - long bytecount = 0; - char const *msg = "(4:post2:q1(4:post0:6:XXXXXX0:)0:)"; - size_t msglen = strlen(msg); - int i; - - if (argc < 2) { - fprintf(stderr, "Usage: test1 \n"); - exit(1); - } - - { - struct hostent *h = gethostbyname(argv[1]); - if (h == NULL) { - fprintf(stderr, "serverhostname lookup: %d\n", h_errno); - exit(1); - } - s.sin_family = AF_INET; - s.sin_addr.s_addr = * (uint32_t *) h->h_addr_list[0]; - s.sin_port = htons(5671); - } - - if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; - - { - int i = 1; - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)); - } - - f = fdopen(fd, "a+"); - - fprintf(f, "(9:subscribe5:test30:0:5:test35:login)"); - fflush(f); - - usleep(100000); - { - char buf[4096]; - size_t n = read(fd, buf, sizeof(buf)); - printf("Received: <<%.*s>>\n", (int) n, buf); - } - - gettimeofday(&start_time, NULL); - - for (i = 0; i < 10000000; i++) { - fwrite(msg, msglen, 1, f); - bytecount += msglen; - if ((bytecount % 100000) < msglen) { - struct timeval now; - double delta; - gettimeofday(&now, NULL); - delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; - printf("So far sent %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", - bytecount, - delta, - bytecount / delta, - bytecount / (delta * msglen)); - fflush(stdout); - } - } - - fprintf(f, "(11:unsubscribe5:test3)"); - fflush(f); - - fclose(f); - - return 0; -} diff --git a/server/test3_latency.c b/server/test3_latency.c deleted file mode 100644 index 8cb2fcb..0000000 --- a/server/test3_latency.c +++ /dev/null @@ -1,149 +0,0 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static size_t build_message(char *message, uint32_t s, uint32_t us) { - char const *msg_prefix = "(4:post2:q1(4:post0:8:"; - char const *msg_suffix = "0:)0:)"; - size_t prefix_len = strlen(msg_prefix); - size_t suffix_len = strlen(msg_suffix); - uint32_t v; - size_t total_len = 0; - - memcpy(message + total_len, msg_prefix, prefix_len); - total_len += prefix_len; - v = htonl(s); - memcpy(message + total_len, &v, sizeof(uint32_t)); - total_len += sizeof(uint32_t); - v = htonl(us); - memcpy(message + total_len, &v, sizeof(uint32_t)); - total_len += sizeof(uint32_t); - memcpy(message + total_len, msg_suffix, suffix_len); - total_len += suffix_len; - - /* - printf("%d<<", total_len); - fwrite(message, total_len, 1, stdout); - printf(">>\n"); - */ - return total_len; -} - -int main(int argc, char *argv[]) { - int fd = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in s; - FILE *f; - struct timeval start_time; - long bytecount = 0; - int i; - unsigned long hz_limit = 1000000; - unsigned long msgcount = 10000000; - - assert(sizeof(uint32_t) == 4); - - if (argc < 2) { - fprintf(stderr, "Usage: test1 [ []]\n"); - exit(1); - } - - if (argc > 2) { - hz_limit = strtoul(argv[2], NULL, 0); - } - printf("hz_limit = %lu\n", hz_limit); - - if (argc > 3) { - msgcount = strtoul(argv[3], NULL, 0); - } - printf("msgcount = %lu\n", msgcount); - - { - struct hostent *h = gethostbyname(argv[1]); - if (h == NULL) { - fprintf(stderr, "serverhostname lookup: %d\n", h_errno); - exit(1); - } - s.sin_family = AF_INET; - s.sin_addr.s_addr = * (uint32_t *) h->h_addr_list[0]; - s.sin_port = htons(5671); - } - - if (connect(fd, (struct sockaddr *) &s, sizeof(s)) < 0) return 1; - - { - int i = 1; - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(i)); - } - - f = fdopen(fd, "a+"); - - fprintf(f, "(9:subscribe5:test30:0:5:test35:login)"); - fflush(f); - - usleep(100000); - { - char buf[4096]; - size_t n = read(fd, buf, sizeof(buf)); - printf("Received: <<%.*s>>\n", (int) n, buf); - } - - gettimeofday(&start_time, NULL); - - for (i = 0; i < msgcount; i++) { - char message[1024]; - size_t msglen; - while (1) { - struct timeval now; - double delta; - gettimeofday(&now, NULL); - delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; - if (i / delta <= hz_limit) break; - fflush(f); - usleep(1000); - } - if ((i % (hz_limit / 4)) == 0) { - struct timeval now; - gettimeofday(&now, NULL); - msglen = build_message(message, now.tv_sec, now.tv_usec); - } else { - msglen = build_message(message, 0, 0); - } - fwrite(message, msglen, 1, f); - bytecount += msglen; - if ((bytecount % 100000) < msglen) { - struct timeval now; - double delta; - gettimeofday(&now, NULL); - delta = (now.tv_sec - start_time.tv_sec) + (now.tv_usec - start_time.tv_usec) / 1000000.0; - printf("So far sent %ld bytes in %g seconds = %g bytes/sec and %g msgs/sec\n", - bytecount, - delta, - bytecount / delta, - bytecount / (delta * msglen)); - fflush(stdout); - } - } - - fprintf(f, "(11:unsubscribe5:test3)"); - fflush(f); - - fclose(f); - - return 0; -} diff --git a/specgen.py b/specgen.py deleted file mode 100644 index 7e21969..0000000 --- a/specgen.py +++ /dev/null @@ -1,229 +0,0 @@ -import xml.dom.minidom - -def constify(s): - s = s.replace('-', '_') - s = s.replace(' ', '_') - s = s.upper() - return s - -def cify(s): - s = constify(s) - s = s.lower() - return s - -def camelify(s): - s = constify(s) - s = s.split('_') - s = [s[0].lower()] + [w.capitalize() for w in s[1:]] - s = ''.join(s) - return s - -ctypemap = { - 'shortstr': 'cmsg_bytes_t', - 'longstr': 'cmsg_bytes_t', - 'table': 'cmsg_bytes_t', # TODO fix - 'octet': 'uint8_t', - 'short': 'uint16_t', - 'long': 'uint32_t', - 'longlong': 'uint64_t', - 'bit': 'uint8_t', - 'timestamp': 'uint64_t', -} - -class Constant: - def __init__(self, e): - self.name = e.getAttribute('name') - self.value = e.getAttribute('value') - self.class_ = e.getAttribute('class') or None - - def getName(self): - if self.class_: - return 'CMSG_AMQP_ERROR_' + constify(self.name) - else: - return 'CMSG_AMQP_' + constify(self.name) - - def getValue(self): - return self.value - -class Field: - def __init__(self, e): - self.name = e.getAttribute('name') - self.domain = e.getAttribute('domain') or e.getAttribute('type') - # self.reserved = bool(e.getAttribute('reserved')) - self.type = resolveDomain(self.domain) - - def getName(self): - return cify(self.name) - - def ctype(self): - return ctypemap[str(self.type)] - -class Entity: - def __init__(self, parent, e): - self.parent = parent - self.name = e.getAttribute('name') - self.index = int(e.getAttribute('index')) - self.fields = [Field(ee) \ - for ee in e.getElementsByTagName('field') \ - if ee.parentNode is e] - - def getName(self): - if self.parent: - return self.parent.getName() + '_' + cify(self.name) - else: - return cify(self.name) - - def printStructDefExtras(self): - pass - - def printStructDef(self, suffix): - if self.fields: - print - print 'typedef struct cmsg_amqp_%s_%s_t_ {' % (self.getName(), suffix) - self.printStructDefExtras() - for f in self.fields: - print ' %s %s;' % (f.ctype(), f.getName()) - print '} cmsg_amqp_%s_%s_t;' % (self.getName(), suffix) - -class BitWriter: - def __init__(self): - self.bit_offset = 0 - - def flush(self): - if self.bit_offset: - print ' write_amqp_octet(bit_buffer);' - self.bit_offset = 0 - - def emit(self, valueExpr): - if self.bit_offset == 0: - print ' bit_buffer = 0;' - print ' if (%s) bit_buffer |= 0x%02x;' % (valueExpr, 1 << self.bit_offset) - self.bit_offset += 1 - if self.bit_offset == 8: - self.flush() - -class Method(Entity): - def __init__(self, parent, e): - Entity.__init__(self, parent, e) - self.has_content = bool(e.getAttribute('content')) - self.synchronous = bool(e.getAttribute('synchronous')) - self.responses = [ee.getAttribute('name') for ee in e.getElementsByTagName('response')] - - def methodId(self): - return self.parent.index << 16 | self.index - - def printParseClause(self): - bit_offset = 0 - print ' case 0x%08x: /* %s */ ' % (self.methodId(), self.getName()) - for f in self.fields: - if f.type == 'bit': - if bit_offset == 0: - print ' if (!parse_amqp_octet(&bit_buffer, &input, &offset))' - print ' return -CMSG_AMQP_ERROR_FRAME_ERROR;' - print ' output->body.%s.%s = (bit_buffer & 0x%02x) != 0;' % \ - (self.getName(), f.getName(), 1 << bit_offset) - bit_offset += 1 - if bit_offset == 8: bit_offset = 0 - else: - print ' if (!parse_amqp_%s(&output->body.%s.%s, &input, &offset))' % \ - (f.type, self.getName(), f.getName()) - print ' return -CMSG_AMQP_ERROR_FRAME_ERROR;' - print ' return 0;' - - def printWriteClause(self): - bw = BitWriter() - print ' case 0x%08x: /* %s */ ' % (self.methodId(), self.getName()) - for f in self.fields: - if f.type == 'bit': - bw.emit('output->body.%s.%s' % (self.getName(), f.getName())) - else: - bw.flush() - print ' write_amqp_%s(output->body.%s.%s);' % \ - (f.type, self.getName(), f.getName()) - bw.flush() - print ' break;' - -class Class(Entity): - def __init__(self, e): - Entity.__init__(self, None, e) - self.methods = [Method(self, ee) for ee in e.getElementsByTagName('method')] - - def printStructDefExtras(self): - print ' uint32_t _flags;' - -def resolveDomain(n): - while n in domainmap and domainmap[n] != n: - n = domainmap[n] - return n - -specxml = xml.dom.minidom.parse("amqp0-9-1.stripped.xml") -constants = [Constant(e) for e in specxml.getElementsByTagName('constant')] -domainmap = dict((e.getAttribute('name'), e.getAttribute('type')) \ - for e in specxml.getElementsByTagName('domain')) - -classes = [Class(e) for e in specxml.getElementsByTagName('class')] -classmap = dict((c.name, c) for c in classes) - -def header(): - print '/* TODO: put copyright etc */' - print '/* Generated from AMQP spec version %s.%s.%s */' % \ - (specxml.documentElement.getAttribute('major'), - specxml.documentElement.getAttribute('minor'), - specxml.documentElement.getAttribute('revision')) - - print - for c in constants: - print '#define %s %s' % (c.getName(), c.getValue()) - - for c in classes: - c.printStructDef('properties') - - for c in classes: - for m in c.methods: - m.printStructDef('method') - - print - print 'typedef struct cmsg_amqp_method_t_ {' - print ' uint32_t id;' - print ' union {' - for c in classes: - for m in c.methods: - if m.fields: - print ' cmsg_amqp_%s_method_t %s;' % (m.getName(), m.getName()) - print ' } body;' - print '} cmsg_amqp_method_t;' - - print - print 'int parse_amqp_method(' - print ' cmsg_bytes_t input,' - print ' cmsg_amqp_method_t *output) {' - print ' size_t offset = 0;' - print ' uint8_t bit_buffer = 0;' - print ' if (!parse_amqp_long(&output->id, &input, &offset))' - print ' return -CMSG_AMQP_ERROR_FRAME_ERROR;' - print ' switch (output->id) {' - for c in classes: - for m in c.methods: - m.printParseClause() - print ' default:' - print ' warn("Invalid AMQP method number 0x%%08x", output->id);' - print ' return -CMSG_AMQP_ERROR_NOT_IMPLEMENTED;' - print ' }' - print '}' - - print - print 'void write_amqp_method(' - print ' IOHandle *h,' - print ' cmsg_amqp_method_t *m) {' - print ' uint8_t bit_buffer = 0;' - print ' write_amqp_long(&m->id);' - print ' switch (m->id) {' - for c in classes: - for m in c.methods: - m.printWriteClause() - print ' default:' - print ' die("Invalid AMQP method number 0x%%08x", m->id);' - print ' }' - print '}' - -header() From 668f7f1de0f83f0c6041b1970ad78d19e44caa95 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 11 May 2012 09:01:53 -0400 Subject: [PATCH 122/122] License. --- experiments/cmsg/cmsg_private.h | 19 +++++++++++++++-- experiments/cmsg/codegen.py | 38 +++++++++++++++++++++++++++++++-- experiments/cmsg/dataq.c | 19 +++++++++++++++-- experiments/cmsg/dataq.h | 19 +++++++++++++++-- experiments/cmsg/direct.c | 19 +++++++++++++++-- experiments/cmsg/direct.h | 19 +++++++++++++++-- experiments/cmsg/fanout.c | 19 +++++++++++++++-- experiments/cmsg/fanout.h | 19 +++++++++++++++-- experiments/cmsg/harness.c | 19 +++++++++++++++-- experiments/cmsg/harness.h | 19 +++++++++++++++-- experiments/cmsg/hashtable.c | 21 +++++++++++++++--- experiments/cmsg/hashtable.h | 19 +++++++++++++++-- experiments/cmsg/main.c | 19 +++++++++++++++-- experiments/cmsg/meta.c | 19 +++++++++++++++-- experiments/cmsg/meta.h | 19 +++++++++++++++-- experiments/cmsg/net.c | 19 +++++++++++++++-- experiments/cmsg/net.h | 19 +++++++++++++++-- experiments/cmsg/node.c | 19 +++++++++++++++-- experiments/cmsg/node.h | 19 +++++++++++++++-- experiments/cmsg/queue.c | 19 +++++++++++++++-- experiments/cmsg/queue.h | 19 +++++++++++++++-- experiments/cmsg/ref.h | 19 +++++++++++++++-- experiments/cmsg/relay.c | 19 +++++++++++++++-- experiments/cmsg/relay.h | 19 +++++++++++++++-- experiments/cmsg/sexp.c | 19 +++++++++++++++-- experiments/cmsg/sexp.h | 19 +++++++++++++++-- experiments/cmsg/sexpio.c | 19 +++++++++++++++-- experiments/cmsg/sexpio.h | 19 +++++++++++++++-- experiments/cmsg/subscription.c | 19 +++++++++++++++-- experiments/cmsg/subscription.h | 19 +++++++++++++++-- experiments/cmsg/util.c | 19 +++++++++++++++-- 31 files changed, 547 insertions(+), 63 deletions(-) diff --git a/experiments/cmsg/cmsg_private.h b/experiments/cmsg/cmsg_private.h index 6cad748..b340dad 100644 --- a/experiments/cmsg/cmsg_private.h +++ b/experiments/cmsg/cmsg_private.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_private_h #define cmsg_private_h diff --git a/experiments/cmsg/codegen.py b/experiments/cmsg/codegen.py index f982fef..fa1077e 100644 --- a/experiments/cmsg/codegen.py +++ b/experiments/cmsg/codegen.py @@ -1,7 +1,41 @@ from __future__ import with_statement -# Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. -copyright_stmt = '/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */' +## Copyright 2010, 2011, 2012 Tony Garnock-Jones . +## +## This file is part of Hop. +## +## Hop is free software: you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## Hop is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +## License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Hop. If not, see . + +copyright_stmt = \ +'''/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ +''' import sys import json diff --git a/experiments/cmsg/dataq.c b/experiments/cmsg/dataq.c index e045e05..9c4eaa2 100644 --- a/experiments/cmsg/dataq.c +++ b/experiments/cmsg/dataq.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/dataq.h b/experiments/cmsg/dataq.h index 6bc1152..33304fa 100644 --- a/experiments/cmsg/dataq.h +++ b/experiments/cmsg/dataq.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_dataq_h #define cmsg_dataq_h diff --git a/experiments/cmsg/direct.c b/experiments/cmsg/direct.c index 5bd0b21..01dfe04 100644 --- a/experiments/cmsg/direct.c +++ b/experiments/cmsg/direct.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/direct.h b/experiments/cmsg/direct.h index 89be112..9c5a18c 100644 --- a/experiments/cmsg/direct.h +++ b/experiments/cmsg/direct.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_direct_h #define cmsg_direct_h diff --git a/experiments/cmsg/fanout.c b/experiments/cmsg/fanout.c index 47d3418..acc4468 100644 --- a/experiments/cmsg/fanout.c +++ b/experiments/cmsg/fanout.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/fanout.h b/experiments/cmsg/fanout.h index 1cdf52f..aa07c36 100644 --- a/experiments/cmsg/fanout.h +++ b/experiments/cmsg/fanout.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_fanout_h #define cmsg_fanout_h diff --git a/experiments/cmsg/harness.c b/experiments/cmsg/harness.c index 2cf39e5..d193ea5 100644 --- a/experiments/cmsg/harness.c +++ b/experiments/cmsg/harness.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/harness.h b/experiments/cmsg/harness.h index d4e612b..65eb14a 100644 --- a/experiments/cmsg/harness.h +++ b/experiments/cmsg/harness.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_harness_h #define cmsg_harness_h diff --git a/experiments/cmsg/hashtable.c b/experiments/cmsg/hashtable.c index d681d5e..566c4c2 100644 --- a/experiments/cmsg/hashtable.c +++ b/experiments/cmsg/hashtable.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include @@ -14,7 +29,7 @@ uint32_t hash_bytes(cmsg_bytes_t bytes) { /* http://en.wikipedia.org/wiki/Jenkins_hash_function */ uint32_t hash = 0; size_t i; - + for (i = 0; i < bytes.len; i++) { hash += bytes.bytes[i]; hash += (hash << 10); diff --git a/experiments/cmsg/hashtable.h b/experiments/cmsg/hashtable.h index 8fa26c9..dfcecfc 100644 --- a/experiments/cmsg/hashtable.h +++ b/experiments/cmsg/hashtable.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_hashtable_h #define cmsg_hashtable_h diff --git a/experiments/cmsg/main.c b/experiments/cmsg/main.c index 9a36f79..e3f0e41 100644 --- a/experiments/cmsg/main.c +++ b/experiments/cmsg/main.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/meta.c b/experiments/cmsg/meta.c index bdd6a32..5c8f443 100644 --- a/experiments/cmsg/meta.c +++ b/experiments/cmsg/meta.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/meta.h b/experiments/cmsg/meta.h index 06989e3..6d423df 100644 --- a/experiments/cmsg/meta.h +++ b/experiments/cmsg/meta.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_meta_h #define cmsg_meta_h diff --git a/experiments/cmsg/net.c b/experiments/cmsg/net.c index a5d848b..eb6c43c 100644 --- a/experiments/cmsg/net.c +++ b/experiments/cmsg/net.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/net.h b/experiments/cmsg/net.h index 15293a7..15332e0 100644 --- a/experiments/cmsg/net.h +++ b/experiments/cmsg/net.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_net_h #define cmsg_net_h diff --git a/experiments/cmsg/node.c b/experiments/cmsg/node.c index 427e95a..f315aa7 100644 --- a/experiments/cmsg/node.c +++ b/experiments/cmsg/node.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/node.h b/experiments/cmsg/node.h index 8df80b7..608f5b4 100644 --- a/experiments/cmsg/node.h +++ b/experiments/cmsg/node.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_node_h #define cmsg_node_h diff --git a/experiments/cmsg/queue.c b/experiments/cmsg/queue.c index 30305ae..9ec2fcb 100644 --- a/experiments/cmsg/queue.c +++ b/experiments/cmsg/queue.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/queue.h b/experiments/cmsg/queue.h index c0f1071..0ab7de1 100644 --- a/experiments/cmsg/queue.h +++ b/experiments/cmsg/queue.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_queue_h #define cmsg_queue_h diff --git a/experiments/cmsg/ref.h b/experiments/cmsg/ref.h index 503311a..e7e3d65 100644 --- a/experiments/cmsg/ref.h +++ b/experiments/cmsg/ref.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_ref_h #define cmsg_ref_h diff --git a/experiments/cmsg/relay.c b/experiments/cmsg/relay.c index 8092d79..ebf322f 100644 --- a/experiments/cmsg/relay.c +++ b/experiments/cmsg/relay.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/relay.h b/experiments/cmsg/relay.h index b5b69f8..4402a9d 100644 --- a/experiments/cmsg/relay.h +++ b/experiments/cmsg/relay.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_relay_h #define cmsg_relay_h diff --git a/experiments/cmsg/sexp.c b/experiments/cmsg/sexp.c index 9da78a4..cc5af20 100644 --- a/experiments/cmsg/sexp.c +++ b/experiments/cmsg/sexp.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/sexp.h b/experiments/cmsg/sexp.h index efacc97..2e9e233 100644 --- a/experiments/cmsg/sexp.h +++ b/experiments/cmsg/sexp.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_sexp_h #define cmsg_sexp_h diff --git a/experiments/cmsg/sexpio.c b/experiments/cmsg/sexpio.c index d34e810..88f4ea3 100644 --- a/experiments/cmsg/sexpio.c +++ b/experiments/cmsg/sexpio.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/sexpio.h b/experiments/cmsg/sexpio.h index 1baf567..b9a6dfc 100644 --- a/experiments/cmsg/sexpio.h +++ b/experiments/cmsg/sexpio.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_sexpio_h #define cmsg_sexpio_h diff --git a/experiments/cmsg/subscription.c b/experiments/cmsg/subscription.c index e9032fd..24636b1 100644 --- a/experiments/cmsg/subscription.c +++ b/experiments/cmsg/subscription.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include diff --git a/experiments/cmsg/subscription.h b/experiments/cmsg/subscription.h index 273d1e9..f48db3f 100644 --- a/experiments/cmsg/subscription.h +++ b/experiments/cmsg/subscription.h @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #ifndef cmsg_subscription_h #define cmsg_subscription_h diff --git a/experiments/cmsg/util.c b/experiments/cmsg/util.c index 1c6665f..9218ac1 100644 --- a/experiments/cmsg/util.c +++ b/experiments/cmsg/util.c @@ -1,5 +1,20 @@ -/* Copyright (C) 2010, 2011 Tony Garnock-Jones. All rights reserved. */ - +/* Copyright 2010, 2011, 2012 Tony Garnock-Jones . + * + * This file is part of Hop. + * + * Hop is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hop is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + * License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Hop. If not, see . + */ #include #include #include