added a rawio interface to render_remote, this broke render_multi.c but it needs a rewrite now anyways
authorTero Marttila <terom@fixme.fi>
Sun, 08 Jun 2008 21:03:23 +0300
changeset 15 e7f0697814dc
parent 14 5a2246f5be78
child 16 50995bbe442a
added a rawio interface to render_remote, this broke render_multi.c but it needs a rewrite now anyways

committer: Tero Marttila <terom@fixme.fi>
Makefile
common.h
render_multi.c
render_png.c
render_remote.c
render_remote.h
web_main.c
--- a/Makefile	Sat Jun 07 05:18:06 2008 +0300
+++ b/Makefile	Sun Jun 08 21:03:23 2008 +0300
@@ -14,7 +14,7 @@
 render_png.o: render_png.c render_png.h
 render_raw.o: render_raw.c render_raw.h
 render_local.o: render_local.c render_local.h
-render_multi.o: render_multi.c render_multi.h
+render_multi.o: render_multi.c render_multi.h render_remote.h
 render_mandelbrot.o: render_mandelbrot.c render_mandelbrot.h
 
 file_main.o: file_main.c
--- a/common.h	Sat Jun 07 05:18:06 2008 +0300
+++ b/common.h	Sun Jun 08 21:03:23 2008 +0300
@@ -3,7 +3,8 @@
  * error handling
  */
 
-void _generic_err (const char *func, int perr, int do_exit, const char *fmt, ...);
+void _generic_err (const char *func, int perr, int do_exit, const char *fmt, ...)
+        __attribute__ ((format (printf, 4, 5)));
 
 // various kinds of ways to handle an error, 2**3 of them, *g*
 #define error(...)                  _generic_err(NULL, 0, 0, __VA_ARGS__)
--- a/render_multi.c	Sat Jun 07 05:18:06 2008 +0300
+++ b/render_multi.c	Sun Jun 08 21:03:23 2008 +0300
@@ -15,7 +15,7 @@
         int index;
 
         // the remote_render_ctx
-        struct remote_render_ctx *remote_render;
+        struct render_remote *remote_render;
         
         // a pointer to ourself
         struct render_multi *self;
@@ -283,8 +283,7 @@
             // XXX: this will result in recursion
             for (i = 0; i < ctx->self->remote_render_count; i++) {
                 if (ctx->self->remote_renders[i].remote_render) {
-                    if (render_remote_shake(ctx->self->remote_renders[i].remote_render))
-                        ERROR("render_remote_shake");
+                    render_remote_flush(ctx->self->remote_renders[i].remote_render);
                 } else {
                     // already disconnected, use in_buf instead
                     assert(ctx->self->remote_renders[i].in_buf);
@@ -338,14 +337,14 @@
     ctx->render_done = 1;
 
     // shake out the rest of the data as needed, as render_multi won't keep it anymore
-    render_remote_shake(ctx->remote_render);
+    render_remote_flush(ctx->remote_render);
    
     // invalidate this ctx's remote render
     ctx->remote_render = NULL;
 
     // is the data incomplete?
     if (!(ctx->col == ctx->slice_width || ctx->col == 0))
-        ERROR("incomplete data for slice %d: %d/%d bytes", ctx->index, ctx->col, ctx->slice_width);
+        ERROR("incomplete data for slice %d: %zu/%zu bytes", ctx->index, ctx->col, ctx->slice_width);
 
     // are all of them done?
     int i;
--- a/render_png.c	Sat Jun 07 05:18:06 2008 +0300
+++ b/render_png.c	Sun Jun 08 21:03:23 2008 +0300
@@ -104,7 +104,7 @@
     return 0;
 
 error:
-    _render_png_free(ctx);
+    // don't free it here, our user needs to do that
     return -1;
 }
 
@@ -139,6 +139,7 @@
     return 0;
 
 error:
+    free(ctx);
     return -1;
 }
 
--- a/render_remote.c	Sat Jun 07 05:18:06 2008 +0300
+++ b/render_remote.c	Sun Jun 08 21:03:23 2008 +0300
@@ -6,15 +6,20 @@
 #include <assert.h>
 
 #include <event2/event.h>
+#include <event2/event_struct.h>
 #include <event2/bufferevent.h>
 
 #include "render_internal.h"    // for render_cmd_build
 #include "render_remote.h"
 #include "common.h"
 
-struct remote_render_ctx {
-    struct event *ev_conn;
-    struct bufferevent *data_bev;
+struct render_remote {
+    // the socket fd
+    int sock;
+
+    // the event/bufferevent
+    struct event ev_connect, ev_data;
+    struct bufferevent *bev_data;
 
     #pragma pack(push)
     #pragma pack(1)
@@ -33,7 +38,13 @@
 
     #pragma pack(pop)
     
-    // has cb_done/cb_fail already been called?
+    // have we sent the command yet?
+    int cmd_sent;
+
+    // have we received the EOF?
+    int have_eof;
+    
+    // has cb_done/cb_fail/cancel already been called?
     int alive;
 
     void (*cb_sent)(void *arg);
@@ -44,83 +55,119 @@
     void *cb_arg;
 };
 
-static void _remote_render_free (struct remote_render_ctx *ctx) {
-    // free the data_bev
-    if (ctx->data_bev) {
-        bufferevent_free(ctx->data_bev);
-        ctx->data_bev = NULL;
-    }
+// internal prototypes
+static void _render_remote_free (struct render_remote *ctx);
+static void _render_remote_do_data (struct render_remote *ctx);
+static void _render_remote_do_done (struct render_remote *ctx);
+static void _render_remote_do_fail (struct render_remote *ctx);
+
+static void _render_remote_free (struct render_remote *ctx) {
+    // free the bev_data
+    if (ctx->bev_data)
+        bufferevent_free(ctx->bev_data);
     
-    // close the socket (ctx->ev_conn remains valid even after we're done with it...)
-    close(event_get_fd(ctx->ev_conn));
-
-    // and the event
-    event_free(ctx->ev_conn);
+    // and the events
+    if (event_pending(&ctx->ev_connect, EV_WRITE, NULL))
+        event_del(&ctx->ev_connect);
+    
+    if (event_pending(&ctx->ev_data, EV_READ, NULL))
+        event_del(&ctx->ev_data);
+        
+    // close the socket (ctx->ev_connect remains valid even after we're done with it...)
+    if (ctx->sock)
+        close(ctx->sock);
     
     // free the context structure
     free(ctx);
 }
 
-static void _remote_render_done (struct remote_render_ctx *ctx) {
+static void _render_remote_do_data (struct render_remote *ctx) {
+    // if there's data in the buffer, call cb_data
+    if (evbuffer_get_length(bufferevent_get_input(ctx->bev_data))) {
+        ctx->cb_data(EVBUFFER_INPUT(ctx->bev_data), ctx->cb_arg);
+    }
+    
+    // if we got EOF on the connection and there's no data left in the buffer, call cb_done
+    if (ctx->have_eof && evbuffer_get_length(bufferevent_get_input(ctx->bev_data)) == 0) {
+        _render_remote_do_done(ctx);
+    }
+}
+
+static void _render_remote_do_done (struct render_remote *ctx) {
     assert(ctx->alive);
     
     ctx->alive = 0;
 
     ctx->cb_done(ctx->cb_arg);
-
-    _remote_render_free(ctx);
 }
 
-static void _remote_render_fail (struct remote_render_ctx *ctx) {
+static void _render_remote_do_fail (struct render_remote *ctx) {
     assert(ctx->alive);
     
     ctx->alive = 0;
 
     ctx->cb_fail(ctx->cb_arg);
-    
-    _remote_render_free(ctx);
 }
 
 static void _remote_write (struct bufferevent *bev, void *arg) {
-    struct remote_render_ctx *ctx = arg;
-
-    // the write buffer was drained, so the render command was sent
-    ctx->cb_sent(ctx->cb_arg);
+    struct render_remote *ctx = arg;
     
-    // we don't care about EV_WRITE anymore
-    if (bufferevent_disable(ctx->data_bev, EV_WRITE))
-        ERROR("bufferevent_disable");
+    if (!ctx->cmd_sent) {
+        // write the render command
+        if (bufferevent_write(ctx->bev_data, &ctx->render_cmd, sizeof(ctx->render_cmd)))
+            ERROR("bufferevent_write");
 
-    // start receiving data
-    if (bufferevent_enable(ctx->data_bev, EV_READ))
-        ERROR("bufferevent_enable");
-    
+        // wait for it to be written out (we get called a second time)
+        ctx->cmd_sent = 1;
+
+    } else {
+        // the write buffer was drained, so the render command was write():n
+        assert(ctx->cb_sent);
+        ctx->cb_sent(ctx->cb_arg);
+        ctx->cb_sent = NULL;
+        
+        // we don't care about EV_WRITE anymore
+        if (bufferevent_disable(ctx->bev_data, EV_WRITE))
+            ERROR("bufferevent_disable");
+        
+        // are we buffered or raw?
+        if (ctx->cb_data) {
+            // start receiving data into our buffer
+            if (bufferevent_enable(ctx->bev_data, EV_READ))
+                ERROR("bufferevent_enable");
+
+        } else {
+            assert(event_initialized(&ctx->ev_data));
+            
+            // enable the raw read event
+            if (event_add(&ctx->ev_data, NULL))
+                ERROR("event_add");
+        }
+    }
+
     return;
+
 error:
-    _remote_render_fail(ctx);
+    _render_remote_do_fail(ctx);
 }
 
 static void _remote_read (struct bufferevent *bev, void *arg) {
-    struct remote_render_ctx *ctx = arg;
-    
-    // pass the bufferevent's input buffer to our callback - libevent doesn't provide any function to access this, but hopefully this works correctly
-    ctx->cb_data(EVBUFFER_INPUT(bev), ctx->cb_arg);
+    struct render_remote *ctx = arg;
+
+    _render_remote_do_data(ctx);
 }
 
 static void _remote_error (struct bufferevent *bev, short what, void *arg) {
-    struct remote_render_ctx *ctx = arg;
+    struct render_remote *ctx = arg;
 
     // OH NOES; WHAT DO WE DO!?
     
     if (what & EVBUFFER_EOF) {
         // great!
+        ctx->have_eof = 1;
         
-        // send any remaining-chunk data
-        if (EVBUFFER_LENGTH(EVBUFFER_INPUT(bev)) > 0)
-            ctx->cb_data(EVBUFFER_INPUT(bev), ctx->cb_arg);
-
-        // signal completion
-        _remote_render_done(ctx);
+        // flush any remaining data/call cb_send as needed
+        _render_remote_do_data(ctx);
 
         return;
 
@@ -137,32 +184,22 @@
     }
     
     // cb_fail + free
-    _remote_render_fail(ctx);
+    _render_remote_do_fail(ctx);
 }
 
-static void _remote_connected (int fd, short event, void *arg) {
-    struct remote_render_ctx *ctx = arg;
-
-    // set up the read/write bufferevent
-    if ((ctx->data_bev = bufferevent_new(fd, &_remote_read, &_remote_write, &_remote_error, ctx)) == NULL)
-        ERROR("bufferevent_new");
-
-    // write the render command
-    if (bufferevent_write(ctx->data_bev, &ctx->render_cmd, sizeof(ctx->render_cmd)))
-        ERROR("bufferevent_write");
+/*
+ * Do the initial IO-agnostic work to initialize the rendering process
+ */
+static struct render_remote *_render_remote_init (struct render *render, struct remote_node *remote_node) {
+    struct render_remote *ctx;
 
-    // wait for it to be written out
-    if (bufferevent_enable(ctx->data_bev, EV_WRITE))
-        ERROR("bufferevent_enable");
-    
-    return;
+    printf("remote_node render load: %d/%d\n", remote_node->current_load, remote_node->parallel_renders);
 
-error:
-    _remote_render_fail(ctx);
-}
-
-static void render_cmd_build (struct render *render, struct remote_render_ctx *ctx) {
-    // just copy over the render params to the render_cmd
+    // alloc the remote render ctx
+    if (!(ctx = calloc(1, sizeof(struct render_remote))))
+        ERROR("calloc");
+        
+    // copy the relevant stuff from the render_ctx
     ctx->render_cmd.mode = render->mode;
     ctx->render_cmd.img_w = htonl(render->img_w);
     ctx->render_cmd.img_h = htonl(render->img_h);
@@ -170,9 +207,77 @@
     ctx->render_cmd.y1 = render->y1;
     ctx->render_cmd.x2 = render->x2;
     ctx->render_cmd.y2 = render->y2;
+
+    // create the socket
+    if ((ctx->sock = socket(remote_node->addr.ss_family, SOCK_STREAM, 0)) < 0)
+        PERROR("socket");
+
+    // mark it as nonblocking
+    if (fcntl(ctx->sock, F_SETFL, O_NONBLOCK) == -1)
+        PERROR("fcntl");
+    
+    // initiate the connect
+    int err = connect(ctx->sock, (struct sockaddr *) &remote_node->addr, sizeof(remote_node->addr));
+
+    if (err != -1 || errno != EINPROGRESS)
+        PERROR("connect");
+
+    // return the raw ctx
+    return ctx;
+
+error:
+    _render_remote_free(ctx);
+    return NULL;
 }
 
-struct remote_render_ctx *render_remote (
+/*
+ * Raw unbuffered I/O mode
+ */
+struct render_remote *render_remote_rawio (
+        struct render *render,
+        struct remote_node *remote_node,
+        void (*cb_sent)(void *arg),
+        void (*cb_fail)(void *arg),
+        void (*cb_io_data)(evutil_socket_t, short, void*),
+        void *cb_arg
+) {
+    struct render_remote *ctx;
+    
+    // short-circuit error handling
+    if (!(ctx = _render_remote_init(render, remote_node)))
+        return NULL;
+
+    // store the provided callback functions
+    ctx->cb_sent = cb_sent;
+    ctx->cb_fail = cb_fail;
+    ctx->cb_arg = cb_arg;
+    
+    // set up the write bufferevent
+    if ((ctx->bev_data = bufferevent_new(ctx->sock, NULL, &_remote_write, &_remote_error, ctx)) == NULL)
+        ERROR("bufferevent_new");
+
+    // wait for it to connect
+    if (bufferevent_enable(ctx->bev_data, EV_WRITE))
+        ERROR("bufferevent_enable");
+
+    // set up the custom EV_READ callback
+    event_set(&ctx->ev_data, ctx->sock, EV_READ, cb_io_data, cb_arg);
+
+    // we are now alive
+    ctx->alive = 1;
+
+    // success
+    return ctx;
+
+error:
+    _render_remote_free(ctx);
+    return NULL;
+}   
+
+/*
+ * Old buffered mode
+ */
+struct render_remote *render_remote (
         struct render *render,
         struct remote_node *remote_node,
         void (*cb_sent)(void *arg),
@@ -181,14 +286,11 @@
         void (*cb_fail)(void *arg),
         void *cb_arg
 ) {    
-    struct remote_render_ctx *ctx;
-    int sock;
-
-    printf("remote_node render load: %d/%d\n", remote_node->current_load, remote_node->parallel_renders);
-
-    // alloc the remote render ctx
-    if (!(ctx = calloc(1, sizeof(struct remote_render_ctx))))
-        ERROR("calloc");
+    struct render_remote *ctx;
+    
+    // short-circuit error handling
+    if (!(ctx = _render_remote_init(render, remote_node)))
+        return NULL;
     
     // store the provided callback functions
     ctx->cb_sent = cb_sent;
@@ -196,31 +298,15 @@
     ctx->cb_done = cb_done;
     ctx->cb_fail = cb_fail;
     ctx->cb_arg = cb_arg;
-    
-    // copy the relevant stuff from the render_ctx
-    render_cmd_build(render, ctx);
-    
-    // create the socket
-    if ((sock = socket(remote_node->addr.ss_family, SOCK_STREAM, 0)) < 0)
-        PERROR("socket");
 
-    // mark it as nonblocking
-    if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1)
-        PERROR("fcntl");
-    
-    // initiate the connect
-    int err = connect(sock, (struct sockaddr *) &remote_node->addr, sizeof(remote_node->addr));
+    // set up the read/write bufferevent
+    if ((ctx->bev_data = bufferevent_new(ctx->sock, &_remote_read, &_remote_write, &_remote_error, ctx)) == NULL)
+        ERROR("bufferevent_new");
 
-    if (err != -1 || errno != EINPROGRESS)
-        PERROR("connect");
+    // wait for it to connect
+    if (bufferevent_enable(ctx->bev_data, EV_WRITE))
+        ERROR("bufferevent_enable");
 
-    // do the libevent dance
-    if (!(ctx->ev_conn = event_new(NULL, sock, EV_WRITE, &_remote_connected, ctx)))
-        ERROR("event_new");
-
-    if (event_add(ctx->ev_conn, NULL))
-        ERROR("event_add");
-    
     // we are now alive
     ctx->alive = 1;
 
@@ -228,41 +314,47 @@
     return ctx;
 
 error:
-    free(ctx);
-
-    if (sock > 0)
-        close(sock);
-
+    _render_remote_free(ctx);
     return NULL;
 }
 
-int render_remote_set_recv (struct remote_render_ctx *ctx, size_t recv_threshold, size_t unread_buffer) {
-    if (ctx->data_bev == NULL)
-        return -1;
+void render_remote_set_recv (struct render_remote *ctx, size_t recv_threshold, size_t unread_buffer) {
+    assert(ctx->bev_data);
 
-    bufferevent_setwatermark(ctx->data_bev, EV_READ, recv_threshold, recv_threshold + unread_buffer);
-
-    return 0;
+    bufferevent_setwatermark(ctx->bev_data, EV_READ, recv_threshold, recv_threshold + unread_buffer);
 }
 
-int render_remote_shake (struct remote_render_ctx *ctx) {
-    if (ctx->data_bev == NULL)
-        return -1;
+void render_remote_flush (struct render_remote *ctx) {
+    assert(ctx->bev_data);
 
-    ctx->cb_data(EVBUFFER_INPUT(ctx->data_bev), ctx->cb_arg);
-
-    return 0;
+    // call cb_data/cb_done as appropriate
+    _render_remote_do_data(ctx);
 }
 
-void render_remote_cancel (struct remote_render_ctx *ctx) {
+int render_remote_reschedule (struct render_remote *ctx) {
+    assert(event_initialized(&ctx->ev_data));
+    
+    // just reschedule it
+    if (event_add(&ctx->ev_data, NULL))
+        ERROR("event_add");
+
+    // ok
+    return 0;
+
+error:
+    return -1;
+}
+
+void render_remote_cancel (struct render_remote *ctx) {
     // we must be alive for this..
     assert(ctx->alive);
 
-    // if it's still just connecting, cancel that
-    if (event_pending(ctx->ev_conn, EV_WRITE, NULL))
-        event_del(ctx->ev_conn);
-    
-    // this takes care of the rest
-    _remote_render_free (ctx);
+    _render_remote_free(ctx);
 }
 
+void render_remote_free (struct render_remote *ctx) {
+    // XXX: add some sanity checks
+    
+    _render_remote_free(ctx);
+}
+
--- a/render_remote.h	Sat Jun 07 05:18:06 2008 +0300
+++ b/render_remote.h	Sun Jun 08 21:03:23 2008 +0300
@@ -8,27 +8,45 @@
 #include "remote_node.h"
 
 /*
- * Execute a render_t operation on a remote render_node
+ * Execute a render operation on a remote render_node
  */
 
 
 #define RENDER_PORT_NAME "6159"
 
-struct remote_render_ctx;
+struct render_remote;
 
 /*
- * Execute the given render operation on the render_node at the given remote address.
- *
- * The various callback functions must all be provided.
+ * Execute the given render operation on the render_node at the given remote
+ * address, and buffer the data stream.
  *
- * cb_sent will be invoked after the request has succesfully been written, and before cb_data is called.
- * cb_data is called whenever new data has been received. See also, render_remote_set_chunk_size
- * cb_done is called after our last call to cb_data (note: see render_remote_shake)
- * cb_fail is called when an error is encountered. This can (and will) happen at any time!
+ * The various callback functions must all be provided!
  *
- * Returns NULL on error, or a handle that can be used for cancel/etc on success
+ * cb_sent
+ *  Will be invoked once we have a pretty good guess that the render will
+ *  complete succesfully.
+ *
+ * cb_data
+ *  Will be invoked after cb_sent has been called whenever any new data has
+ *  been received into our buffer, once we've buffered all the data, or after
+ *  render_remote_flush.
+ *
+ * cb_done
+ *  The request was terminated succesfully and all data has been read out of
+ *  our buffer by cb_data
+ *
+ * cb_fail 
+ *  The request was terminated abnormally. This may happen at any time, except
+ *  after cb_done has been called.
+ *
+ * The returned struct render_remote can be used as a handle for the control
+ * functions, and it is the caller's responsibility to free it using
+ * render_remote_free after cb_done/cb_fail has been called, or alternatively,
+ * cancel it using render_remote_cancel.
+ *
+ * Returns NULL on error, or a handle on success
  */
-struct remote_render_ctx *render_remote (
+struct render_remote *render_remote (
         struct render *render,              // what to render
         struct remote_node *remote_node,    // what render node to use
         void (*cb_sent)(void *arg),
@@ -39,10 +57,29 @@
 );
 
 /*
- * Cancel the given request. No more callbacks will be called, buffered data is
- * discarded and the remote render process will cancel ASAP.
+ * Execute the given render operation on the render_node at the given remote
+ * address, and let the user handle the read I/O events.
+ *
+ * The various callback functions must all be provided!
+ *
+ * cb_sent
+ *  Will be called once the request has been sent.
+ *
+ * cb_fail
+ *  Will be called if sending the request fails.
+ *
+ * cb_io_data
+ *  Will be called once when there is data on the socket to receive. Must be
+ *  re-enabled using render_remote_again each time.
  */
-void render_remote_cancel (struct remote_render_ctx *ctx);
+struct render_remote *render_remote_rawio (
+        struct render *render,
+        struct remote_node *remote_node,
+        void (*cb_sent)(void *arg),
+        void (*cb_fail)(void *arg),
+        void (*cb_io_data)(evutil_socket_t, short, void*),
+        void *cb_arg
+);
 
 /*
  * Controls the behaviour of when cb_data is called, and how remote data is read in.
@@ -59,20 +96,40 @@
  * called any more.
  *
  * This means that there is a potential for deadlock if the buffer is full. You
- * MUST call render_remote_shake once you are ready to start consuming the
+ * MUST call render_remote_again once you are ready to start consuming the
  * buffer again.
  *
- * Only call this once cb_sent has fired
+ * Only valid for normal buffered renders.
  */
-int render_remote_set_recv (struct remote_render_ctx *ctx, size_t recv_threshold, size_t unread_buffer);
+void render_remote_set_recv (struct render_remote *ctx, size_t recv_threshold, size_t unread_buffer);
 
 /*
- * Call cb_data with the current set of buffered input data immediately,
- * regardless of whether or not the buffer contains any data, or any new
- * data has been received.
+ * For buffered I/O, call cb_data with the current set of buffered input data
+ * immediately, regardless of whether or not the buffer contains any data, or
+ * any new data has been received. Only call this once cb_sent has been invoked.
+ */
+void render_remote_flush (struct render_remote *ctx);
+
+/*
+ * For non-buffered I/O, reschedule the cb_io_data event to be called when more
+ * data is received.
+ */
+int render_remote_reschedule (struct render_remote *ctx);
+
+/*
+ * Cancel the given request. No more callbacks will be called, the ctx is freed
+ * and the remote render process will cancel ASAP.
  *
- * Only call this after cb_sent and before cb_done/cb_fail.
+ * For buffered I/O, the any buffered data is discarded.
+ *
+ * Note that you do not need to call render_remote_free after this.
  */
-int render_remote_shake (struct remote_render_ctx *ctx);
+void render_remote_cancel (struct render_remote *ctx);
+
+/*
+ * Free the render_remote ctx and its assoicated resources. Only valid after a
+ * cb_done/cb_fail, otherwise use render_remote_cancel.
+ */
+void render_remote_free (struct render_remote *ctx);
 
 #endif /* RENDER_REMOTE_H */
--- a/web_main.c	Sat Jun 07 05:18:06 2008 +0300
+++ b/web_main.c	Sun Jun 08 21:03:23 2008 +0300
@@ -8,6 +8,7 @@
 #include <arpa/inet.h>
 #include <signal.h>
 #include <unistd.h>
+#include <assert.h>
 
 #include <event2/event.h>
 #include <event2/event_compat.h>
@@ -43,7 +44,7 @@
 
     int headers_sent;
     
-    struct render_multi *render_info;
+    struct render_remote *render_info;
 
     size_t bytes_sent;
 
@@ -54,16 +55,15 @@
 static void _render_http_written (struct evhttp_request *request, void *arg);
 
 static void _render_cleanup (struct render_request *ctx) {
-    // clean up
+    if (ctx->render_info)
+        render_remote_free(ctx->render_info);
+
     free(ctx);
 }
 
 static void _render_sent (void *arg) {
     struct render_request *ctx = arg;
 
-    // set chunk size
-    render_multi_set_recv(ctx->render_info, MIN_CHUNK_SIZE, OVERFLOW_BUFFER);
-
     // send headers
     evhttp_add_header(evhttp_request_get_output_headers(ctx->http_request), "Content-Type", "image/png");
     evhttp_send_reply_start(ctx->http_request, HTTP_OK, "OK");
@@ -81,12 +81,7 @@
 
     size_t buf_size = EVBUFFER_LENGTH(buf);
 
-    // ignore empty buffers, a result of render_remote_shake()
-    if (buf_size == 0) {
-        printf("render [%p]: remote buffer is empty\n", ctx);
-
-        return;
-    }
+    assert(buf_size > 0);   // shouldn't happen anymore with the new render_remote
     
     // check if we are paused
     if (ctx->paused) {
@@ -111,14 +106,6 @@
 static void _render_done (void *arg) {
     struct render_request *ctx = arg;
 
-    // if we are paused, just shove the data into the http buffers, they might become larger than they should be, but it's easier to just move the data there and let render_remote complete
-    if (ctx->paused) {
-        printf("render [%p]: done: flushing the rest of our data\n", ctx);
-        ctx->paused = 0;
-
-        render_multi_shake(ctx->render_info);
-    }
-
     // send end
     evhttp_send_reply_end(ctx->http_request);
 
@@ -134,6 +121,7 @@
     if (ctx->headers_sent) {
         // just terminate the PNG stream where it is
         evhttp_send_reply_end(ctx->http_request);
+
     } else {
         evhttp_send_error(ctx->http_request, 500, "Internal Server Error");
     }
@@ -149,7 +137,8 @@
     printf("render [%p]: lost http connection\n", ctx);
 
     // cancel
-    render_multi_cancel(ctx->render_info);
+    render_remote_cancel(ctx->render_info);
+    ctx->render_info = NULL;
 
     _render_cleanup(ctx);
 }
@@ -162,20 +151,20 @@
     // unpause ourself
     ctx->paused = 0;
 
-    // shake out the buffers
-    render_multi_shake(ctx->render_info);
+    // any data waiting in the buffer?
+    render_remote_flush(ctx->render_info);
 }
 
 static void _http_render_execute (struct evhttp_request *request, u_int32_t img_w, u_int32_t img_h) {
     // render request context
-    struct render_request *req_ctx = calloc(1, sizeof(struct render_request));
+    struct render_request *ctx = calloc(1, sizeof(struct render_request));
 
-    if (!req_ctx)
+    if (!ctx)
         ERROR("calloc");
     
-    req_ctx->http_request = request;
-    req_ctx->headers_sent = 0;
-    req_ctx->bytes_sent = 0;
+    ctx->http_request = request;
+    ctx->headers_sent = 0;
+    ctx->bytes_sent = 0;
     
     // render context
     struct render render;
@@ -189,36 +178,36 @@
     if (render_region_full(&render))
         ERROR("render_region_full");
 
-/*
     // pick a render_node
     struct remote_node *node_info;
 
     if ((node_info = remote_pool_get(&remote_pool)) == NULL)
         ERROR("remote_pool_get");
-*/
 
     // initiate the remote render operation
-    if ((req_ctx->render_info = render_multi(&render, &remote_pool,
+    if ((ctx->render_info = render_remote(&render, node_info,
         &_render_sent,
         &_render_data,
         &_render_done,
         &_render_fail,
-        req_ctx
+        ctx
     )) == NULL)
         ERROR("render_multi");
+    
+    // set chunk size
+    render_remote_set_recv(ctx->render_info, MIN_CHUNK_SIZE, OVERFLOW_BUFFER);
 
     // set close cb
-    evhttp_set_reply_abortcb(request, &_render_http_lost, req_ctx);
+    evhttp_set_reply_abortcb(request, &_render_http_lost, ctx);
     
-    printf("render [%p]: started\n", req_ctx);
+    printf("render [%p]: started\n", ctx);
     
     return;
 
 error:
     evhttp_send_error(request, 500, "Internal Server Error");
 
-    free(req_ctx);
-        
+    _render_cleanup(ctx);
 }
 
 /*