]> sourceware.org Git - libabigail.git/commitdiff
Fix a race condition in queue::priv::do_bring_workers_down
authorDodji Seketeli <dodji@redhat.com>
Tue, 18 Apr 2017 07:56:23 +0000 (03:56 -0400)
committerDodji Seketeli <dodji@redhat.com>
Tue, 18 Apr 2017 08:21:13 +0000 (04:21 -0400)
It can happen that queue::priv_::do_bring_workers_down stays forever
waiting for a task to finish (via pthread_join).  The it waits for is
itself blocked in worker::wait_to_execute_a_task, in pthread_cond_wait.

It seems to me that this is because we forget to lock the
queue::priv::queue_cond_mutex before inspecting and updating the
variables on which the wait on the condition depend.

This patch fixes that.

The patch also moves tests/test-read-write.cc over to using the work queue
to increase test coverage for the work queue interface.

* src/abg-workers.cc (queue::priv::tasks_todo_mutex): Make this
data member mutable.
(more_tasks_to_execute):
(queue::priv::do_bring_workers_down): Update the
queue::priv::bring_workers_down only in the critical section
defined by queue::priv::queue_cond_mutex.
(worker::wait_to_execute_a_task): Testing for
queue::priv::bring_workers_down is done in the critical section
defined by queue::priv::queue_cond_mutex.  The loop over waiting
ont the condition is also in the critical section, as it ought to
be.
* tests/test-read-write.cc (struct test_task): New type.
(main): Express in terms of the new test_task type.

Signed-off-by: Dodji Seketeli <dodji@redhat.com>
src/abg-workers.cc
tests/test-read-write.cc

index 34ef1fd5f038401da77446d03b6ec492ef91570f..07acaa18f590eb1d17148d9a955acc87714abc24 100644 (file)
@@ -145,7 +145,7 @@ struct queue::priv
   pthread_cond_t               queue_cond;
   // A mutex that protects the todo tasks queue from being accessed in
   // read/write by two threads at the same time.
-  pthread_mutex_t              tasks_todo_mutex;
+  mutable pthread_mutex_t      tasks_todo_mutex;
   // A mutex that protects the done tasks queue from being accessed in
   // read/write by two threads at the same time.
   pthread_mutex_t              tasks_done_mutex;
@@ -209,6 +209,18 @@ struct queue::priv
       notify(n)
   {create_workers();}
 
+  /// Tests if there are more tasks to execute from the task queue.
+  ///
+  /// @return true iff there are more tasks to execute.
+  bool
+  more_tasks_to_execute() const
+  {
+    pthread_mutex_lock(&tasks_todo_mutex);
+    bool result = !tasks_todo.empty();
+    pthread_mutex_unlock(&tasks_todo_mutex);
+    return result;
+  }
+
   /// Create the worker threads pool and have all threads sit idle,
   /// waiting for a task to be added to the todo queue.
   void
@@ -288,14 +300,11 @@ struct queue::priv
     if (workers.empty())
       return;
 
-    pthread_mutex_lock(&tasks_todo_mutex);
-    bring_workers_down = true;
-    pthread_mutex_unlock(&tasks_todo_mutex);
-
     // Acquire the mutex that protects the queue condition variable
     // (queue_cond) and wake up all the workers that are sleeping on
     // the condition.
     pthread_mutex_lock(&queue_cond_mutex);
+    bring_workers_down = true;
     assert(pthread_cond_broadcast(&queue_cond) == 0);
     pthread_mutex_unlock(&queue_cond_mutex);
 
@@ -428,27 +437,15 @@ queue::task_done_notify::operator()(const task_sptr&/*task_done*/)
 queue::priv*
 worker::wait_to_execute_a_task(queue::priv* p)
 {
-  pthread_mutex_lock(&p->tasks_todo_mutex);
-  bool more_tasks = !p->tasks_todo.empty();
-  bool bring_workers_down = p->bring_workers_down;
-  pthread_mutex_unlock(&p->tasks_todo_mutex);
+
+  pthread_mutex_lock(&p->queue_cond_mutex);
 
   do
     {
       // If there is no more tasks to perform and the queue is not to
       // be brought down then wait (sleep) for new tasks to come up.
-      while (!more_tasks && !bring_workers_down)
-       {
-         pthread_mutex_lock(&p->queue_cond_mutex);
-         pthread_cond_wait(&p->queue_cond, &p->queue_cond_mutex);
-         pthread_mutex_unlock(&p->queue_cond_mutex);
-
-         pthread_mutex_lock(&p->tasks_todo_mutex);
-         more_tasks = !p->tasks_todo.empty();
-         bring_workers_down = p->bring_workers_down;
-         pthread_mutex_unlock(&p->tasks_todo_mutex);
-       }
-
+      while (!p->more_tasks_to_execute() && !p->bring_workers_down)
+       pthread_cond_wait(&p->queue_cond, &p->queue_cond_mutex);
 
       // We were woken up.  So maybe there are tasks to perform?  If
       // so, get a task from the queue ...
@@ -480,13 +477,10 @@ worker::wait_to_execute_a_task(queue::priv* p)
          p->notify(t);
          pthread_mutex_unlock(&p->tasks_done_mutex);
        }
-
-      pthread_mutex_lock(&p->tasks_todo_mutex);
-      more_tasks = !p->tasks_todo.empty();
-      bring_workers_down = p->bring_workers_down;
-      pthread_mutex_unlock(&p->tasks_todo_mutex);
     }
-    while (!p->bring_workers_down || more_tasks);
+  while (!p->bring_workers_down || p->more_tasks_to_execute());
+
+  pthread_mutex_unlock(&p->queue_cond_mutex);
 
   return p;
 }
index 014882dc65744eb69d6155be23233f3ae38408d5..0347573fe2d74f22a30f92425896d1efdb0f57a6 100644 (file)
 #include <iostream>
 #include <cstdlib>
 #include <cstring>
+#include <vector>
 #include "abg-ir.h"
 #include "abg-reader.h"
 #include "abg-writer.h"
+#include "abg-workers.h"
 #include "abg-tools-utils.h"
 #include "test-utils.h"
 
 using std::string;
+using std::vector;
 using std::ofstream;
 using std::cerr;
 
+using std::tr1::dynamic_pointer_cast;
+
 using abigail::tools_utils::file_type;
 using abigail::tools_utils::check_file;
 using abigail::tools_utils::guess_file_type;
@@ -50,6 +55,11 @@ using abigail::xml_reader::read_corpus_from_native_xml_file;
 using abigail::xml_writer::write_translation_unit;
 using abigail::xml_writer::write_corpus_to_native_xml;
 
+using abigail::workers::queue;
+using abigail::workers::task;
+using abigail::workers::task_sptr;
+using abigail::workers::get_number_of_threads;
+
 /// This is an aggregate that specifies where a test shall get its
 /// input from, and where it shall write its ouput to.
 struct InOutSpec
@@ -247,79 +257,155 @@ InOutSpec in_out_specs[] =
   {NULL, NULL, NULL, NULL}
 };
 
+/// A task wihch reads an abixml file using abilint and compares its
+/// output against a reference output.
+struct test_task : public abigail::workers::task
+{
+  InOutSpec spec;
+  bool is_ok;
+  string in_path, out_path, in_suppr_spec_path, ref_out_path;
+  string diff_cmd, error_message;
+
+  /// Constructor of the task.
+  ///
+  /// @param the spec of where to find the abixml file to read and the
+  /// reference output of the test.
+  test_task( InOutSpec& s)
+    : spec(s),
+      is_ok(true)
+  {}
+
+  /// This method defines what the task performs.
+  virtual void
+  perform()
+  {
+    string input_suffix(spec.in_path);
+    in_path =
+      string(abigail::tests::get_src_dir()) + "/tests/" + input_suffix;
+
+    if (!check_file(in_path, cerr))
+      {
+       is_ok = false;
+       return;
+      }
+
+    string ref_out_path_suffix(spec.ref_out_path);
+    ref_out_path =
+      string(abigail::tests::get_src_dir())
+      + "/tests/" + ref_out_path_suffix;
+
+    if (!check_file(ref_out_path, cerr))
+      {
+       is_ok = false;
+       return;
+      }
+
+    if (spec.in_suppr_spec_path && strcmp(spec.in_suppr_spec_path, ""))
+      {
+       in_suppr_spec_path = string(spec.in_suppr_spec_path);
+       in_suppr_spec_path =
+         string(abigail::tests::get_src_dir())
+         + "/tests/"
+         + in_suppr_spec_path;
+      }
+    else
+      in_suppr_spec_path.clear();
+
+    environment_sptr env(new environment);
+    translation_unit_sptr tu;
+    corpus_sptr corpus;
+
+    file_type t = guess_file_type(in_path);
+    if (t == abigail::tools_utils::FILE_TYPE_UNKNOWN)
+      {
+       cerr << in_path << "is an unknown file type\n";
+       is_ok = false;
+       return;
+      }
+
+    string output_suffix(spec.out_path);
+    out_path =
+      string(abigail::tests::get_build_dir()) + "/tests/" + output_suffix;
+    if (!abigail::tools_utils::ensure_parent_dir_created(out_path))
+      {
+       error_message =
+         "Could not create parent director for " + out_path;
+       is_ok = false;
+       return;
+      }
+
+    string abilint = string(get_build_dir()) + "/tools/abilint";
+    if (!in_suppr_spec_path.empty())
+      abilint +=string(" --suppr ") + in_suppr_spec_path;
+    string cmd = abilint + " " + in_path + " > " + out_path;
+
+    if (system(cmd.c_str()))
+      {
+       error_message =
+         "ABI XML file doesn't pass abilint: " + out_path + "\n";
+       is_ok = false;
+      }
+
+    cmd = "diff -u " + ref_out_path + " " + out_path;
+    diff_cmd = cmd;
+    if (system(cmd.c_str()))
+      is_ok = false;
+  }
+};// end struct test_task
+
+/// A convenience typedef for shared
+typedef shared_ptr<test_task> test_task_sptr;
+
 /// Walk the array of InOutSpecs above, read the input files it points
 /// to, write it into the output it points to and diff them.
 int
 main()
 {
-  unsigned result = 1;
+  using abigail::workers::queue;
+  using abigail::workers::task;
+  using abigail::workers::task_sptr;
+  using abigail::workers::get_number_of_threads;
+
+  const size_t num_tests = sizeof(in_out_specs) / sizeof (InOutSpec) - 1;
+  size_t num_workers = std::min(get_number_of_threads(), num_tests);
+  queue task_queue(num_workers);
 
   bool is_ok = true;
+
+
   string in_path, out_path, in_suppr_spec_path, ref_out_path;
   for (InOutSpec* s = in_out_specs; s->in_path; ++s)
     {
-      string input_suffix(s->in_path);
-      in_path =
-       string(abigail::tests::get_src_dir()) + "/tests/" + input_suffix;
-
-      if (!check_file(in_path, cerr))
-       return true;
+      test_task_sptr t(new test_task(*s));
+      assert(task_queue.schedule_task(t));
+    }
 
-      string ref_out_path_suffix(s->ref_out_path);
-      ref_out_path =
-       string(abigail::tests::get_src_dir())
-       + "/tests/" + ref_out_path_suffix;
+  /// Wait for all worker threads to finish their job, and wind down.
+  task_queue.wait_for_workers_to_complete();
 
-      if (!check_file(ref_out_path, cerr))
-       return true;
+  // Now walk the results and print whatever error messages need to be
+  // printed.
 
-      if (s->in_suppr_spec_path && strcmp(s->in_suppr_spec_path, ""))
-       {
-         in_suppr_spec_path = string(s->in_suppr_spec_path);
-         in_suppr_spec_path =
-           string(abigail::tests::get_src_dir())
-           + "/tests/"
-           + in_suppr_spec_path;
-       }
-      else
-       in_suppr_spec_path.clear();
+  const vector<task_sptr>& completed_tasks =
+    task_queue.get_completed_tasks();
 
-      environment_sptr env(new environment);
-      translation_unit_sptr tu;
-      corpus_sptr corpus;
+  assert(completed_tasks.size() == num_tests);
 
-      file_type t = guess_file_type(in_path);
-      if (t == abigail::tools_utils::FILE_TYPE_UNKNOWN)
-       {
-         cerr << in_path << "is an unknown file type\n";
-         is_ok = false;
-         continue;
-       }
-
-      string output_suffix(s->out_path);
-      out_path =
-       string(abigail::tests::get_build_dir()) + "/tests/" + output_suffix;
-      if (!abigail::tools_utils::ensure_parent_dir_created(out_path))
+  for (vector<task_sptr>::const_iterator ti = completed_tasks.begin();
+       ti != completed_tasks.end();
+       ++ti)
+    {
+      test_task_sptr t = dynamic_pointer_cast<test_task>(*ti);
+      if (!t->is_ok)
        {
-         cerr << "Could not create parent director for " << out_path;
          is_ok = false;
-         return result;
-       }
 
-      string abilint = string(get_build_dir()) + "/tools/abilint";
-      if (!in_suppr_spec_path.empty())
-       abilint +=string(" --suppr ") + in_suppr_spec_path;
-      string cmd = abilint + " " + in_path + " > " + out_path;
+         if (!t->error_message.empty())
+           cerr << t->error_message << '\n';
 
-      if (system(cmd.c_str()))
-       {
-         cerr << "ABI XML file doesn't pass abilint: " << out_path << "\n";
-         is_ok &= false;
+         if (!t->diff_cmd.empty())
+           system(t->diff_cmd.c_str());
        }
-
-      cmd = "diff -u " + ref_out_path + " " + out_path;
-      if (system(cmd.c_str()))
-       is_ok &= false;
     }
 
   return !is_ok;
This page took 0.044493 seconds and 5 git commands to generate.