Esempio n. 1
0
        internal FileLockUse(FileLockContext fileLockContext, string lockId)
        {
            this.fileLockContext = fileLockContext ?? throw new ArgumentNullException(nameof(fileLockContext));

            if (fileLockContext.FileStream is null)
            {
                throw new ArgumentException("File stream context has invalid file stream.");
            }

            LockId = lockId;
        }
Esempio n. 2
0
        /// <summary>
        /// Decreases the number of locks in use. If becoming zero, file gets unlocked.
        /// </summary>
        internal int DecreaseLockUse(bool decreaseToZero, string?lockId)
        {
            lockId = lockId ?? "none";
            SpinWait spinWait = new SpinWait();
            int      desiredLocksInUse;

            do
            {
                var currentLocksInUse = locksInUse;

                if (0 >= currentLocksInUse)
                {
                    Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Number of lock remains at 0 because file has been unlocked before. {unlockSourceString(decreaseToZero)}", TraceCategory);
                    return(0);
                }

                if (decreaseToZero)
                {
                    desiredLocksInUse = 0;
                }
                else
                {
                    desiredLocksInUse = currentLocksInUse - 1;
                }

                var actualLocksInUse = Interlocked.CompareExchange(ref locksInUse, desiredLocksInUse, currentLocksInUse);

                if (currentLocksInUse == actualLocksInUse)
                {
                    break;
                }

                spinWait.SpinOnce();
            } while (true);

            string decreasedNumberOfLocksInUseMessage() =>
            $"{CurrentThreadWithLockIdPrefix(lockId)} Number of lock uses is decreased to {desiredLocksInUse}. {unlockSourceString(decreaseToZero)}";

            // When no locks are registered, we have to ..
            if (0 == desiredLocksInUse)
            {
                // 1. wait for file stream assignment,
                FileLockContext?nullState    = null;
                FileLockContext nonNullState = null !;

                while (true)
                {
                    nullState = Interlocked.CompareExchange(ref fileLockerState, null, nullState);

                    /* When class scoped file stream is null local file stream will be null too.
                     * => If so, spin once and continue loop.
                     *
                     * When class scoped file stream is not null the local file stream will become
                     * not null too.
                     * => If so, assigned class scoped file streama to to local non null file stream
                     *    and continue loop.
                     *
                     * When class scoped file stream is null and local non null file stream is not null
                     * => If so, break loop.
                     */
                    if (nullState == null && nonNullState is null)
                    {
                        spinWait.SpinOnce();
                    }
                    else if (nullState == null && !(nonNullState is null))
                    {
                        break;
                    }
                    else
                    {
                        nonNullState = nullState !;
                    }
                }

                // 2. invalidate the file stream.
                nonNullState.FileStream?.Close();
                nonNullState.FileStream?.Dispose();
                Trace.WriteLine($"{decreasedNumberOfLocksInUseMessage()}{Environment.NewLine}{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} unlocked by file locker. {unlockSourceString(decreaseToZero)}", TraceCategory);
            }
            else
            {
                Trace.WriteLine($"{decreasedNumberOfLocksInUseMessage()}");
            }

            return(desiredLocksInUse);
        }
Esempio n. 3
0
        /// <summary>
        /// Locks the file specified at location <see cref="FilePath"/>.
        /// </summary>
        /// <returns>The file lock use that can be revoked by disposing it.</returns>
        public FileLockUse WaitUntilAcquired()
        {
            var lockId = getLockId();

            Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Begin locking file {FilePath}.", TraceCategory);
            SpinWait spinWait = new SpinWait();

            while (true)
            {
                var currentLocksInUse      = locksInUse;
                var desiredLocksInUse      = currentLocksInUse + 1;
                var currentFileLockerState = fileLockerState;

                if (currentFileLockerState.IsErroneous())
                {
                    if (EnableConcurrentRethrow)
                    {
                        Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Error from previous lock will be rethrown.", TraceCategory);
                        throw currentFileLockerState !.Error !;
                    }

                    // Imagine stair steps where each stair step is Lock():
                    // Thread #0 Lock #0 -> Incremented to 1 -> Exception occured.
                    //  Thread #1 Lock #1 -> Incremented to 2. Recognozes exception in #0 because #0 not yet entered Unlock().
                    //   Thread #2 Lock #2 -> Incremented to 3. Recognizes excetion in #1 because #0 not yet entered Unlock().
                    // Thread #3 Lock #3 -> Incremented to 1. Lock was successful.
                    // We want Lock #1 and Lock #2 to retry their Lock():
                    //  Thread #1 Lock #1 -> Incremented to 2. Lock was successful.
                    //   Thread #2 Lock #2 -> Incremented to 3. Lock was successful.
                    currentFileLockerState !.ErrorUnlockDone !.WaitOne();
                    Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Retry lock due to previously failed lock.", TraceCategory);
                    continue;
                }
                // If it is the initial lock, then we expect file stream being null.
                // If it is not the initial lock, we expect the stream being not null.
                else if ((currentLocksInUse == 0 && currentFileLockerState != null) ||
                         (currentLocksInUse != 0 && currentFileLockerState == null))
                {
                    spinWait.SpinOnce();
                    continue;
                }
                else
                {
                    if (currentLocksInUse != Interlocked.CompareExchange(ref locksInUse, desiredLocksInUse, currentLocksInUse))
                    {
                        continue;
                    }

                    // The above conditions met, so if it is the initial lock, then we want
                    // to acquire the lock.
                    if (desiredLocksInUse == 1)
                    {
                        try {
                            var fileStream = LockFileApi.Default.WaitUntilAcquired(FilePath, TimeoutInMilliseconds, fileMode: FileMode,
                                                                                   fileAccess: FileAccess, fileShare: FileShare) !;

                            currentFileLockerState = new FileLockContext(this, decreaseLockUseLocker, fileStream);

                            fileLockerState = currentFileLockerState;
                            Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} locked by file locker.", TraceCategory);
                        } catch (Exception error) {
                            var errorUnlockDone = new ManualResetEvent(false);
                            currentFileLockerState = new FileLockContext(this, decreaseLockUseLocker, error, errorUnlockDone);
                            fileLockerState        = currentFileLockerState;
                            Unlock(lockId);
                            // After we processed Unlock(), we can surpass these locks
                            // who could be dependent on state assigment of this Lock().
                            currentFileLockerState.ErrorUnlockDone !.Set();
                            throw;
                        }
                    }
                    else
                    {
                        Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} locked {desiredLocksInUse} time(s) concurrently by file locker. {fileStreamHasBeenLockedString(currentFileLockerState!.FileStream!)}", TraceCategory);
                    }
                }

                var fileLockContract = new FileLockUse(currentFileLockerState, lockId);
                return(fileLockContract);
            }
        }