尝试打开互斥锁时出现未经授权的访问异常

18

在尝试打开互斥锁时,我遇到了这个异常(它只会偶尔发生;大多数调用都是成功的):

System.UnauthorizedAccessException: Access to the path 'Global\4c7cddf7-e729-43b6-a75c-43f54a0ac6ac' is denied.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.Threading.Mutex.OpenExisting(String name, MutexRights rights)

我正在使用的与互斥锁相关的代码:

public class MutexLocker : IDisposable
{
    public MutexLocker(string id)
    {
        var doesNotExist = false;
        var unauthorized = false;

        try
        {
            _mutex = Mutex.OpenExisting(id, MutexRights.Synchronize | MutexRights.Modify);
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            doesNotExist = true;
        }
        catch (UnauthorizedAccessException ex)
        {
            unauthorized = true;
        }

        if (doesNotExist)
        {
            _mutex = new Mutex(false, id);

            var allowEveryoneRule = new MutexAccessRule(
                new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
            _mutex.SetAccessControl(securitySettings);
        }
        else if (unauthorized)
        {
            var tempMutex = Mutex.OpenExisting(id, MutexRights.ReadPermissions | MutexRights.ChangePermissions);
            var securitySettings = tempMutex.GetAccessControl();

            var user = Environment.UserDomainName + "\\" + Environment.UserName;

            // the rule that denied the current user the right to enter and release the mutex must be removed
            var rule = new MutexAccessRule(user, MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Deny);
            securitySettings.RemoveAccessRule(rule);

            // Now grant the correct rights
            var allowEveryoneRule = new MutexAccessRule(
                new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
            securitySettings.AddAccessRule(allowEveryoneRule);
            tempMutex.SetAccessControl(securitySettings);

            _mutex = Mutex.OpenExisting(id, MutexRights.Synchronize | MutexRights.Modify);
        }

        var success = _mutex.WaitOne(TimeSpan.FromSeconds(10), false);
        if (success == false)
        {
            _mutex.Dispose();
            _mutex = null;
            throw new ApplicationException(string.Format("Can't lock mutex (timed out): {0}", id));
        }
    }

    public void Dispose()
    {
        if (_mutex != null)
        {
            try
            {
                _mutex.ReleaseMutex();
            }
            catch (Exception exc)
            {
                Trace.WriteLine(exc);
            }

            _mutex.Dispose();
        }
    }

    private readonly Mutex _mutex;
}

互斥锁"id"是一个唯一标识符,名称冲突是不可能的。
这是唯一能够创建该互斥锁的代码,并且为所有用户授予完全访问权限(我的进程可以在不同的用户凭据下运行)。

任何想法,为什么会出现未经授权的访问错误?


你尝试过为测试目的从名称中删除“Global”吗?您还可以尝试使用较短的名称。Guid可能对于名称来说太长了。 - Demir
1
也许将您的代码与此答案中的代码进行比较 - 也许您会发现一些有用的东西:https://dev59.com/yXVC5IYBdhLWcg3woStW#229567 - Matthew Watson
1
有两个 Mutex.OpenExisting 可能会在 if (unauthorized) 中生成异常。哪一个是导致异常的? - MC ND
2
不是很明显。互斥锁在没有 ACL 的情况下创建,因此它会根据创建它的用户权限获得一个 ACL。然后生成并应用于互斥锁的 ACL。在此期间,ACL 可能会阻止访问互斥锁,因此执行“tempMutex = Mutex.OpenExisting”的另一个线程/进程/用户/会话将抛出异常。 - MC ND
虽然我认为这与你的问题无关,但在“_mutex.Dispose()”之后,在你的Dispose方法中添加“_mutex = null; GC.SuppressFinalizer(this);”。 - Dweeberly
显示剩余6条评论
1个回答

24

这个类应该可以解决你的问题:

    public class MutexLocker: IDisposable
    {
        private Mutex _mutex;

        public MutexLocker ( string id )
        {
            bool createdNew;
            MutexSecurity mutexSecurity = new MutexSecurity();
            mutexSecurity.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), 
                                                            MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));

            try
            {
                // attempt to create the mutex, with the desired DACL..
                _mutex = new Mutex(false, id, out createdNew, mutexSecurity);
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                // the mutex cannot be opened, probably because a Win32 object of a different
                // type with the same name already exists.
                throw;
            }
            catch (UnauthorizedAccessException)
            {
                // the mutex exists, but the current process or thread token does not
                // have permission to open the mutex with SYNCHRONIZE | MUTEX_MODIFY rights.
                throw;
            }
        }

        public void Dispose ()
        {
            if (_mutex != null)
            {
                _mutex.ReleaseMutex();
                _mutex.Dispose();
            }

            _mutex = null;
        }
    }

需要注意的唯一部分是 Mutex 构造函数,它将尝试立即创建互斥对象(通过调用 Win32 的 CreateMutex()),并将提供的安全描述符分配给命名对象。如果 CreateMutex 调用失败,框架将尝试使用 OpenMutex 打开命名的互斥对象,请求 SYNCHRONIZEMUTEX_MODIFY 权限。

您看到的问题是创建互斥对象和赋值安全描述符之间的简单竞争条件(至少就我所注意到的而言)。使创建和安全描述符分配成为原子操作将解决这个问题。


你的解决方案和解释仍然是错误的。在我的机器上它仍然抛出相同的异常。 - Cary
@Cary 你能再解释一下吗? - William
@Cary:在 .NET 4.7.2 的文档中,它说以 "Global" 开头的互斥对象仅适用于 "运行终端服务的服务器"。你是在 Windows Server 上运行吗? - sthiers
@sthiers,不,我没有在Windows服务器上运行它。 - Cary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接