#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);
}