// Code an OP_TableLock instruction for each table locked by the statement (configured by calls to sqlite3TableLock()). static void codeTableLocks(Parse pParse) { var pVdbe = sqlite3GetVdbe(pParse); Debug.Assert(pVdbe != null); // sqlite3GetVdbe cannot fail: VDBE already allocated for (int i = 0; i < pParse.nTableLock; i++) { TableLock p = pParse.aTableLock[i]; int p1 = p.iDb; sqlite3VdbeAddOp4(pVdbe, OP_TableLock, p1, p.iTab, p.isWriteLock, p.zName, P4_STATIC); } }
public TableLock GetTableLock(string tableName, int recordId) { TableLock result = null; string userLogin = _userService.GetCurrentUser(); lock (safeLock) { result = tableLocks.FirstOrDefault(p => p.TableName == tableName.ToLower() && p.RecordKey == recordId && p.LockDate > DateTime.Now.AddSeconds(-1 * _settings.LockExpireDuration) //&& p.UserLogin != userLogin ); } return(result); }
public void RemoveLock(TableLock tableLock) { lock (_changeLocker) { if (tableLock.LockType == LockType.Update) { _hasMonopolLock = false; } if (_tableLocksQueue.Count > 0 && !_hasMonopolLock) { _tableLocksQueue.TryDequeue(out var newLock); newLock.Notify.Set(); _hasMonopolLock = newLock.LockType == LockType.Update; } } }
public static void MergeMask(AdoTransaction adoTransaction) { try { ServerMarketData.maskLock.AcquireReaderLock(CommonTimeout.LockWait); TableLock tableLock = (TableLock)ServerMarketData.maskLocks[MarkThree.Guardian.Server.Environment.UserName]; adoTransaction.LockRequests.AddWriterLock(tableLock); } finally { if (ServerMarketData.maskLock.IsReaderLockHeld) { ServerMarketData.maskLock.ReleaseReaderLock(); } } adoTransaction.LockRequests.AddWriterLock(ServerMarketData.maskLock); }
public override bool TryAcquireLock(string correlationId, string key, long timeToLive) { try { if (_table == null) { _logger.Error(correlationId, $"TryAcquireLock: Lock storage table is not initialized."); return(false); } var operation = TableOperation.Retrieve <TableLock>(correlationId, key); var record = _table.ExecuteAsync(operation).Result; var tableLock = record.Result as TableLock; if (tableLock != null) { if (tableLock.Expired > DateTime.UtcNow) { _logger.Trace(correlationId, $"TryAcquireLock: Key = '{key}' has been already locked and not expired."); return(false); } _logger.Trace(correlationId, $"TryAcquireLock: Locked key = '{key}' expired."); } var lockRecord = new TableLock(correlationId, key, timeToLive); var insertOrReplaceOperation = TableOperation.InsertOrReplace(lockRecord); _table.ExecuteAsync(insertOrReplaceOperation).Wait(); _logger.Trace(correlationId, $"TryAcquireLock: Set Key = '{key}' to 'lock' state; it will be expired at {lockRecord.Expired.ToString()} UTC."); return(true); } catch (Exception exception) { _logger.Error(correlationId, exception, $"TryAcquireLock: Failed to acquire lock for key = '{key}'."); return(false); } }
public void SetTableLock(string tableName, int recordId) { string userLogin = _userService.GetCurrentUser(); lock (safeLock) { var existingLock = tableLocks.FirstOrDefault(p => p.TableName.ToLower() == tableName.ToLower() && p.RecordKey == recordId); if (existingLock == null) { TableLock tableLock = new TableLock(); tableLock.LockDate = DateTime.Now; tableLock.RecordKey = recordId; tableLock.TableName = tableName?.ToLower(); tableLock.UserName = _sharedDataManager.GetUserName(); tableLock.UserLogin = _userService.GetCurrentUser(); tableLocks.Add(tableLock); } else { existingLock.LockDate = DateTime.Now; } } }
public void AddLock(TableLock tableLock) { lock (_changeLocker) { if (tableLock.LockType == LockType.Update) { if (!_hasMonopolLock) { _hasMonopolLock = true; tableLock.Notify.Set(); } else { _tableLocksQueue.Enqueue(tableLock); } } else { tableLock.Notify.Set(); } } }
/// <summary> /// Initializes the Globa Data Model. /// </summary> static ServerDataModel() { // IMPORTANT CONCEPT: The RowVersion object keeps track of the 'age' of the server dataset. When rows are added, // updated and deleted, the row version is incremented. There is a single, monotonically incrementing value used for // the entire DataSet. We initialize the database with a value of 1, that allows the client to use a value of zero // when initializing. A requested row version of zero will guarantee that all the contents of the database are // returned to the client. ServerDataModel.rowVersion = new RowVersion(); // The persistent storage device used by this server is specified in this custom configuration section. PersistentStoreSection persistentStoreSection = (PersistentStoreSection)ConfigurationManager.GetSection("persistentStoreSection"); PersistentStoreInfo persistentStoreInfo = persistentStoreSection[PersistentStore]; if (persistentStoreInfo == null) { throw new Exception("There is no persistent storage device defined for this server."); } SqlConnection sqlConnection = new SqlConnection(persistentStoreInfo.ConnectionString); try { // Make sure all the tables are locked before populating them. foreach (TableLock tableLock in ServerDataModel.TableLocks) { tableLock.AcquireWriterLock(CommonTimeout.LockWait); } // IMPORTANT CONCEPT: These filters are used for security and performance. They remove elements that a user has // no right to view on the local client. They must also guarantee referential integrity if a record is removed. // That is, if you have a filter to remove an element, there must be another filter to guarantee the children of // that element are not returned to the client. ServerDataModel.Object.UserFilter = new RowFilterDelegate(FilterObject); ServerDataModel.Security.UserFilter = new RowFilterDelegate(FilterSecurity); ServerDataModel.Price.UserFilter = new RowFilterDelegate(FilterPrice); ServerDataModel.Currency.UserFilter = new RowFilterDelegate(FilterCurrency); ServerDataModel.Debt.UserFilter = new RowFilterDelegate(FilterDebt); ServerDataModel.Equity.UserFilter = new RowFilterDelegate(FilterEquity); // A view is needed for all the tables so we can search the records according to 'age'. The 'RowVersion' is an // indication of the relative age of an record. When a client requests a refresh of it's data model, we will // return any records that are younger than the oldest record on the client. To find these records efficiently, we // need this view on the table. foreach (Table table in ServerDataModel.Tables) { table.DefaultView.Sort = "RowVersion DESC"; table.DefaultView.RowStateFilter = DataViewRowState.OriginalRows; } // Open a connection to the server and read all the tables into the data model. sqlConnection.Open(); // Constraints are disabled so the data can be loaded in table-by-table from the persistent store. ServerDataModel.EnforceConstraints = false; // This will keep track of the largest row number in the persistent data store. This maximum row version will // become the seed value for the system-wide row version that is assigned to every row that is added, updated or // deleted. long maxRowVersion = 0; // Read each of the persistent tables from the relational database store. foreach (Table table in ServerDataModel.Tables) { if (table.IsPersistent) { // Every record has an 'Archived' bit which indicates whether the record has been deleted from the active // database. This bit is used here to discard records that shouldn't be included in the active database. // While it would be possible to simply select all the records where the 'Archived' bit is clear, they are // added to the table and then deleted in order to keep the identity columns of the ADO tables synchronized // with the indices of the persistent SQL tables. This section of code will construct a statement that // will select all the persistent columns from the relational database. string columnList = "IsArchived, IsDeleted"; foreach (Column column in table.Columns) { if (column.IsPersistent) { columnList += (columnList == string.Empty ? string.Empty : ", ") + "\"" + column.ColumnName + "\""; } } string selectCommand = String.Format("select {0} from \"{1}\"", columnList, table.TableName); // Execute the command to read the table. SqlCommand sqlCommand = new SqlCommand(selectCommand); sqlCommand.Connection = sqlConnection; SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); // IMPORTANT CONCEPT: The persistent database can store many deleted and archived rows. To minimize the // amount of memory needed to read these tables, the rows are reused as they are read when the row has been // deleted or archived. Active rows will be added to the data model and a new row will be created the next // time a row from that table is read. Row row = null; // Read each record from the table into the ADO database. while (sqlDataReader.Read()) { // Create a new record (unless the row is being reused because it was deleted or archived). if (row == null) { row = (Row)table.NewRow(); } // Read all records from the database, throw away the ones that are archived. This will keep the // identity columns in the ADO database from repeating any of the key elements in the SQL database. bool isArchived = false; bool isDeleted = false; for (int column = 0; column < sqlDataReader.FieldCount; column++) { string columnName = sqlDataReader.GetName(column); if (columnName == "IsArchived") { isArchived = (bool)sqlDataReader.GetValue(column); } if (columnName == "IsDeleted") { isDeleted = (bool)sqlDataReader.GetValue(column); } DataColumn destinationColumn = table.Columns[columnName]; if (destinationColumn != null) { row[destinationColumn] = sqlDataReader.GetValue(column); } } // IMPORTANT CONCEPT: The initial system-wide row version used by the in-memory data model to keep // track of the age of a record will be the maximum row version read from the persistent store // (including deleted and archived rows). maxRowVersion = row.RowVersion > maxRowVersion ? row.RowVersion : maxRowVersion; // Add active rows to the ADO table, reuse deleted and archived rows for the next record read. if (!isArchived && !isDeleted) { table.Rows.Add(row); row = null; } } // This is the end of reading a table. Close out the reader, accept the changes and move on to the next // table in the DataSet. sqlDataReader.Close(); table.AcceptChanges(); } } // This is the row version that will be used for all inserted, modified and deleted records. rowVersion.Set(maxRowVersion); // Once all the tables have been read, the constraints can be enforced again. This is where any Relational // Integrity problems will kick out. ServerDataModel.EnforceConstraints = true; // These masks are used to dynamically filter the data that is returned to the client. ServerDataModel.masks = new Hashtable(); ServerDataModel.maskLocks = new Hashtable(); ServerDataModel.maskLock = new TableLock("Mask.Master"); // Run through each of the users and create a mask for their pricing data. foreach (ServerDataModel.UserRow userRow in ServerDataModel.User.Rows) { // UserRow.UserName must match MarkThree.Quasar.Server.Environment.UserName which is always lower case. string maskName = string.Format("Mask.{0}", userRow.UserName.ToLower()); DataSet maskSet = new DataSet(maskName); ServerDataModel.masks[userRow.UserName.ToLower()] = maskSet; ServerDataModel.maskLocks[userRow.UserName.ToLower()] = new TableLock(maskName); // The mask has a single table with the security identifier in it. Any price that matches the security // identifier is returned to the client. DataTable priceTable = maskSet.Tables.Add("Price"); DataColumn securityIdColumn = priceTable.Columns.Add("SecurityId", typeof(int)); priceTable.PrimaryKey = new DataColumn[] { securityIdColumn }; } } catch (ConstraintException constraintException) { // Write out the exact location of the error. foreach (DataTable dataTable in ServerDataModel.Tables) { foreach (DataRow dataRow in dataTable.Rows) { if (dataRow.HasErrors) { EventLog.Error("Error in '{0}': {1}", dataRow.Table.TableName, dataRow.RowError); } } } // Rethrow the exception. throw constraintException; } catch (SqlException sqlException) { // Write out the exact location of the error. foreach (SqlError sqlError in sqlException.Errors) { EventLog.Error(sqlError.Message); } // Rethrow the exception. throw sqlException; } finally { // Release all of the write locks. foreach (TableLock tableLock in ServerDataModel.TableLocks) { if (tableLock.IsWriterLockHeld) { tableLock.ReleaseWriterLock(); } } // Make sure the sqlConnection is closed, even when an exception happens. sqlConnection.Close(); } }