|
|
/* -*- coding: utf-8 -*-
* ---------------------------------------------------------------------- * Copyright © 2013-2015, RedJack, LLC. * All rights reserved. * * Please see the COPYING file in this distribution for license details. * ---------------------------------------------------------------------- */
#if defined(__linux)
/* This is needed on Linux to get the pthread_setname_np function. */ #if !defined(_GNU_SOURCE)
#define _GNU_SOURCE 1
#endif
#endif
#include <assert.h>
#include <string.h>
#include <pthread.h>
#include "libcork/core/allocator.h"
#include "libcork/core/error.h"
#include "libcork/core/types.h"
#include "libcork/ds/buffer.h"
#include "libcork/threads/basics.h"
/*-----------------------------------------------------------------------
* Current thread */
static volatile cork_thread_id last_thread_descriptor = 0;
struct cork_thread { const char *name; cork_thread_id id; pthread_t thread_id; void *user_data; cork_free_f free_user_data; cork_run_f run; cork_error error_code; struct cork_buffer error_message; bool started; bool joined; };
struct cork_thread_descriptor { struct cork_thread *current_thread; cork_thread_id id; };
cork_tls(struct cork_thread_descriptor, cork_thread_descriptor);
struct cork_thread * cork_current_thread_get(void) { struct cork_thread_descriptor *desc = cork_thread_descriptor_get(); return desc->current_thread; }
cork_thread_id cork_current_thread_get_id(void) { struct cork_thread_descriptor *desc = cork_thread_descriptor_get(); if (CORK_UNLIKELY(desc->id == 0)) { if (desc->current_thread == NULL) { desc->id = cork_uint_atomic_add(&last_thread_descriptor, 1); } else { desc->id = desc->current_thread->id; } } return desc->id; }
/*-----------------------------------------------------------------------
* Threads */
struct cork_thread * cork_thread_new(const char *name, void *user_data, cork_free_f free_user_data, cork_run_f run) { struct cork_thread *self = cork_new(struct cork_thread); self->name = cork_strdup(name); self->id = cork_uint_atomic_add(&last_thread_descriptor, 1); self->user_data = user_data; self->free_user_data = free_user_data; self->run = run; self->error_code = CORK_ERROR_NONE; cork_buffer_init(&self->error_message); self->started = false; self->joined = false; return self; }
static void cork_thread_free_private(struct cork_thread *self) { cork_strfree(self->name); cork_free_user_data(self); cork_buffer_done(&self->error_message); cork_delete(struct cork_thread, self); }
void cork_thread_free(struct cork_thread *self) { assert(!self->started); cork_thread_free_private(self); }
const char * cork_thread_get_name(struct cork_thread *self) { return self->name; }
cork_thread_id cork_thread_get_id(struct cork_thread *self) { return self->id; }
#define PTHREADS_MAX_THREAD_NAME_LENGTH 16
static void * cork_thread_pthread_run(void *vself) { int rc; struct cork_thread *self = vself; struct cork_thread_descriptor *desc = cork_thread_descriptor_get(); #if defined(__APPLE__) && defined(__MACH__)
char thread_name[PTHREADS_MAX_THREAD_NAME_LENGTH]; #endif
desc->current_thread = self; desc->id = self->id; rc = self->run(self->user_data);
#if defined(__APPLE__) && defined(__MACH__)
/* On Mac OS X, we set the name of the current thread, not of an arbitrary
* thread of our choosing. */ strncpy(thread_name, self->name, PTHREADS_MAX_THREAD_NAME_LENGTH); thread_name[PTHREADS_MAX_THREAD_NAME_LENGTH - 1] = '\0'; pthread_setname_np(thread_name); #endif
/* If an error occurred in the body of the thread, save the error into the
* cork_thread object so that we can propagate that error when some calls * cork_thread_join. */ if (CORK_UNLIKELY(rc != 0)) { if (CORK_LIKELY(cork_error_occurred())) { self->error_code = cork_error_code(); cork_buffer_set_string(&self->error_message, cork_error_message()); } else { self->error_code = CORK_UNKNOWN_ERROR; cork_buffer_set_string(&self->error_message, "Unknown error"); } }
return NULL; }
int cork_thread_start(struct cork_thread *self) { int rc; pthread_t thread_id; #if defined(__linux) && ((__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 12))
char thread_name[PTHREADS_MAX_THREAD_NAME_LENGTH]; #endif
assert(!self->started);
rc = pthread_create(&thread_id, NULL, cork_thread_pthread_run, self); if (CORK_UNLIKELY(rc != 0)) { cork_system_error_set_explicit(rc); return -1; }
#if defined(__linux) && ((__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 12))
/* On Linux we choose which thread to name via an explicit thread ID.
* However, pthread_setname_np() isn't supported on versions of glibc * earlier than 2.12. So we need to check for a MINOR version of 12 or * higher. */ strncpy(thread_name, self->name, PTHREADS_MAX_THREAD_NAME_LENGTH); thread_name[PTHREADS_MAX_THREAD_NAME_LENGTH - 1] = '\0'; pthread_setname_np(thread_id, thread_name); #endif
self->thread_id = thread_id; self->started = true; return 0; }
int cork_thread_join(struct cork_thread *self) { int rc;
assert(self->started && !self->joined);
rc = pthread_join(self->thread_id, NULL); if (CORK_UNLIKELY(rc != 0)) { cork_system_error_set_explicit(rc); cork_thread_free_private(self); return -1; }
if (CORK_UNLIKELY(self->error_code != CORK_ERROR_NONE)) { cork_error_set_printf (self->error_code, "Error from thread %s: %s", self->name, (char *) self->error_message.buf); cork_thread_free_private(self); return -1; }
cork_thread_free_private(self); return 0; }
|