render_thread.c
author Tero Marttila <terom@fixme.fi>
Wed, 27 Aug 2008 21:30:32 +0300
changeset 41 540737bf6bac
parent 24 8307d28329ae
permissions -rw-r--r--
sending requests, and partial support for receiving -- incomplete, not tested
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>

#include <pthread.h>
#include <event2/event.h>

#include "common.h"
#include "render_thread.h"
#include "render_thread_struct.h"
#include "render_local.h"

static void *_render_thread_func (void *arg) {
    struct render_thread *ctx = arg;
    
    // measure how long it takes
    double duration;
    
    struct render_local local_ctx;
    
    // initialize it...
    if (!(ctx->err = render_local_init(&local_ctx, &ctx->render_info))) {
        // setup the cancel exit handlers
        pthread_cleanup_push( (void (*)(void *)) render_local_deinit, &local_ctx);

        // render it...
        ctx->err = render_local_run(&local_ctx, &duration);
        
        if (!ctx->err) {
#if INFO_ENABLED
            u_int32_t img_w, img_h;

            render_get_size(&ctx->render_info, &img_w, &img_h);

            // report the duration
            INFO("rendered [%ux%u] in %f seconds", img_w, img_h, duration);
#endif
        }
        
        // cleanup
        pthread_cleanup_pop(1);
    }

    // notify completion, writeall()
    ssize_t ret;
    char *buf = (void *) &ctx;
    size_t len = sizeof(ctx);

    do {
        ret = write(ctx->notify_fd, buf, len);

        if (ret > 0) {
            buf += ret;
            len -= ret;
        }
    } while ((ret == -1 && errno == EINTR) || len > 0);
    
    if (ret == -1)
        PERROR("write");
    
    // done...
    return NULL;

error:
    // if notifying of completion failed...
    return ctx;
}

static void _render_thread_done (evutil_socket_t fd, short what, void *arg) {
    struct render_thread *ctx = arg;
    void *thread_return;

    // join the thread and check the return value
    if (pthread_join(ctx->thread_id, &thread_return))
        PWARNING("pthread_join");
    else if (thread_return == PTHREAD_CANCELED)
        PWARNING("PTHREAD_CANCELED");
    else if (thread_return != NULL)
        PWARNING("thread_return != NULL");

    // make a lazy effort to read the contents of the pipe
    struct render_thread *ctx2;
    char *buf = (void *) &ctx2;
    size_t len = sizeof(ctx);

    ssize_t ret;
    
    if ((ret = read(fd, buf, len)) == -1)
        PWARNING("read");

    else if (ret != len)
        WARNING("short read");

    else if (ctx2 != ctx)
        FATAL("wrong ctx: %p <> %p", ctx, ctx2);
    
    // close the pipe
    if (close(fd))
        PWARNING("close(pipe-read)");

    if (close(ctx->notify_fd))
        PWARNING("close(pipe-write)");

    // mark it as done
    ctx->is_active = 0;

    // call our callback
    ctx->cb_func(ctx, ctx->err, ctx->cb_arg);
}

int render_thread_init (struct render_thread *ctx, struct render *render_info, render_thread_done_cb cb_func, void *cb_arg) {
    // we need to copy over the render info, as it will probably be invalidated before the thread finishes
    memcpy(&ctx->render_info, render_info, sizeof(ctx->render_info));
    
    // the cb stuff
    ctx->cb_func = cb_func;
    ctx->cb_arg = cb_arg;

    // the notify pipe
    int pipefds[2];
    
    if (pipe(pipefds))
        PERROR("pipe");
    
    // the write end...
    ctx->notify_fd = pipefds[1];

    // the read end...
    event_set(&ctx->ev, pipefds[0], EV_READ, &_render_thread_done, ctx);

    if (event_add(&ctx->ev, NULL))
        PERROR("event_add");

    // spawn the render thread
    if (pthread_create(&ctx->thread_id, NULL, &_render_thread_func, ctx))
        PERROR("pthread_create(manager_func)");
    
    // mark it as active
    ctx->is_active = 1;
    
    return 0;

error:
    render_thread_deinit(ctx);
    return -1;
}

void render_thread_cancel (struct render_thread *ctx) {
    assert(ctx->is_active);
  
    // we don't care about joining the thread, detach
    // XXX if already detached, it won't get canceled here
    if (pthread_detach(ctx->thread_id))
        PWARNING("pthread_detach");

    // cancel it
    else if (pthread_cancel(ctx->thread_id))
        PWARNING("pthread_cancel");
    
    // XXX: should we actually join it before continuing?

    // slam the pipe shut in front of its face
    if (close(event_get_fd(&ctx->ev)))
        PWARNING("close");

    if (close(ctx->notify_fd))
        PWARNING("close");

    // pipe's closed, and don't call _render_thread_func
    event_del(&ctx->ev);
    
    // we are now ready for deinit
    ctx->is_active = 0;
}

void render_thread_deinit (struct render_thread *ctx) {
    assert(!ctx->is_active);

    // nothing to do
}

struct render_thread *render_thread_alloc (struct render *render_info, render_thread_done_cb cb_func, void *cb_arg) {
    struct render_thread *ctx = NULL;

    if (!(ctx = calloc(1, sizeof(*ctx))))
        ERROR("calloc");
    
    // flag it for free()ing
    ctx->owned_by_me = 1;
    
    // init with silent fall-through
    if (render_thread_init(ctx, render_info, cb_func, cb_arg))
        goto error;
    
    // success
    return ctx;

error:
    // XXX: do other modules do this?
    free(ctx);

    return NULL;
}

void render_thread_free (struct render_thread *ctx) {
    render_thread_deinit(ctx);

    if (ctx->owned_by_me)
        free(ctx);
}