You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

223 lines
5.9 KiB

/* -*- 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;
}