【问题标题】:Unreliable file system operations on WindowsWindows 上不可靠的文件系统操作
【发布时间】:2015-11-10 19:00:14
【问题描述】:

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

  1. 创建目录Test,然后创建Test/held,然后创建文件Test/held/info

  2. Test 重命名为YXCV

  3. 读取在步骤 1 中创建的文件(现在从路径 YXCV/held/info)。

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

  5. 重复。

很奇怪,这失败了。有时在第 2 步(“权限被拒绝”),有时在第 3 步(文件无法打开,尽管之后我可以在常规文本编辑器中打开文件)。 有时这会立即失败,有时会成功执行数千次迭代

我在这里运行的是 Windows 7。我怀疑一些配置更改(公司 IT 管理超出了我的控制),因为问题发生在一周前。

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

这是我的测试代码:

#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();
}

【问题讨论】:

  • 您可以使用Process Monitor 准确找出失败的操作和错误代码。也许您遇到了共享违规。
  • 检查 peek() 方法是否有任何打开的“信息”文件句柄
  • 用你关闭的任何防病毒程序试试。
  • @JonathanPotter 绝对值得一试。如果这是防病毒软件的可能影响,我想知道我的系统是如何保持可用的。

标签: c++ windows winapi file-io filesystems


【解决方案1】:

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

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

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

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

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

P.S: 为了帮助您了解发生了什么,您可以使用 Microsoft 的 process explorer 并使用 Ctrl+F搜索文件句柄>。在“句柄”字段中输入文件的名称,它将显示哪些进程使用该文件。注意:需要以管理员身份运行才能搜索系统进程。

【讨论】:

  • 在大多数情况下,您可以通过显式验证删除后目录是否不再存在来解决此问题,如果不存在,请定期重试删除操作。还应该注意的是,Windows 不支持使用目录结构作为锁定机制。相反,它提供了明确的文件锁定机制。
  • 我知道 windows 不喜欢删除在资源管理器中打开的目录(在本地驱动器上,网络驱动器的工作方式似乎不同)。打开文本文件在这里工作正常。因此,如果发生一些并发(锁定)操作,我会收到 expected 错误。但是:我尝试过单独运行,我什至关闭了Bazaar的后台进程。
  • @HarryJohnston 也许我不应该提到“Bazaar”和“locking”,因为我对上述访问模式可能失败的可能原因感兴趣。但是,是的,如果用作锁定机制会因为并发操作而失败。
  • 事情是虽然你不能真正控制所有可能的并发操作。有防病毒软件、Windows Search、任何数量的第三方程序,我不知道还有什么。我怀疑如果你将rmdir 调用放在一个循环中,即使 cleanup() 函数同步,你会发现问题消失了。
  • @HarryJohnston 好的,了解了一些关于 Windows 的知识:rmdir 在内部调用 RemoveDirectory,这只是“在关闭时标记要删除的目录”,使该函数异步。因此我不必遍历rmdir,我只需要等待删除发生即可。显然,rename(调用MoveFileEx)也可以,可能是出于同样的原因,尽管我看不到the documentation 中明确说明。
【解决方案2】:

我在这里发布一个答案,以展示我现在是如何解决这个问题的。

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

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

编辑:正如对 Christophe 帖子的评论中所述,rmdir 在内部调用 RemoveDirectory,这只是“在关闭时标记要删除的目录”,从而使该功能异步。因此我不必遍历rmdir,我只需要等待删除发生。显然,rename(它调用MoveFileEx)也可以,可能是由于同样的原因,虽然我看不到明确说明in the documentation,所以仍有谜团有待解决.

在此处查看完整代码:

#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();
}

【讨论】:

    猜你喜欢
    • 2012-07-07
    • 1970-01-01
    • 2015-07-14
    • 1970-01-01
    • 2011-06-21
    • 1970-01-01
    • 2020-09-12
    • 1970-01-01
    • 2020-04-05
    相关资源
    最近更新 更多