[binutils-gdb] Windows: Fix set_unbuffered_mode.o file rename race

Pedro Alves via Gdb-cvs <[email protected]> Wed, 22 Jul 2026 15:04:18 +0000 (GMT)
Newsgroups gmane.comp.gdb.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=3Dbinutils-gdb.git;h=3D81a1c8f753e5=
06b3890db4b1254df05aa67a0230

commit 81a1c8f753e506b3890db4b1254df05aa67a0230
Author: Pedro Alves <[email protected]>
Date:   Fri Nov 28 11:28:06 2025 +0000

    Windows: Fix set_unbuffered_mode.o file rename race
   =20
    The atomic file rename for set_unbuffered_mode.o can fail in this scena=
rio:
   =20
     | process A                       | process B                 |
     |---------------------------------+---------------------------|
     | compiles temp .o                | compiles temp .o          |
     | moves .o                        |                           |
     | links with .o file (locks file) | moves .o (fails w/ EBUSY) |
   =20
    Here's what it looks like:
   =20
      builtin_spawn -ignore SIGHUP /mingw64/bin/clang -fdiagnostics-color=
=3Dnever -Wno-unknown-warning-option -w -c -o /c/msys2/home/alves/gdb/build=
-testsuite/temp/53930/set_unbuffered_mode-c.o /c/rocgdb/src/gdb/testsuite/l=
ib/set_unbuffered_mode.c
      pid is 54259 -54259
      pid is -1
      output is  status 0
      ERROR: tcl error sourcing /c/rocgdb/src/gdb/testsuite/gdb.base/step-o=
ver-no-symbols.exp.
      ERROR: tcl error code POSIX EBUSY {file busy}
      ERROR: error renaming "/c/msys2/home/alves/gdb/build-testsuite/temp/5=
3930/set_unbuffered_mode.o" to "/c/msys2/home/alves/gdb/build-testsuite/set=
_unbuffered_mode.o": file busy
          while executing
      "file rename -force --  $unbuf_obj  $gdb_saved_set_unbuffered_mode_ob=
j"
          (procedure "gdb_compile" line 559)
          invoked from within
      "gdb_compile $source $dest $type $options"
          (procedure "gdb_compile" line 42)
          invoked from within
      "$func $objects "${binfile}" executable $options"
          (procedure "build_executable_from_specs" line 50)
          invoked from within
      "build_executable_from_specs {*}$arglist"
          (procedure "build_executable" line 11)
          invoked from within
      "build_executable "failed to build" ${testfile} $srcfile"
          (file "/c/rocgdb/src/gdb/testsuite/gdb.base/step-over-no-symbols.=
exp" line 21)
          invoked from within
      "source /c/rocgdb/src/gdb/testsuite/gdb.base/step-over-no-symbols.exp"
          ("uplevel" body line 1)
          invoked from within
      "uplevel #0 source /c/rocgdb/src/gdb/testsuite/gdb.base/step-over-no-=
symbols.exp"
          invoked from within
      "catch "uplevel #0 source $test_file_name" msg"
      UNRESOLVED: gdb.base/step-over-no-symbols.exp: testcase '/c/rocgdb/sr=
c/gdb/testsuite/gdb.base/step-over-no-symbols.exp' aborted due to Tcl error
   =20
    If we get EBUSY, it's because another parallel worker already managed
    to build and move its set_unbuffered_mode.o copy to the final
    destination.  So fix it by simply ignoring EBUSY.
   =20
    gdb_windows_manifest_obj has similar code with the same problem, so
    put the atomic rename in a new file_rename_atomic procedure, and use
    it from both places.
   =20
    (Note: both the set_unbuffered_mode.o path and
    gdb_windows_manifest_obj are Windows-specific.)
   =20
    Approved-By: Tom Tromey <[email protected]>
    Change-Id: I6a32d17364a19337d7f55e4376de736e1cca799d

Diff:
---
 gdb/testsuite/lib/gdb.exp | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 9b86d53be08..41e9a8c721e 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -6408,6 +6408,25 @@ proc quote_for_host { args } {
     return $str
 }
=20
+# Rename SRC to DST, ignoring EBUSY.  This is used when multiple
+# parallel workers all want to rename their copy of SRC to DST, as an
+# atomic commit, and it doesn't matter which one wins, as all the
+# copies are identical.
+proc file_rename_atomic {src dst} {
+    set rc [catch { file rename -force -- $src $dst } err opts]
+
+    if {$rc} {
+	set code [dict get $opts -errorcode]
+	if {[llength $code] >=3D 2 && [lindex $code 1] eq "EBUSY"} {
+	    # Normal parallel race loss.
+	} else {
+	    error $err $opts
+	}
+    }
+
+    return $rc
+}
+
 # Set while linker_supports_manifest_embed is running its test link,
 # so that the inner gdb_compile that link goes through skips the
 # manifest-embedding logic and doesn't recurse back into the probe.
@@ -6491,7 +6510,7 @@ proc gdb_windows_manifest_obj {} {
     if {[info exists ::GDB_PARALLEL]} {
 	# Make sure to write the .o file atomically.  (Note
 	# GDB_PARALLEL mode does not support remote host testing.)
-	file rename -force -- $obj $saved
+	file_rename_atomic $obj $saved
     } else {
 	remote_download host $obj $saved
     }
@@ -7019,7 +7038,7 @@ proc gdb_compile {source dest type options} {
 		    # Make sure to write the .o file atomically.
 		    # (Note GDB_PARALLEL mode does not support remote
 		    # host testing.)
-		    file rename -force -- $unbuf_obj $gdb_saved_set_unbuffered_mode_obj
+		    file_rename_atomic $unbuf_obj $gdb_saved_set_unbuffered_mode_obj
 		} else {
 		    remote_download host $unbuf_obj $gdb_saved_set_unbuffered_mode_obj
 		}