Compare commits
3 Commits
9df451de61
...
4eed5f9af1
Author | SHA1 | Date |
---|---|---|
Vftdan | 4eed5f9af1 | |
Vftdan | 44cf381ce1 | |
Vftdan | 1fc696dc51 |
|
@ -0,0 +1,4 @@
|
||||||
|
/build
|
||||||
|
/server
|
||||||
|
compile_flags.txt
|
||||||
|
tags
|
5
Makefile
5
Makefile
|
@ -2,7 +2,10 @@ include common.mk
|
||||||
|
|
||||||
all: server
|
all: server
|
||||||
|
|
||||||
server: $(BUILD_DIR)/server/main.o $(BUILD_DIR)/common/util/hash_table.o
|
$(BUILD_DIR)/common/util/thread.o: $(BUILD_DIR)/common/util/thread.posix.o
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
|
server: $(BUILD_DIR)/server/main.o $(BUILD_DIR)/common/util/hash_table.o $(BUILD_DIR)/common/util/byte_stream.o $(BUILD_DIR)/common/util/thread.o
|
||||||
$(COMPILE_EXE)
|
$(COMPILE_EXE)
|
||||||
|
|
||||||
run: server
|
run: server
|
||||||
|
|
|
@ -31,6 +31,7 @@ ifneq ($(strip $(DEPS)),)
|
||||||
CPPFLAGS += $(shell $(PKGCONFIG) --cflags $(DEPS))
|
CPPFLAGS += $(shell $(PKGCONFIG) --cflags $(DEPS))
|
||||||
LDLIBS += $(shell $(PKGCONFIG) --libs $(DEPS))
|
LDLIBS += $(shell $(PKGCONFIG) --libs $(DEPS))
|
||||||
endif
|
endif
|
||||||
|
LDLIBS += -lpthread
|
||||||
INCPATH += -iquote $(SRC_DIR)
|
INCPATH += -iquote $(SRC_DIR)
|
||||||
ENSURE_DIR = mkdir -p $(shell dirname "$@")
|
ENSURE_DIR = mkdir -p $(shell dirname "$@")
|
||||||
COMPILE_EXE = $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
COMPILE_EXE = $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
#ifndef COMMON_UTIL_BYTE_SERDES_H_
|
||||||
|
#define COMMON_UTIL_BYTE_SERDES_H_
|
||||||
|
|
||||||
|
#include "common/util/byte_stream.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
HEADER_FN void
|
||||||
|
byteser_bytes_lcrop(AbstractOutputByteStream *stream, ByteSlice bytes, size_t size, uint8_t pad_value)
|
||||||
|
{
|
||||||
|
ByteSlice pad_slc = VAR_BYTE_SLICE_CONST(pad_value);
|
||||||
|
while (size > bytes.length) {
|
||||||
|
byte_stream_write(stream, pad_slc);
|
||||||
|
--size;
|
||||||
|
}
|
||||||
|
byte_slice_advance_inplace(&bytes, bytes.length - size);
|
||||||
|
if (bytes.data && bytes.length) {
|
||||||
|
byte_stream_write(stream, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN void
|
||||||
|
byteser_net_integral_signed(AbstractOutputByteStream *stream, intmax_t value, size_t size)
|
||||||
|
{
|
||||||
|
int8_t buf[sizeof(value)];
|
||||||
|
for (size_t i = 0; i < sizeof(buf); ++i) {
|
||||||
|
buf[size - i - 1] = value & 0xFF;
|
||||||
|
value >>= 8;
|
||||||
|
}
|
||||||
|
byteser_bytes_lcrop(stream, VAR_BYTE_SLICE_CONST(buf), size, value < 0 ? -1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN void
|
||||||
|
byteser_net_integral_unsigned(AbstractOutputByteStream *stream, uintmax_t value, size_t size)
|
||||||
|
{
|
||||||
|
uint8_t buf[sizeof(value)];
|
||||||
|
for (size_t i = 0; i < sizeof(buf); ++i) {
|
||||||
|
buf[size - i - 1] = value & 0xFF;
|
||||||
|
value >>= 8;
|
||||||
|
}
|
||||||
|
byteser_bytes_lcrop(stream, VAR_BYTE_SLICE_CONST(buf), size, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO finish deserialization
|
||||||
|
HEADER_FN bool
|
||||||
|
bytedeser_net_integral_signed(AbstractInputByteStream *stream, intmax_t *value_ptr, size_t size)
|
||||||
|
{
|
||||||
|
*value_ptr = 0;
|
||||||
|
int8_t buf[sizeof(*value_ptr)] = {0,};
|
||||||
|
int8_t dummy;
|
||||||
|
MutByteSlice dummy_slc = VAR_BYTE_SLICE_MUT(dummy);
|
||||||
|
while (size > sizeof(buf)) {
|
||||||
|
byte_stream_read_to(stream, dummy_slc);
|
||||||
|
--size;
|
||||||
|
}
|
||||||
|
MutByteSlice buf_slc = VAR_BYTE_SLICE_MUT(buf);
|
||||||
|
byte_slice_advance_inplace(&buf_slc.as_ByteSlice, buf_slc.length - size);
|
||||||
|
if (!buf_slc.data || !buf_slc.length) {
|
||||||
|
return true; // Successfully read 0 bytes
|
||||||
|
}
|
||||||
|
if (byte_stream_is_end(stream)) {
|
||||||
|
return false; // Failed to read bytes
|
||||||
|
}
|
||||||
|
if (byte_stream_read_to(stream, buf_slc) != buf_slc.length) {
|
||||||
|
return false; // Failed to read enough bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(buf_slc.data - (uint8_t*) buf < (ssize_t) sizeof(buf));
|
||||||
|
int8_t sign = *(int8_t*) buf_slc.data < 0 ? -1 : 0;
|
||||||
|
for (int8_t *ptr = buf; (uint8_t*) ptr != buf_slc.data; ++ptr) {
|
||||||
|
*ptr = sign;
|
||||||
|
}
|
||||||
|
|
||||||
|
intmax_t value = 0;
|
||||||
|
for (size_t i = 0; i < sizeof(buf); ++i) {
|
||||||
|
value <<= 8;
|
||||||
|
value |= 0xFF & (intmax_t) buf[i];
|
||||||
|
}
|
||||||
|
*value_ptr = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* end of include guard: COMMON_UTIL_BYTE_SERDES_H_ */
|
|
@ -0,0 +1,60 @@
|
||||||
|
#ifndef COMMON_UTIL_BYTE_SLICE_H_
|
||||||
|
#define COMMON_UTIL_BYTE_SLICE_H_
|
||||||
|
|
||||||
|
#include "common/defs.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const uint8_t *data;
|
||||||
|
size_t length;
|
||||||
|
} ByteSlice;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint8_t *data;
|
||||||
|
size_t length;
|
||||||
|
};
|
||||||
|
ByteSlice as_ByteSlice;
|
||||||
|
} MutByteSlice;
|
||||||
|
|
||||||
|
#define EMPTY_BYTE_SLICE (ByteSlice) { .data = NULL, .length = 0 }
|
||||||
|
#define EMPTY_MUT_BYTE_SLICE (MutByteSlice) { .data = NULL, .length = 0 }
|
||||||
|
#define VAR_BYTE_SLICE_CONST(x) (ByteSlice) { .data = (const void*) &(x), .length = sizeof(x) }
|
||||||
|
#define VAR_BYTE_SLICE_MUT(x) (MutByteSlice) { .data = (void*) &(x), .length = sizeof(x) }
|
||||||
|
|
||||||
|
HEADER_FN void
|
||||||
|
byte_slice_advance_inplace(ByteSlice *self, size_t amount)
|
||||||
|
{
|
||||||
|
if (amount >= self->length) {
|
||||||
|
*self = EMPTY_BYTE_SLICE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self->length -= amount;
|
||||||
|
self->data += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN MutByteSlice
|
||||||
|
byte_slice_alloc(size_t length)
|
||||||
|
{
|
||||||
|
if (!length) {
|
||||||
|
return EMPTY_MUT_BYTE_SLICE;
|
||||||
|
}
|
||||||
|
uint8_t *data = T_ALLOC(length, uint8_t);
|
||||||
|
if (!data) {
|
||||||
|
return EMPTY_MUT_BYTE_SLICE;
|
||||||
|
}
|
||||||
|
return (MutByteSlice) {
|
||||||
|
.data = data,
|
||||||
|
.length = length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN MutByteSlice
|
||||||
|
byte_slice_free(MutByteSlice slice)
|
||||||
|
{
|
||||||
|
if (slice.data) {
|
||||||
|
free(slice.data);
|
||||||
|
}
|
||||||
|
return EMPTY_MUT_BYTE_SLICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* end of include guard: COMMON_UTIL_BYTE_SLICE_H_ */
|
|
@ -0,0 +1,81 @@
|
||||||
|
#include "byte_stream.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
AbstractInputByteStream as_AbstractInputByteStream;
|
||||||
|
FILE *file;
|
||||||
|
} FileInputByteStream;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
AbstractOutputByteStream as_AbstractOutputByteStream;
|
||||||
|
FILE *file;
|
||||||
|
} FileOutputByteStream;
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
file_steam_read(AbstractInputByteStream *self, MutByteSlice buf)
|
||||||
|
{
|
||||||
|
return fread(buf.data, 1, buf.length, DOWNCAST(FileInputByteStream, AbstractInputByteStream, self)->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
file_steam_is_end(AbstractInputByteStream *self)
|
||||||
|
{
|
||||||
|
FILE *file = DOWNCAST(FileInputByteStream, AbstractInputByteStream, self)->file;
|
||||||
|
return (feof(file) || ferror(file)) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
file_steam_write(AbstractOutputByteStream *self, ByteSlice bytes)
|
||||||
|
{
|
||||||
|
fwrite(bytes.data, 1, bytes.length, DOWNCAST(FileOutputByteStream, AbstractOutputByteStream, self)->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
file_input_stream_destroy(AbstractInputByteStream *self)
|
||||||
|
{
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
file_output_stream_destroy(AbstractOutputByteStream *self)
|
||||||
|
{
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractInputByteStream *file_as_input_byte_stream(FILE *file)
|
||||||
|
{
|
||||||
|
if (!file) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
FileInputByteStream *self = T_ALLOC(1, FileInputByteStream);
|
||||||
|
if (self == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*self = (FileInputByteStream) {
|
||||||
|
.as_AbstractInputByteStream = {
|
||||||
|
.read = &file_steam_read,
|
||||||
|
.is_end = &file_steam_is_end,
|
||||||
|
.destroy = &file_input_stream_destroy,
|
||||||
|
},
|
||||||
|
.file = file,
|
||||||
|
};
|
||||||
|
return &self->as_AbstractInputByteStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractOutputByteStream *file_as_output_byte_stream(FILE *file)
|
||||||
|
{
|
||||||
|
if (!file) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
FileOutputByteStream *self = T_ALLOC(1, FileOutputByteStream);
|
||||||
|
if (self == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*self = (FileOutputByteStream) {
|
||||||
|
.as_AbstractOutputByteStream = {
|
||||||
|
.write = &file_steam_write,
|
||||||
|
.destroy = &file_output_stream_destroy,
|
||||||
|
},
|
||||||
|
.file = file,
|
||||||
|
};
|
||||||
|
return &self->as_AbstractOutputByteStream;
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
#ifndef COMMON_UTIL_BYTE_STREAM_H_
|
||||||
|
#define COMMON_UTIL_BYTE_STREAM_H_
|
||||||
|
|
||||||
|
#include "common/util/byte_slice.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef struct abstract_input_byte_stream AbstractInputByteStream;
|
||||||
|
typedef struct abstract_output_byte_stream AbstractOutputByteStream;
|
||||||
|
|
||||||
|
struct abstract_input_byte_stream {
|
||||||
|
size_t (*read)(AbstractInputByteStream *self, MutByteSlice buf);
|
||||||
|
bool (*is_end)(AbstractInputByteStream *self);
|
||||||
|
void (*destroy)(AbstractInputByteStream *self);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct abstract_output_byte_stream {
|
||||||
|
void (*write)(AbstractOutputByteStream *self, ByteSlice bytes);
|
||||||
|
void (*destroy)(AbstractOutputByteStream *self);
|
||||||
|
};
|
||||||
|
|
||||||
|
// file is borrowed, not moved
|
||||||
|
AbstractInputByteStream *file_as_input_byte_stream(FILE *file);
|
||||||
|
AbstractOutputByteStream *file_as_output_byte_stream(FILE *file);
|
||||||
|
|
||||||
|
HEADER_FN size_t
|
||||||
|
byte_stream_read_to(AbstractInputByteStream *stream, MutByteSlice buf)
|
||||||
|
{
|
||||||
|
if (!stream->read) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return stream->read(stream, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN MutByteSlice
|
||||||
|
byte_stream_read_alloc(AbstractInputByteStream *stream, size_t amount)
|
||||||
|
{
|
||||||
|
if (!stream->read) {
|
||||||
|
return EMPTY_MUT_BYTE_SLICE;
|
||||||
|
}
|
||||||
|
MutByteSlice buf = byte_slice_alloc(amount);
|
||||||
|
if (!buf.data) {
|
||||||
|
return EMPTY_MUT_BYTE_SLICE;
|
||||||
|
}
|
||||||
|
size_t actual = stream->read(stream, buf);
|
||||||
|
if (actual < buf.length) {
|
||||||
|
buf.length = actual;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN bool
|
||||||
|
byte_stream_is_end(AbstractInputByteStream *stream)
|
||||||
|
{
|
||||||
|
if (!stream->read) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!stream->is_end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return stream->is_end(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN void
|
||||||
|
byte_stream_write(AbstractOutputByteStream *stream, ByteSlice bytes)
|
||||||
|
{
|
||||||
|
if (!stream->write) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return stream->write(stream, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN void
|
||||||
|
input_byte_stream_destroy(AbstractInputByteStream *stream)
|
||||||
|
{
|
||||||
|
if (!stream->destroy) {
|
||||||
|
free(stream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stream->destroy(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FN void
|
||||||
|
output_byte_stream_destroy(AbstractOutputByteStream *stream)
|
||||||
|
{
|
||||||
|
if (!stream->destroy) {
|
||||||
|
free(stream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stream->destroy(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* end of include guard: COMMON_UTIL_BYTE_STREAM_H_ */
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef COMMON_UTIL_CLOSURE_H_
|
||||||
|
#define COMMON_UTIL_CLOSURE_H_
|
||||||
|
|
||||||
|
#include "common/defs.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *opaque;
|
||||||
|
} ClosureEnvironment;
|
||||||
|
|
||||||
|
#define CLOSURE_CALLBACK_FN(TRet, ...) typeof(TRet (ClosureEnvironment closure ,## __VA_ARGS__))
|
||||||
|
#define CLOSURE_T(...) struct { CLOSURE_CALLBACK_FN(__VA_ARGS__) *callback; ClosureEnvironment env; }
|
||||||
|
|
||||||
|
#endif /* end of include guard: COMMON_UTIL_CLOSURE_H_ */
|
|
@ -0,0 +1,44 @@
|
||||||
|
#ifndef COMMON_UTIL_THREAD_H_
|
||||||
|
#define COMMON_UTIL_THREAD_H_
|
||||||
|
|
||||||
|
#include "common/defs.h"
|
||||||
|
#include "common/util/closure.h"
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
uintptr_t handle;
|
||||||
|
void *opaque;
|
||||||
|
} Thread;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
uintptr_t value;
|
||||||
|
void *pointer;
|
||||||
|
} ThreadResult;
|
||||||
|
|
||||||
|
#define THREAD_NONE (Thread) { .handle = 0, }
|
||||||
|
|
||||||
|
typedef CLOSURE_T(ThreadResult) ThreadEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts new thread
|
||||||
|
* @param entry main thread function
|
||||||
|
* @param error_result join result in case of invalid entry
|
||||||
|
* @result new thread
|
||||||
|
*/
|
||||||
|
Thread thread_spawn(ThreadEntry entry, ThreadResult error_result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees memory used to store thread handle (if allocated by the implementation)
|
||||||
|
* Usage of th is invalid after this
|
||||||
|
* @return THREAD_NONE
|
||||||
|
*/
|
||||||
|
Thread thread_delete_handle(Thread th);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for a thread to exit
|
||||||
|
* @param th the thread to join
|
||||||
|
* @param result_ptr pointer to store the exit result or NULL
|
||||||
|
* @return bool on success, false on failure (errno may be set)
|
||||||
|
*/
|
||||||
|
bool thread_join(Thread th, ThreadResult *result_ptr);
|
||||||
|
|
||||||
|
#endif /* end of include guard: COMMON_UTIL_THREAD_H_ */
|
|
@ -0,0 +1,115 @@
|
||||||
|
#include "thread.h"
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ThreadEntry entry;
|
||||||
|
ThreadResult error_result;
|
||||||
|
} NativeEntryArg;
|
||||||
|
|
||||||
|
inline static pthread_t
|
||||||
|
get_handle(Thread wrapper)
|
||||||
|
{
|
||||||
|
assert(wrapper.opaque != NULL);
|
||||||
|
return *(pthread_t*) wrapper.opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static bool
|
||||||
|
new_uninitialized(Thread *th)
|
||||||
|
{
|
||||||
|
pthread_t *data = T_ALLOC(1, pthread_t);
|
||||||
|
if (!data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*th = (Thread) { .opaque = data };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static void
|
||||||
|
initialize_handle(Thread *th, pthread_t handle)
|
||||||
|
{
|
||||||
|
assert(th != NULL);
|
||||||
|
assert(th->opaque != NULL);
|
||||||
|
*(pthread_t*) th->opaque = handle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static Thread
|
||||||
|
delete_handle(Thread th)
|
||||||
|
{
|
||||||
|
if (!th.opaque) {
|
||||||
|
return THREAD_NONE;
|
||||||
|
}
|
||||||
|
free(th.opaque);
|
||||||
|
return THREAD_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void*
|
||||||
|
run_entry(void *arg)
|
||||||
|
{
|
||||||
|
assert(arg != NULL);
|
||||||
|
NativeEntryArg casted_arg = *(NativeEntryArg*) arg;
|
||||||
|
ThreadEntry entry = casted_arg.entry;
|
||||||
|
void *error_result = casted_arg.error_result.pointer;
|
||||||
|
free(arg);
|
||||||
|
if (!entry.callback) {
|
||||||
|
return error_result;
|
||||||
|
}
|
||||||
|
ThreadResult result = entry.callback(entry.env);
|
||||||
|
return result.pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread
|
||||||
|
thread_spawn(ThreadEntry entry, ThreadResult error_result)
|
||||||
|
{
|
||||||
|
NativeEntryArg *native_arg = T_ALLOC(1, NativeEntryArg);
|
||||||
|
if (!native_arg) {
|
||||||
|
return THREAD_NONE;
|
||||||
|
}
|
||||||
|
*native_arg = (NativeEntryArg) {
|
||||||
|
.entry = entry,
|
||||||
|
.error_result = error_result,
|
||||||
|
};
|
||||||
|
Thread th;
|
||||||
|
pthread_t handle;
|
||||||
|
int error;
|
||||||
|
if (!new_uninitialized(&th)) {
|
||||||
|
return THREAD_NONE;
|
||||||
|
}
|
||||||
|
error = pthread_create(&handle, NULL, &run_entry, native_arg);
|
||||||
|
if (error) {
|
||||||
|
errno = error;
|
||||||
|
return delete_handle(th);
|
||||||
|
}
|
||||||
|
assert(handle && "System allows 0 as thread id");
|
||||||
|
initialize_handle(&th, handle);
|
||||||
|
return th;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread
|
||||||
|
thread_delete_handle(Thread th)
|
||||||
|
{
|
||||||
|
return delete_handle(th);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
thread_join(Thread th, ThreadResult *result_ptr)
|
||||||
|
{
|
||||||
|
if (!th.opaque) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pthread_t handle = get_handle(th);
|
||||||
|
void **retval = NULL;
|
||||||
|
if (result_ptr) {
|
||||||
|
retval = &result_ptr->pointer;
|
||||||
|
}
|
||||||
|
int error = pthread_join(handle, retval);
|
||||||
|
if (error) {
|
||||||
|
errno = error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "common/util/hash_table.h"
|
#include "common/util/hash_table.h"
|
||||||
|
#include "common/util/byte_serdes.h"
|
||||||
|
#include "common/util/thread.h"
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
|
|
Loading…
Reference in New Issue