Merge branch 'fix-mutex-ub'

This commit is contained in:
Arttu Ylä-Outinen 2017-04-12 04:12:39 -07:00
commit b41f0fa8e8
2 changed files with 102 additions and 34 deletions

View file

@ -124,9 +124,9 @@ static void compute_psnr(const kvz_picture *const src,
} }
typedef struct { typedef struct {
// Mutexes for synchronization. // Semaphores for synchronization.
pthread_mutex_t* input_mutex; kvz_sem_t* available_input_slots;
pthread_mutex_t* main_thread_mutex; kvz_sem_t* filled_input_slots;
// Parameters passed from main thread to input thread. // Parameters passed from main thread to input thread.
FILE* input; FILE* input;
@ -141,9 +141,6 @@ typedef struct {
int retval; int retval;
} input_handler_args; } input_handler_args;
#define PTHREAD_LOCK(l) if (pthread_mutex_lock((l)) != 0) { fprintf(stderr, "pthread_mutex_lock(%s) failed!\n", #l); assert(0); return 0; }
#define PTHREAD_UNLOCK(l) if (pthread_mutex_unlock((l)) != 0) { fprintf(stderr, "pthread_mutex_unlock(%s) failed!\n", #l); assert(0); return 0; }
#define RETVAL_RUNNING 0 #define RETVAL_RUNNING 0
#define RETVAL_FAILURE 1 #define RETVAL_FAILURE 1
#define RETVAL_EOF 2 #define RETVAL_EOF 2
@ -242,30 +239,30 @@ static void* input_read_thread(void* in_args)
} }
// Wait until main thread is ready to receive the next frame. // Wait until main thread is ready to receive the next frame.
PTHREAD_LOCK(args->input_mutex); kvz_sem_wait(args->available_input_slots);
args->img_in = frame_in; args->img_in = frame_in;
args->retval = retval; args->retval = retval;
// Unlock main_thread_mutex to notify main thread that the new img_in // Unlock main_thread_mutex to notify main thread that the new img_in
// and retval have been placed to args. // and retval have been placed to args.
PTHREAD_UNLOCK(args->main_thread_mutex); kvz_sem_post(args->filled_input_slots);
frame_in = NULL; frame_in = NULL;
} }
done: done:
// Wait until main thread is ready to receive the next frame. // Wait until main thread is ready to receive the next frame.
PTHREAD_LOCK(args->input_mutex); kvz_sem_wait(args->available_input_slots);
args->img_in = NULL; args->img_in = NULL;
args->retval = retval; args->retval = retval;
// Unlock main_thread_mutex to notify main thread that the new img_in // Unlock main_thread_mutex to notify main thread that the new img_in
// and retval have been placed to args. // and retval have been placed to args.
PTHREAD_UNLOCK(args->main_thread_mutex); kvz_sem_post(args->filled_input_slots);
// Do some cleaning up. // Do some cleaning up.
args->api->picture_free(frame_in); args->api->picture_free(frame_in);
pthread_exit(NULL); pthread_exit(NULL);
return 0; return NULL;
} }
@ -335,6 +332,19 @@ int main(int argc, char *argv[])
kvz_picture *recon_buffer[KVZ_MAX_GOP_LENGTH] = { NULL }; kvz_picture *recon_buffer[KVZ_MAX_GOP_LENGTH] = { NULL };
int recon_buffer_size = 0; int recon_buffer_size = 0;
// Semaphores for synchronizing the input reader thread and the main
// thread.
//
// available_input_slots tells whether the main thread is currently using
// input_handler_args.img_in. (0 = in use, 1 = not in use)
//
// filled_input_slots tells whether there is a new input picture (or NULL
// if the input has ended) in input_handler_args.img_in placed by the
// input reader thread. (0 = no new image, 1 = one new image)
//
kvz_sem_t *available_input_slots = NULL;
kvz_sem_t *filled_input_slots = NULL;
#ifdef _WIN32 #ifdef _WIN32
// Stderr needs to be text mode to convert \n to \r\n in Windows. // Stderr needs to be text mode to convert \n to \r\n in Windows.
setmode( _fileno( stderr ), _O_TEXT ); setmode( _fileno( stderr ), _O_TEXT );
@ -423,17 +433,15 @@ int main(int argc, char *argv[])
pthread_t input_thread; pthread_t input_thread;
pthread_mutex_t input_mutex = PTHREAD_MUTEX_INITIALIZER; available_input_slots = calloc(1, sizeof(kvz_sem_t));
pthread_mutex_t main_thread_mutex = PTHREAD_MUTEX_INITIALIZER; filled_input_slots = calloc(1, sizeof(kvz_sem_t));
kvz_sem_init(available_input_slots, 0);
// Lock both mutexes at startup kvz_sem_init(filled_input_slots, 0);
PTHREAD_LOCK(&main_thread_mutex);
PTHREAD_LOCK(&input_mutex);
// Give arguments via struct to the input thread // Give arguments via struct to the input thread
input_handler_args in_args = { input_handler_args in_args = {
.input_mutex = NULL, .available_input_slots = available_input_slots,
.main_thread_mutex = NULL, .filled_input_slots = filled_input_slots,
.input = input, .input = input,
.api = api, .api = api,
@ -445,8 +453,8 @@ int main(int argc, char *argv[])
.img_in = NULL, .img_in = NULL,
.retval = RETVAL_RUNNING, .retval = RETVAL_RUNNING,
}; };
in_args.input_mutex = &input_mutex; in_args.available_input_slots = available_input_slots;
in_args.main_thread_mutex = &main_thread_mutex; in_args.filled_input_slots = filled_input_slots;
if (pthread_create(&input_thread, NULL, input_read_thread, (void*)&in_args) != 0) { if (pthread_create(&input_thread, NULL, input_read_thread, (void*)&in_args) != 0) {
fprintf(stderr, "pthread_create failed!\n"); fprintf(stderr, "pthread_create failed!\n");
@ -458,11 +466,12 @@ int main(int argc, char *argv[])
// Skip mutex locking if the input thread does not exist. // Skip mutex locking if the input thread does not exist.
if (in_args.retval == RETVAL_RUNNING) { if (in_args.retval == RETVAL_RUNNING) {
// Unlock input_mutex so that the input thread can write the new // Increase available_input_slots so that the input thread can
// img_in and retval to in_args. // write the new img_in and retval to in_args.
PTHREAD_UNLOCK(&input_mutex); kvz_sem_post(available_input_slots);
// Wait until the input thread has updated in_args. // Wait until the input thread has updated in_args and then
PTHREAD_LOCK(&main_thread_mutex); // decrease filled_input_slots.
kvz_sem_wait(filled_input_slots);
cur_in_img = in_args.img_in; cur_in_img = in_args.img_in;
in_args.img_in = NULL; in_args.img_in = NULL;
@ -595,6 +604,12 @@ exit_failure:
retval = EXIT_FAILURE; retval = EXIT_FAILURE;
done: done:
// destroy semaphores
if (available_input_slots) kvz_sem_destroy(available_input_slots);
if (filled_input_slots) kvz_sem_destroy(filled_input_slots);
FREE_POINTER(available_input_slots);
FREE_POINTER(filled_input_slots);
// deallocate structures // deallocate structures
if (enc) api->encoder_close(enc); if (enc) api->encoder_close(enc);
if (opts) cmdline_opts_free(api, opts); if (opts) cmdline_opts_free(api, opts);

View file

@ -30,10 +30,6 @@
#include <pthread.h> #include <pthread.h>
#define E3 1000
#define E9 1000000000
#define FILETIME_TO_EPOCH 0x19DB1DED53E8000LL
#if defined(__GNUC__) && !defined(__MINGW32__) #if defined(__GNUC__) && !defined(__MINGW32__)
#include <unistd.h> // IWYU pragma: export #include <unistd.h> // IWYU pragma: export
#include <time.h> // IWYU pragma: export #include <time.h> // IWYU pragma: export
@ -76,7 +72,64 @@
#endif //__GNUC__ #endif //__GNUC__
#undef E9 #ifdef __APPLE__
#undef E3 // POSIX semaphores are deprecated on Mac so we use Grand Central Dispatch
// semaphores instead.
#include <dispatch/dispatch.h>
typedef dispatch_semaphore_t kvz_sem_t;
static INLINE void kvz_sem_init(kvz_sem_t *sem, int value)
{
assert(value >= 0);
*sem = dispatch_semaphore_create(value);
}
static INLINE void kvz_sem_wait(kvz_sem_t *sem)
{
dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER);
}
static INLINE void kvz_sem_post(kvz_sem_t *sem)
{
dispatch_semaphore_signal(*sem);
}
static INLINE void kvz_sem_destroy(kvz_sem_t *sem)
{
// Do nothing for GCD semaphores.
}
#else
// Use POSIX semaphores.
#include <semaphore.h>
typedef sem_t kvz_sem_t;
static INLINE void kvz_sem_init(kvz_sem_t *sem, int value)
{
assert(value >= 0);
// Pthreads-w32 does not support process-shared semaphores, so pshared
// must always be zero.
int pshared = 0;
sem_init(sem, pshared, value);
}
static INLINE void kvz_sem_wait(kvz_sem_t *sem)
{
sem_wait(sem);
}
static INLINE void kvz_sem_post(kvz_sem_t *sem)
{
sem_post(sem);
}
static INLINE void kvz_sem_destroy(kvz_sem_t *sem)
{
sem_destroy(sem);
}
#endif
#endif //THREADS_H_ #endif //THREADS_H_