静默应用ACL失败(有时)

And*_*tel 8 c# acl file-permissions distributed-computing

我有一个应用程序在多个服务器上运行应用一些ACL.

问题是当多个服务器应用于相同的文件夹结构(即三个级别)时,通常只有一级和三级应用了ACL,但是没有例外.

我用并行任务创建了一个测试(模拟不同的服务器):

[TestMethod]
public void ApplyACL()
{
    var baseDir = Path.Combine(Path.GetTempPath(), "ACL-PROBLEM");

    if (Directory.Exists(baseDir))
    {
        Directory.Delete(baseDir, true);
    }

    var paths = new[]
    {
        Path.Combine(baseDir, "LEVEL-1"),
        Path.Combine(baseDir, "LEVEL-1", "LEVEL-2"),
        Path.Combine(baseDir, "LEVEL-1", "LEVEL-2", "LEVEL-3")
    };

    //create folders and files, so the ACL takes some time to apply
    foreach (var dir in paths)
    {
        Directory.CreateDirectory(dir);

        for (int i = 0; i < 1000; i++)
        {
            var id = string.Format("{0:000}", i);
            File.WriteAllText(Path.Combine(dir, id + ".txt"), id);
        }
    }

    var sids = new[]
    {
        "S-1-5-21-448539723-725345543-1417001333-1111111",
        "S-1-5-21-448539723-725345543-1417001333-2222222",
        "S-1-5-21-448539723-725345543-1417001333-3333333"
    };

    var taskList = new List<Task>();
    for (int i = 0; i < paths.Length; i++)
    {
        taskList.Add(CreateTask(i + 1, paths[i], sids[i]));        
    }

    Parallel.ForEach(taskList, t => t.Start());

    Task.WaitAll(taskList.ToArray());

    var output = new StringBuilder();
    var failed = false;
    for (int i = 0; i < paths.Length; i++)
    {
        var ok = Directory.GetAccessControl(paths[i])
                          .GetAccessRules(true, false, typeof(SecurityIdentifier))
                          .OfType<FileSystemAccessRule>()
                          .Any(f => f.IdentityReference.Value == sids[i]);

        if (!ok)
        {
            failed = true;
        }
        output.AppendLine(paths[i].Remove(0, baseDir.Length + 1) + " --> " + (ok ? "OK" : "ERROR"));
    }

    Debug.WriteLine(output);

    if (failed)
    {
        Assert.Fail();
    }
}

private static Task CreateTask(int i, string path, string sid)
{
    return new Task(() =>
    {
        var start = DateTime.Now;
        Debug.WriteLine("Task {0} start:  {1:HH:mm:ss.fffffff}", i, start);
        var fileSystemAccessRule = new FileSystemAccessRule(new SecurityIdentifier(sid), 
                                                            FileSystemRights.Modify | FileSystemRights.Synchronize,
                                                            InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                                                            PropagationFlags.None,
                                                            AccessControlType.Allow);

        var directorySecurity = Directory.GetAccessControl(path);
        directorySecurity.ResetAccessRule(fileSystemAccessRule);
        Directory.SetAccessControl(path, directorySecurity);

        Debug.WriteLine("Task {0} finish: {1:HH:mm:ss.fffffff} ({2} ms)", i, DateTime.Now, (DateTime.Now - start).TotalMilliseconds);
    });
}
Run Code Online (Sandbox Code Playgroud)

我遇到了同样的问题:通常(但并非总是)只有第一级和第三级应用了ACL.

为什么这样,我该如何解决这个问题?

Cod*_*ler 2

这是一个有趣的谜题。

我已经启动了您的测试,几乎每次运行都会重现问题。而且 ACL 通常也不适用于 LEVEL-3。

但是,如果任务不并行运行,则该问题不会重现。此外,如果目录不包含这 1000 个文件,则问题重现的频率就会大大降低。

这种行为与经典的竞争条件非常相似。

我没有找到有关此主题的任何明确信息,但似乎在重叠目录树上应用 ACL 不是线程安全操作。

为了确认这一点,我们需要分析SetAccessControl()(或者更确切地说是底层 Windows API 调用)的实现。但让我们试着想象一下它可能是什么。

  1. SetAccessControl()DirectorySecurity为给定的目录和记录调用。
  2. 它创建一些内部结构(文件系统对象)并用提供的数据填充它。
  3. 然后它开始枚举子对象(目录和文件)。这种枚举部分地由任务执行时间来确认。task3 约为 500 ms,task2 约为 1000 ms,task1 约为 1500 ms。
  4. 枚举完成后,将内部目录安全记录分配给目录。
  5. 但同时,对SetAccessControl()父目录的调用也执行相同的操作。最后它将覆盖在步骤 4 中创建的记录。

当然,所描述的流程只是一个假设。我们需要 NTFS 或 Windows 内部专家来确认这一点。

但观察到的行为几乎肯定表明了竞争状况。只要避免在重叠的目录树上并行应用 ACL 就可以睡个好觉了。