Fix race in background index-cache writing

Message ID 20230324215528.891455-1-tom@tromey.com
State New
Headers
Series Fix race in background index-cache writing |

Commit Message

Tom Tromey March 24, 2023, 9:55 p.m. UTC
  Tom de Vries pointed out a bug in the index-cache background writer --
sometimes it will fail.  He also noted that it fails when the number
of worker threads is set to zero.  These turn out to be the same
problem -- the cache can't be written to until the per-BFD's
"index_table" member is set.

This patch avoids the race by rearranging the code slightly, to ensure
the cache cannot possibly be written before the member is set.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30261
---
 gdb/dwarf2/cooked-index.c | 24 +++++++++++++++---------
 gdb/dwarf2/cooked-index.h |  5 ++++-
 gdb/dwarf2/read.c         |  6 +++++-
 3 files changed, 24 insertions(+), 11 deletions(-)
  

Comments

Simon Marchi March 30, 2023, 2:27 p.m. UTC | #1
On 3/24/23 17:55, Tom Tromey wrote:
> Tom de Vries pointed out a bug in the index-cache background writer --
> sometimes it will fail.  He also noted that it fails when the number
> of worker threads is set to zero.  These turn out to be the same
> problem -- the cache can't be written to until the per-BFD's
> "index_table" member is set.
> 
> This patch avoids the race by rearranging the code slightly, to ensure
> the cache cannot possibly be written before the member is set.

I've been seeing a lot of random failures of the index-cache tests
lately on my CI, so I presume this will help.

> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> index c910be875a3..dbf323d0da0 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -5147,9 +5147,13 @@ dwarf2_build_psymtabs_hard (dwarf2_per_objfile *per_objfile)
>    indexes.push_back (index_storage.release ());
>    indexes.shrink_to_fit ();
>  
> -  cooked_index *vec = new cooked_index (std::move (indexes), per_bfd);
> +  cooked_index *vec = new cooked_index (std::move (indexes));
>    per_bfd->index_table.reset (vec);
>  
> +  /* Cannot start writing the index entry until after the
> +     'index_table' member has been set.  */
> +  vec->start_writing_index (per_bfd);

An alternative I see would be to make cooked_index::cooked_index set the
per_bfd field itself (before starting the background task).  But if you
don't find it a clean solution for whatever reason, I think what you
have is fine.

Simon
  
Tom Tromey March 31, 2023, 2:39 p.m. UTC | #2
>> This patch avoids the race by rearranging the code slightly, to ensure
>> the cache cannot possibly be written before the member is set.

Simon> I've been seeing a lot of random failures of the index-cache tests
Simon> lately on my CI, so I presume this will help.

Hopefully.

Simon> An alternative I see would be to make cooked_index::cooked_index set the
Simon> per_bfd field itself (before starting the background task).  But if you
Simon> don't find it a clean solution for whatever reason, I think what you
Simon> have is fine.

I'm not sure I want cooked_index modifying the dwarf per-bfd.

This area is all rewritten again on my WIP branch to move all DWARF
reading to workers.  There, the index_table member is set very early in
the DWARF reading process.  I looked at extracting this code, but it's
tied in to how background reading works.

Tom
  

Patch

diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c
index 3a907690906..167beb2bf0e 100644
--- a/gdb/dwarf2/cooked-index.c
+++ b/gdb/dwarf2/cooked-index.c
@@ -442,26 +442,32 @@  cooked_index_shard::wait (bool allow_quit) const
     m_future.wait ();
 }
 
-cooked_index::cooked_index (vec_type &&vec, dwarf2_per_bfd *per_bfd)
+cooked_index::cooked_index (vec_type &&vec)
   : m_vector (std::move (vec))
 {
   for (auto &idx : m_vector)
     idx->finalize ();
 
-  /* This must be set after all the finalization tasks have been
-     started, because it may call 'wait'.  */
-  m_write_future
-    = gdb::thread_pool::g_thread_pool->post_task ([this, per_bfd] ()
-      {
-	maybe_write_index (per_bfd);
-      });
-
   /* ACTIVE_VECTORS is not locked, and this assert ensures that this
      will be caught if ever moved to the background.  */
   gdb_assert (is_main_thread ());
   active_vectors.insert (this);
 }
 
+/* See cooked-index.h.  */
+
+void
+cooked_index::start_writing_index (dwarf2_per_bfd *per_bfd)
+{
+  /* This must be set after all the finalization tasks have been
+     started, because it may call 'wait'.  */
+  m_write_future
+    = gdb::thread_pool::g_thread_pool->post_task ([this, per_bfd] ()
+	{
+	  maybe_write_index (per_bfd);
+	});
+}
+
 cooked_index::~cooked_index ()
 {
   /* The 'finalize' method may be run in a different thread.  If
diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h
index b16b87bbb0e..f2664929e52 100644
--- a/gdb/dwarf2/cooked-index.h
+++ b/gdb/dwarf2/cooked-index.h
@@ -366,7 +366,7 @@  class cooked_index : public dwarf_scanner_base
      object.  */
   using vec_type = std::vector<std::unique_ptr<cooked_index_shard>>;
 
-  cooked_index (vec_type &&vec, dwarf2_per_bfd *per_bfd);
+  explicit cooked_index (vec_type &&vec);
   ~cooked_index () override;
   DISABLE_COPY_AND_ASSIGN (cooked_index);
 
@@ -429,6 +429,9 @@  class cooked_index : public dwarf_scanner_base
     m_write_future.wait ();
   }
 
+  /* Start writing to the index cache, if the user asked for this.  */
+  void start_writing_index (dwarf2_per_bfd *per_bfd);
+
 private:
 
   /* Maybe write the index to the index cache.  */
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index c910be875a3..dbf323d0da0 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -5147,9 +5147,13 @@  dwarf2_build_psymtabs_hard (dwarf2_per_objfile *per_objfile)
   indexes.push_back (index_storage.release ());
   indexes.shrink_to_fit ();
 
-  cooked_index *vec = new cooked_index (std::move (indexes), per_bfd);
+  cooked_index *vec = new cooked_index (std::move (indexes));
   per_bfd->index_table.reset (vec);
 
+  /* Cannot start writing the index entry until after the
+     'index_table' member has been set.  */
+  vec->start_writing_index (per_bfd);
+
   const cooked_index_entry *main_entry = vec->get_main ();
   if (main_entry != nullptr)
     {