JDK-4742723 : File.mkdirs() fails due to race condition
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 1.3.0,1.4.1,5.0u11,6
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: generic,windows_2000
  • CPU: generic,x86
  • Submitted: 2002-09-06
  • Updated: 2007-10-05
  • Resolved: 2011-05-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7
6u2Fixed 7 b09Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Description
Name: nt126004			Date: 09/05/2002


FULL PRODUCT VERSION :
java version "1.4.1-rc"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1-rc-b19)
Java HotSpot(TM) Client VM (build 1.4.1-rc-b19, mixed mode)

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

ADDITIONAL OPERATING SYSTEMS :
All others probably



A DESCRIPTION OF THE PROBLEM :
File.mkdirs fails to create the entire hierarchy of
directories if another application or thread creates
one of the intervening directories while mkdirs is
running.

An example: We have two threads, A and B.
A calls new File("c:\base\a").mkdirs();
B calls new File("c:\base\b").mkdirs();

Pseudo-trace (follow along in File.mkdirs):
A: if (exists())     // c:\base\a doesn't exist
A: if (mkdir())      // fails to create c:\base\a
A: parent = getParent();  // c:\base
A: new File(parent).mkdirs()
B: if (exists())     // c:\base\b doesn't exist
B: if (mkdir())      // fails to create c:\base\b
B: parent = getParent();  // c:\base
B: new File(parent).mkdirs()
A: if (exists())     // c:\base doesn't exist
A: if (mkdir())      // creates c:\base
A: return true;
B: if (exists())     // c:\base does exist
B: return false;
A: mkdir()           // creates c:\base\a and returns true
B: // parent.mkdirs returned false so doesn't create
c:\base\b. Returns false.

I can see no reasonable excuse for thread B's mkdirs to
fail just because someone else happened to create one of
the directories in the desired path.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Compile and run the sample code
2. Check whether both directories were created

It's timing-dependant so it might not happen every time.



EXPECTED VERSUS ACTUAL BEHAVIOR :
Two directories, c:\base\a and c:\base\b, should be created.
Instead, just one of them gets created.


REPRODUCIBILITY :
This bug can be reproduced often.

---------- BEGIN SOURCE ----------
import java.io.File;

public class MkdirsBug {
   public static void main(String[] args)
   {
      Thread A = new Thread() {
         public void run()
         {
            new File("c:\\base\\a").mkdirs();
         }
      };
      Thread B = new Thread() {
         public void run()
         {
            new File("c:\\base\\b").mkdirs();
         }
      };

      A.start();
      B.start();
   }
}


---------- END SOURCE ----------

CUSTOMER WORKAROUND :
Write your own mkdirs.
(Review ID: 164083) 
======================================================================

Comments
EVALUATION There is indeed a possible race condition in mkdirs() and the suggested fix seems to make it go away in this particular situation. But the reality is that as long as the mkdirs() is not an "atomic" operation it's impossible to "completely" fix this bug at java lib level (for example, there is no way you can prevent another thread, or other process, from deleting your parent dir after you successful created it, right before you go into to invoke mkdir()...), the only thing we can do is to "improve the situation" to reduce the race condition window, the suggested one is something worth doing.
08-02-2007

SUGGESTED FIX One of my fellow programmers reported that this bug was a serious problem for their commercial server and they eventually had to make a workaround. This race condition can be fixed using the following change, which recovers the thread B after unsuccessful mkdir(): public boolean mkdirs() { if (exists()) { return false; } if (mkdir()) { return true; } File canonFile = null; try { canonFile = getCanonicalFile(); } catch (IOException e) { return false; } < String parent = canonFile.getParent(); < return (parent != null) && < (new File(parent, fs.prefixLength(parent)).mkdirs() && canonFile.mkdir()); --- > File parentFile = canonFile.getParentFile(); > return (parentFile != null) && > (parentFile.mkdirs() || parentFile.exists()) && canonFile.mkdir(); }
11-10-2006