Esempio n. 1
0
        private bool?TryAcquire(IUmbracoDatabase db, string tempId, string updatedTempId)
        {
            using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted);

            try
            {
                // get a read lock
                _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);

                // the row
                var mainDomRows = db.Fetch <KeyValueDto>("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });

                if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId)
                {
                    // the other main dom has updated our record
                    // Or the other maindom shutdown super fast and just deleted the record
                    // which indicates that we
                    // can acquire it and it has shutdown.

                    _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);

                    // so now we update the row with our appdomain id
                    InsertLockRecord(_lockId, db);
                    _logger.Debug <SqlMainDomLock>("Acquired with ID {LockId}", _lockId);
                    return(true);
                }
                else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId))
                {
                    // in this case, the prefixed ID is different which  means
                    // another new AppDomain has come online and is wanting to take over. In that case, we will not
                    // acquire.

                    _logger.Debug <SqlMainDomLock>("Cannot acquire, another booting application detected.");
                    return(false);
                }
            }
            catch (Exception ex)
            {
                if (IsLockTimeoutException(ex as SqlException))
                {
                    _logger.Error <SqlMainDomLock>(ex, "Sql timeout occurred, waiting for existing MainDom is canceled.");
                    _errorDuringAcquiring = true;
                    return(false);
                }
                // unexpected
                _logger.Error <SqlMainDomLock>(ex, "Unexpected error, waiting for existing MainDom is canceled.");
                _errorDuringAcquiring = true;
                return(false);
            }
            finally
            {
                transaction.Complete();
            }

            return(null); // continue
        }
Esempio n. 2
0
        public async Task <bool> AcquireLockAsync(int millisecondsTimeout)
        {
            if (!_dbFactory.Configured)
            {
                // if we aren't configured then we're in an install state, in which case we have no choice but to assume we can acquire
                return(true);
            }

            if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider))
            {
                throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server");
            }

            _sqlServerSyntax = sqlServerSyntaxProvider;

            _logger.Debug <SqlMainDomLock>("Acquiring lock...");

            var tempId = Guid.NewGuid().ToString();

            IUmbracoDatabase db = null;

            try
            {
                db = _dbFactory.CreateDatabase();

                _hasTable = db.HasTable(Constants.DatabaseSchema.Tables.KeyValue);
                if (!_hasTable)
                {
                    // the Db does not contain the required table, we must be in an install state we have no choice but to assume we can acquire
                    return(true);
                }

                db.BeginTransaction(IsolationLevel.ReadCommitted);

                try
                {
                    // wait to get a write lock
                    _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom);
                }
                catch (SqlException ex)
                {
                    if (IsLockTimeoutException(ex))
                    {
                        _logger.Error <SqlMainDomLock>(ex, "Sql timeout occurred, could not acquire MainDom.");
                        _errorDuringAcquiring = true;
                        return(false);
                    }

                    // unexpected (will be caught below)
                    throw;
                }

                var result = InsertLockRecord(tempId, db); //we change the row to a random Id to signal other MainDom to shutdown
                if (result == RecordPersistenceType.Insert)
                {
                    // if we've inserted, then there was no MainDom so we can instantly acquire

                    InsertLockRecord(_lockId, db); // so update with our appdomain id
                    _logger.Debug <SqlMainDomLock, string>("Acquired with ID {LockId}", _lockId);
                    return(true);
                }

                // if we've updated, this means there is an active MainDom, now we need to wait to
                // for the current MainDom to shutdown which also requires releasing our write lock
            }
            catch (Exception ex)
            {
                // unexpected
                _logger.Error <SqlMainDomLock>(ex, "Unexpected error, cannot acquire MainDom");
                _errorDuringAcquiring = true;
                return(false);
            }
            finally
            {
                db?.CompleteTransaction();
                db?.Dispose();
            }


            return(await WaitForExistingAsync(tempId, millisecondsTimeout).ConfigureAwait(false));
        }
Esempio n. 3
0
        public async Task <bool> AcquireLockAsync(int millisecondsTimeout)
        {
            if (!_dbFactory.Configured)
            {
                // if we aren't configured, then we're in an install state, in which case we have no choice but to assume we can acquire
                return(true);
            }

            if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider))
            {
                throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server");
            }

            _sqlServerSyntax = sqlServerSyntaxProvider;

            _logger.Debug <SqlMainDomLock>("Acquiring lock...");

            var db = GetDatabase();

            var tempId = Guid.NewGuid().ToString();

            try
            {
                db.BeginTransaction(IsolationLevel.ReadCommitted);

                try
                {
                    // wait to get a write lock
                    _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom);
                }
                catch (Exception ex)
                {
                    if (IsLockTimeoutException(ex))
                    {
                        _logger.Error <SqlMainDomLock>(ex, "Sql timeout occurred, could not acquire MainDom.");
                        _hasError = true;
                        return(false);
                    }

                    // unexpected (will be caught below)
                    throw;
                }

                var result = InsertLockRecord(tempId); //we change the row to a random Id to signal other MainDom to shutdown
                if (result == RecordPersistenceType.Insert)
                {
                    // if we've inserted, then there was no MainDom so we can instantly acquire

                    // TODO: see the other TODO, could we just delete the row and that would indicate that we
                    // are MainDom? then we don't leave any orphan rows behind.

                    InsertLockRecord(_lockId); // so update with our appdomain id
                    _logger.Debug <SqlMainDomLock>("Acquired with ID {LockId}", _lockId);
                    return(true);
                }

                // if we've updated, this means there is an active MainDom, now we need to wait to
                // for the current MainDom to shutdown which also requires releasing our write lock
            }
            catch (Exception ex)
            {
                ResetDatabase();
                // unexpected
                _logger.Error <SqlMainDomLock>(ex, "Unexpected error, cannot acquire MainDom");
                _hasError = true;
                return(false);
            }
            finally
            {
                db?.CompleteTransaction();
            }

            return(await WaitForExistingAsync(tempId, millisecondsTimeout));
        }
Esempio n. 4
0
        /// <summary>
        /// Wait for any existing MainDom to release so we can continue booting
        /// </summary>
        /// <param name="tempId"></param>
        /// <param name="millisecondsTimeout"></param>
        /// <returns></returns>
        private Task <bool> WaitForExistingAsync(string tempId, int millisecondsTimeout)
        {
            var updatedTempId = tempId + UpdatedSuffix;

            return(Task.Run(() =>
            {
                var db = GetDatabase();
                var watch = new Stopwatch();
                watch.Start();
                while (true)
                {
                    // poll very often, we need to take over as fast as we can
                    Thread.Sleep(100);

                    try
                    {
                        db.BeginTransaction(IsolationLevel.ReadCommitted);

                        // get a read lock
                        _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);

                        // the row
                        var mainDomRows = db.Fetch <KeyValueDto>("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });

                        if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId)
                        {
                            // the other main dom has updated our record
                            // Or the other maindom shutdown super fast and just deleted the record
                            // which indicates that we
                            // can acquire it and it has shutdown.

                            _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);

                            // so now we update the row with our appdomain id
                            InsertLockRecord(_lockId);
                            _logger.Debug <SqlMainDomLock>("Acquired with ID {LockId}", _lockId);
                            return true;
                        }
                        else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId))
                        {
                            // in this case, the prefixed ID is different which  means
                            // another new AppDomain has come online and is wanting to take over. In that case, we will not
                            // acquire.

                            _logger.Debug <SqlMainDomLock>("Cannot acquire, another booting application detected.");

                            return false;
                        }
                    }
                    catch (Exception ex)
                    {
                        ResetDatabase();

                        if (IsLockTimeoutException(ex))
                        {
                            _logger.Error <SqlMainDomLock>(ex, "Sql timeout occurred, waiting for existing MainDom is canceled.");
                            _hasError = true;
                            return false;
                        }
                        // unexpected
                        _logger.Error <SqlMainDomLock>(ex, "Unexpected error, waiting for existing MainDom is canceled.");
                        _hasError = true;
                        return false;
                    }
                    finally
                    {
                        db?.CompleteTransaction();
                    }

                    if (watch.ElapsedMilliseconds >= millisecondsTimeout)
                    {
                        // if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown,
                        // or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row
                        // and it's just been left as an orphan row.
                        // There's really know way of knowing unless we are constantly updating the row for the current maindom
                        // which isn't ideal.
                        // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok

                        _logger.Debug <SqlMainDomLock>("Timeout elapsed, assuming orphan row, acquiring MainDom.");

                        try
                        {
                            db.BeginTransaction(IsolationLevel.ReadCommitted);

                            _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);

                            // so now we update the row with our appdomain id
                            InsertLockRecord(_lockId);
                            _logger.Debug <SqlMainDomLock>("Acquired with ID {LockId}", _lockId);
                            return true;
                        }
                        catch (Exception ex)
                        {
                            ResetDatabase();

                            if (IsLockTimeoutException(ex))
                            {
                                // something is wrong, we cannot acquire, not much we can do
                                _logger.Error <SqlMainDomLock>(ex, "Sql timeout occurred, could not forcibly acquire MainDom.");
                                _hasError = true;
                                return false;
                            }
                            _logger.Error <SqlMainDomLock>(ex, "Unexpected error, could not forcibly acquire MainDom.");
                            _hasError = true;
                            return false;
                        }
                        finally
                        {
                            db?.CompleteTransaction();
                        }
                    }
                }
            }, _cancellationTokenSource.Token));
        }