ao_write: Avoid segfault on exit.
[paraslash.git] / ao_write.c
index 93861ab63f50baf478bf74fe6f216fce6cf39f53..12ab77fe8dc33c1b5be189eff6bd4621cb4305a6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2012 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2011-2014 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
@@ -39,6 +39,10 @@ static void aow_close(struct writer_node *wn)
 
        if (!pawd)
                return;
+       if (pawd->thread_btrn) {
+               pthread_cancel(pawd->thread);
+               pthread_join(pawd->thread, NULL);
+       }
        ao_close(pawd->dev);
        free(pawd);
        wn->private_data = NULL;
@@ -48,10 +52,37 @@ static void aow_close(struct writer_node *wn)
 static void aow_pre_select(struct sched *s, struct task *t)
 {
        struct writer_node *wn = container_of(t, struct writer_node, task);
-       int ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
+       struct private_aow_data *pawd = wn->private_data;
+       int ret;
 
-       if (ret == 0)
-               return;
+       if (!pawd) { /* not yet started */
+               assert(wn->btrn);
+               ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
+               if (ret != 0)
+                       goto min_delay;
+               return; /* no data available */
+       }
+       if (!wn->btrn) { /* EOF */
+               if (!pawd->thread_btrn) /* ready to exit */
+                       goto min_delay;
+               /* wait for the play thread to terminate */
+               goto timeout;
+       }
+       pthread_mutex_lock(&pawd->mutex);
+       ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
+       pthread_mutex_unlock(&pawd->mutex);
+       if (ret != 0)
+               goto min_delay;
+       /*
+        * Even though the node status is zero, we might have data available,
+        * but the output buffer is full. If we don't set a timeout here, we
+        * are woken up only if new data arrives, which might be too late and
+        * result in a buffer underrun in the playing thread. To avoid this we
+        * never sleep longer than the (default) buffer time.
+        */
+timeout:
+       return sched_request_timeout_ms(20, s);
+min_delay:
        sched_min_delay(s);
 }
 
@@ -223,13 +254,16 @@ __noreturn static void *aow_play(void *priv)
                ret = -E_AO_PLAY;
                if (ao_play(pawd->dev, data, bytes) == 0) /* failure */
                        goto out;
+               pthread_mutex_lock(&pawd->mutex);
                btr_consume(btrn, bytes);
+               pthread_mutex_unlock(&pawd->mutex);
        }
 unlock:
        pthread_mutex_unlock(&pawd->mutex);
 out:
        assert(ret < 0);
        PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
+       btr_remove_node(&pawd->thread_btrn);
        pthread_exit(NULL);
 }
 
@@ -276,11 +310,10 @@ fail:
        return -E_AO_PTHREAD;
 }
 
-static void aow_post_select(__a_unused struct sched *s,
+static int aow_post_select(__a_unused struct sched *s,
                struct task *t)
 {
        struct writer_node *wn = container_of(t, struct writer_node, task);
-       struct btr_node *btrn = wn->btrn;
        struct private_aow_data *pawd = wn->private_data;
        int ret;
 
@@ -288,14 +321,14 @@ static void aow_post_select(__a_unused struct sched *s,
                int32_t rate, ch, format;
                struct btr_node_description bnd;
 
-               ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+               ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
                if (ret < 0)
                        goto remove_btrn;
                if (ret == 0)
-                       return;
-               get_btr_sample_rate(btrn, &rate);
-               get_btr_channels(btrn, &ch);
-               get_btr_sample_format(btrn, &format);
+                       return 0;
+               get_btr_sample_rate(wn->btrn, &rate);
+               get_btr_channels(wn->btrn, &ch);
+               get_btr_sample_format(wn->btrn, &format);
                ret = aow_init(wn, rate, ch, format);
                if (ret < 0)
                        goto remove_btrn;
@@ -303,7 +336,7 @@ static void aow_post_select(__a_unused struct sched *s,
 
                /* set up thread btr node */
                bnd.name = "ao_thread_btrn";
-               bnd.parent = btrn;
+               bnd.parent = wn->btrn;
                bnd.child = NULL;
                bnd.handler = NULL;
                bnd.context = pawd;
@@ -313,46 +346,48 @@ static void aow_post_select(__a_unused struct sched *s,
                ret = aow_create_thread(wn);
                if (ret < 0)
                        goto remove_thread_btrn;
-               return;
+               return 0;
+       }
+       if (!wn->btrn) {
+               if (!pawd->thread_btrn)
+                       return -E_AO_EOF;
+               PARA_INFO_LOG("waiting for play thread to terminate\n");
+               return 0;
        }
        pthread_mutex_lock(&pawd->mutex);
-       ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+       ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
        if (ret > 0) {
-               btr_pushdown(btrn);
+               btr_pushdown(wn->btrn);
                pthread_cond_signal(&pawd->data_available);
        }
        pthread_mutex_unlock(&pawd->mutex);
        if (ret >= 0)
                goto out;
        pthread_mutex_lock(&pawd->mutex);
-       btr_remove_node(btrn);
-       btrn = NULL;
-       PARA_INFO_LOG("waiting for thread to terminate\n");
+       btr_remove_node(&wn->btrn);
        pthread_cond_signal(&pawd->data_available);
        pthread_mutex_unlock(&pawd->mutex);
-       pthread_join(pawd->thread, NULL);
+       return 0;
 remove_thread_btrn:
-       btr_remove_node(pawd->thread_btrn);
-       btr_free_node(pawd->thread_btrn);
+       btr_remove_node(&pawd->thread_btrn);
 remove_btrn:
-       if (btrn)
-               btr_remove_node(btrn);
+       btr_remove_node(&wn->btrn);
 out:
-       t->error = ret;
+       return ret;
 }
 
-__malloc static void *aow_parse_config_or_die(const char *options)
+__malloc static void *aow_parse_config_or_die(int argc, char **argv)
 {
        struct ao_write_args_info *conf = para_calloc(sizeof(*conf));
 
        /* exits on errors */
-       ao_cmdline_parser_string(options, conf, "ao_write");
+       ao_write_cmdline_parser(argc, argv, conf);
        return conf;
 }
 
 static void aow_free_config(void *conf)
 {
-       ao_cmdline_parser_free(conf);
+       ao_write_cmdline_parser_free(conf);
 }
 
 /**
@@ -369,15 +404,13 @@ void ao_write_init(struct writer *w)
        ao_info **driver_list;
        char **dh; /* detailed help */
 
-       ao_cmdline_parser_init(&dummy);
+       ao_write_cmdline_parser_init(&dummy);
        w->close = aow_close;
        w->pre_select = aow_pre_select;
        w->post_select = aow_post_select;
        w->parse_config_or_die = aow_parse_config_or_die;
        w->free_config = aow_free_config;
-       w->help = (struct ggo_help) {
-               .short_help = ao_write_args_info_help,
-       };
+       w->help = (struct ggo_help)DEFINE_GGO_HELP(ao_write);
        /* create detailed help containing all supported drivers/options */
        for (i = 0; ao_write_args_info_detailed_help[i]; i++)
                ; /* nothing */
@@ -415,7 +448,7 @@ void ao_write_init(struct writer *w)
        }
        dh[num_lines] = NULL;
        w->help.detailed_help = (const char **)dh;
-       ao_cmdline_parser_free(&dummy);
+       ao_write_cmdline_parser_free(&dummy);
        ao_shutdown();
 }