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