Eliminate a race condition with input thread.

Changes communication between the input thread and main thread in
encmain.c so that only one of them uses img_in and retval at a time.
Fixes a race condition which would sometimes result in a deadlock.
This commit is contained in:
Arttu Ylä-Outinen 2016-02-17 12:09:19 +02:00
parent dac57fa154
commit e5c84c361c

View file

@ -123,16 +123,20 @@ static void compute_psnr(const kvz_picture *const src,
} }
typedef struct { typedef struct {
FILE* input; // Mutexes for synchronization.
pthread_mutex_t* input_mutex; pthread_mutex_t* input_mutex;
pthread_mutex_t* main_thread_mutex; pthread_mutex_t* main_thread_mutex;
kvz_picture **img_in; // Parameters passed from main thread to input thread.
cmdline_opts_t *opts; FILE* input;
encoder_control_t *encoder; const kvz_api *api;
uint8_t padding_x; const cmdline_opts_t *opts;
uint8_t padding_y; const encoder_control_t *encoder;
const kvz_api * api; const uint8_t padding_x;
const uint8_t padding_y;
// Picture and thread status passed from input thread to main thread.
kvz_picture *img_in;
int retval; int retval;
} input_handler_args; } input_handler_args;
@ -160,6 +164,7 @@ static void* input_read_thread(void* in_args)
input_handler_args* args = (input_handler_args*)in_args; input_handler_args* args = (input_handler_args*)in_args;
kvz_picture *frame_in = NULL; kvz_picture *frame_in = NULL;
int retval = RETVAL_RUNNING;
int frames_read = 0; int frames_read = 0;
for (;;) { for (;;) {
@ -169,49 +174,61 @@ static void* input_read_thread(void* in_args)
bool input_empty = !(args->opts->frames == 0 // number of frames to read is unknown bool input_empty = !(args->opts->frames == 0 // number of frames to read is unknown
|| frames_read < args->opts->frames); // not all frames have been read || frames_read < args->opts->frames); // not all frames have been read
if (feof(args->input) || input_empty) { if (feof(args->input) || input_empty) {
goto exit_eof; retval = RETVAL_EOF;
goto done;
} }
frame_in = args->api->picture_alloc(args->opts->config->width + args->padding_x, args->opts->config->height + args->padding_y); frame_in = args->api->picture_alloc(args->opts->config->width + args->padding_x,
args->opts->config->height + args->padding_y);
if (!frame_in) { if (!frame_in) {
fprintf(stderr, "Failed to allocate image.\n"); fprintf(stderr, "Failed to allocate image.\n");
goto exit_failure; retval = RETVAL_FAILURE;
goto done;
} }
if (!yuv_io_read(args->input, args->opts->config->width, args->opts->config->height, frame_in)) { if (!yuv_io_read(args->input, args->opts->config->width, args->opts->config->height, frame_in)) {
// reading failed // reading failed
if (feof(args->input)) { if (feof(args->input)) {
goto exit_eof; retval = RETVAL_EOF;
goto done;
} else { } else {
fprintf(stderr, "Failed to read a frame %d\n", frames_read); fprintf(stderr, "Failed to read a frame %d\n", frames_read);
goto exit_failure; retval = RETVAL_FAILURE;
goto done;
} }
} }
frames_read++;
if (args->encoder->cfg->source_scan_type != 0) { if (args->encoder->cfg->source_scan_type != 0) {
// Set source scan type for frame, so that it will be turned into fields. // Set source scan type for frame, so that it will be turned into fields.
frame_in->interlacing = args->encoder->cfg->source_scan_type; frame_in->interlacing = args->encoder->cfg->source_scan_type;
} }
args->img_in[frames_read & 1] = frame_in;
frame_in = NULL;
frames_read++; // Wait until main thread is ready to receive the next frame.
// Wait until main thread is ready to receive input and then release main thread
PTHREAD_LOCK(args->input_mutex); PTHREAD_LOCK(args->input_mutex);
args->img_in = frame_in;
args->retval = retval;
// Unlock main_thread_mutex to notify main thread that the new img_in
// and retval have been placed to args.
PTHREAD_UNLOCK(args->main_thread_mutex); PTHREAD_UNLOCK(args->main_thread_mutex);
frame_in = NULL;
} }
exit_eof: done:
args->retval = RETVAL_EOF; // Wait until main thread is ready to receive the next frame.
args->img_in[frames_read & 1] = NULL; PTHREAD_LOCK(args->input_mutex);
exit_failure: args->img_in = NULL;
// Do some cleaning up args->retval = retval;
// Unlock main_thread_mutex to notify main thread that the new img_in
// and retval have been placed to args.
PTHREAD_UNLOCK(args->main_thread_mutex);
// Do some cleaning up.
args->api->picture_free(frame_in); args->api->picture_free(frame_in);
if (!args->retval) {
args->retval = RETVAL_FAILURE;
}
pthread_exit(NULL); pthread_exit(NULL);
return 0; return 0;
} }
@ -319,7 +336,6 @@ int main(int argc, char *argv[])
encoding_start_cpu_time = clock(); encoding_start_cpu_time = clock();
uint64_t bitstream_length = 0; uint64_t bitstream_length = 0;
uint32_t frames_read = 0;
uint32_t frames_done = 0; uint32_t frames_done = 0;
double psnr_sum[3] = { 0.0, 0.0, 0.0 }; double psnr_sum[3] = { 0.0, 0.0, 0.0 };
@ -336,18 +352,20 @@ int main(int argc, char *argv[])
PTHREAD_LOCK(&input_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 = {
kvz_picture *img_in[2] = { NULL }; .input_mutex = &input_mutex,
in_args.input = input; .main_thread_mutex = &main_thread_mutex,
in_args.img_in = img_in;
in_args.main_thread_mutex = &main_thread_mutex; .input = input,
in_args.input_mutex = &input_mutex; .api = api,
in_args.opts = opts; .opts = opts,
in_args.encoder = encoder; .encoder = encoder,
in_args.padding_x = padding_x; .padding_x = padding_x,
in_args.padding_y = padding_y; .padding_y = padding_y,
in_args.api = api;
in_args.retval = RETVAL_RUNNING; .img_in = NULL,
.retval = RETVAL_RUNNING,
};
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");
@ -357,16 +375,20 @@ int main(int argc, char *argv[])
kvz_picture *cur_in_img; kvz_picture *cur_in_img;
for (;;) { for (;;) {
// Skip mutex locking when thread is no longer in run state // Skip mutex locking if the input thread does not exist.
if (in_args.retval == RETVAL_RUNNING) { if (in_args.retval == RETVAL_RUNNING) {
// Wait for input to be read // Unlock input_mutex so that the input thread can write the new
// unlock the input thread to be able to continue to the next picture // img_in and retval to in_args.
PTHREAD_UNLOCK(&input_mutex); PTHREAD_UNLOCK(&input_mutex);
// Wait until the input thread has updated in_args.
PTHREAD_LOCK(&main_thread_mutex); PTHREAD_LOCK(&main_thread_mutex);
cur_in_img = in_args.img_in;
in_args.img_in = NULL;
} else {
cur_in_img = NULL;
} }
cur_in_img = img_in[frames_read & 1];
img_in[frames_read & 1] = NULL;
frames_read++;
if (in_args.retval == EXIT_FAILURE) { if (in_args.retval == EXIT_FAILURE) {
goto exit_failure; goto exit_failure;