在Windows上不可靠的文件系统操作

我不得不注意到在我的机器上Bazaar的目录locking机制的一些奇怪的行为,并试图重现。 这是我的简单testing用例:

  1. 创build一个目录Test ,然后Test/held ,然后是一个文件Test/held/info

  2. Test重命名为YXCV

  3. 读取步骤1中创build的文件(现在从pathYXCV/held/info )。

  4. 清理(​​删除文件和目录)。

  5. 重复。

奇怪的是,这失败了。 有时在步骤2(“权限被拒绝”),有时在步骤3(文件无法打开,虽然我可以在普通的文本编辑器中打开文件)。 有时这会立即失败,有时会有数千次迭代成功执行

我在这里运行Windows 7。 我怀疑一些configuration变化(我的控制权在企业ITpipe理),因为这个问题发生在一个星期前。

你知道任何可能的合理的解释吗?

这是我的testing代码:

 #include <iostream> #include <fstream> #include <direct.h> #include <stdio.h> void mkdir() { if ( mkdir( "Test" ) ) throw std::runtime_error( "mkdir" ); if ( mkdir( "Test/held" ) ) throw std::runtime_error( "mkdir" ); } void create() { if ( !std::ofstream( "Test/held/info" ).write( "asdf", 4 ) ) throw std::runtime_error( "create" ); } void rename() { if ( rename( "Test", "YXCV" ) ) throw std::runtime_error( "rename" ); } void peek() { char buf[ 4 ]; if ( !std::ifstream( "YXCV/held/info" ).read( buf, 4 ) ) throw std::runtime_error( "peek" ); } void del() { if ( unlink( "YXCV/held/info" ) ) throw std::runtime_error( "remove" ); if ( rmdir( "YXCV/held" ) ) throw std::runtime_error( "remove" ); if ( rmdir( "YXCV" ) ) throw std::runtime_error( "remove" ); } void cleanup() { unlink( "Test/held/info" ); rmdir( "Test/held" ); rmdir( "Test" ); unlink( "YXCV/held/info" ); rmdir( "YXCV/held" ); rmdir( "YXCV" ); } int main() { cleanup(); int count = 1; try { for ( ;; ++count ) { mkdir (); create(); rename(); peek (); del (); } } catch ( const std::exception &e ) { std::cout << "Run: " << count << "\nError: " << e.what() << "\n\t" << strerror( errno ) << '\n'; } std::cin.get(); } 

当我单独运行程序时,它会永远循环而不会出错。

但是,一旦我同时执行一些文件系统操作,使用其他程序,您的代码就会像您所描述的那样失败:

  • 如果打开一个资源管理器窗口并导航到创建的叶子目录并停留在那里,则代码将无法删除或重命名目录(步骤1或4)。
  • 如果我用一些文本编辑器打开新文件,文件读取失败(步骤3)。

这是Windows文件系统的正常行为。 例如,如果一个程序在目录上有一个句柄,则不能删除( rmdir()错误代码为EACCESS )。

你已经解释过你在Bazaar版本管理目录结构中工作。 这意味着一些后台服务进程监视目录和文件的变化,并最终执行一些钩子和插件 (这可能会延长锁定条件)。 这通常会产生上述锁定情况。

PS:为了帮助您查看正在发生的事情,您可以使用Microsoft的进程资源管理器并使用Ctrl + F搜索文件句柄。 在“句柄”字段中输入文件的名称,它将显示哪些进程使用该文件。 注意:以管理员身份运行也需要搜索系统进程。

我在这里发布一个答案来显示我现在如何解决这个问题。

以“打开文件YXCV/held/info问题”为例,不同之处在于我在打开文件YXCV/held/info明确检查了文件的存在(通过调用stat() )。

有趣的是:我不必重试重rename() ,这已经成功根据返回代码,我的函数seek()只是看不到它立即。 我观察到的所有错误都可以通过等待文件系统状态预期可观察到。

编辑:正如在克里斯托夫的帖子的评论中指出的那样, rmdir内部调用RemoveDirectory ,并且只是“标记一个关闭的删除目录”,使该功能异步。 因此,我不必循环rmdir,我只需要等待删除发生。 显然, rename (也称为MoveFileEx )也可能是由于相同的原因,尽管我看不到在文档中明确指出,所以仍然有些谜团需要解决

在这里查看完整的代码:

 #include <iostream> #include <fstream> #include <direct.h> #include <io.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <Windows.h> bool exists( const char *p ) { struct stat buffer; return stat( p, &buffer ) == 0; } void wait_for( const char *p ) { while ( !exists( p ) ) { std::cout << "wait for " << p << '\n'; Sleep( 500 ); } } void wait_for_del( const char *p ) { while ( exists( p ) ) { std::cout << "wait for deletion of " << p << '\n'; Sleep( 500 ); } } void mkdir() { if ( mkdir( "Test" ) ) throw std::runtime_error( "mkdir" ); if ( mkdir( "Test/held" ) ) throw std::runtime_error( "mkdir" ); } void create() { if ( !std::ofstream( "Test/held/info" ).write( "asdf", 4 ) ) throw std::runtime_error( "create" ); } void rename() { wait_for( "Test" ); if ( rename( "Test", "YXCV" ) ) throw std::runtime_error( "rename" ); } void peek() { wait_for( "YXCV/held/info" ); char buf[ 4 ]; if ( !std::ifstream( "YXCV/held/info" ).read( buf, 4 ) ) throw std::runtime_error( "peek" ); } void del() { wait_for( "YXCV/held/info" ); if ( unlink( "YXCV/held/info" ) ) throw std::runtime_error( "remove" ); if ( rmdir( "YXCV/held" ) ) throw std::runtime_error( "remove" ); if ( rmdir( "YXCV" ) ) throw std::runtime_error( "remove" ); wait_for_del( "YXCV" ); } void cleanup() { unlink( "Test/held/info" ); wait_for_del( "Test/held/info" ); rmdir( "Test/held" ); wait_for_del( "Test/held" ); rmdir( "Test" ); wait_for_del( "Test" ); unlink( "YXCV/held/info" ); wait_for_del( "YXCV/held/info" ); rmdir( "YXCV/held" ); wait_for_del( "YXCV/held" ); rmdir( "YXCV" ); wait_for_del( "YXCV" ); } int main() { cleanup(); int count = 1; try { for ( ; count <= 1000; ++count ) { mkdir (); create(); rename(); peek (); del (); } std::cout << "OK."; } catch ( const std::exception &e ) { std::cout << "Run: " << count << "\nError: " << e.what() << "\n\t" << strerror( errno ) << '\n'; } std::cin.get(); }