/// <summary> /// Checks if in configuration was supplied alternative path for table location. /// Returns true if intersection was found. /// Alternative Path equals to String.Empty - locate in Memory /// </summary> /// <param name="userTableName"></param> /// <param name="alternativePath"></param> /// <returns></returns> internal bool CheckAlternativeTableLocationsIntersections(string userTableName, out string alternativePath) { alternativePath = String.Empty; foreach (var pattern in Engine.Configuration.AlternativeTablesLocations) { //pattern.Key if (DbUserTables.PatternsIntersect(pattern.Key, userTableName)) { alternativePath = pattern.Value; return(true); } } return(false); }
/// <summary> /// Access synchronizer. /// All calls of the WRITE LOCATOR come over this function. /// </summary> /// <param name="transactionThreadId"></param> /// <param name="tablesNames"></param> /// <param name="calledBySynchronizer"></param> public void RegisterWriteTablesForTransaction(int transactionThreadId, List <string> tablesNames, bool calledBySynchronizer) { //in every transaction unit we got a list of reserved for WRITING tables //if we have in tablesNames one of the tables which is in this list we have to stop the thread with mre bool toWaitTillTransactionIsFinished = false; bool breakOuterLoop = false; TransactionUnit transactionUnit = null; bool deadlock = false; //When SyncTables is called //Console.WriteLine(DateTime.UtcNow.ToString("dd.MM.yyyy HH:mm:ss") + "> SYNC IN Thread: " + transactionThreadId); while (true) //loop till thread will get full access to write tables { toWaitTillTransactionIsFinished = false; breakOuterLoop = false; deadlock = false; //Console.WriteLine(DateTime.UtcNow.ToString("dd.MM.yyyy HH:mm:ss") + "> " + "Thread: " + transactionThreadId + "WHILE "); //only tables required for writing or read-commited will have to go over this fast bottleneck lock (_sync_dl) { _sync_transactions.EnterReadLock(); try { this._transactions.TryGetValue(transactionThreadId, out transactionUnit); if (transactionUnit == null) { return; //transaction doesn't exist anymore, gracefully goes out } if (!calledBySynchronizer) { //Here we are in case if Registrator is called by WriteTableCall, so we check intersections //Between reserved Write tables and current table using patterns intersections technique. //If they intersect we let the thread to proceed if (DbUserTables.TableNamesIntersect(transactionUnit.GetTransactionWriteTablesNames(), tablesNames)) { return; } } //iterating over all open transactions except self, finding out if desired tables are locked by other threads. foreach (var tu in this._transactions.Where(r => r.Value.TransactionThreadId != transactionThreadId)) { foreach (string tableName in tu.Value.GetTransactionWriteTablesNames()) { //if (tablesNames.Contains(tableName)) if (DbUserTables.TableNamesContains(tablesNames, tableName)) { // //++++++++++++++ here we can register all tables which are waiting for write lock release transactionUnit.AddTransactionWriteTablesAwaitingReservation(tablesNames); //++++++++++++++ if thread, who has locked this table has another table in a "waiting for reservation" blocked by this thread - it's a deadlock //if (transactionUnit.GetTransactionWriteTablesNames().Intersect(tu.Value.GetTransactionWriteTablesAwaitingReservation()).Count() > 0) if (DbUserTables.TableNamesIntersect(transactionUnit.GetTransactionWriteTablesNames(), tu.Value.GetTransactionWriteTablesAwaitingReservation())) { //we got deadlock, we will stop this transaction with an exception deadlock = true; } //other thread has reserved table for the transaction we have to wait toWaitTillTransactionIsFinished = true; breakOuterLoop = true; if (!deadlock) { //Console.WriteLine(DateTime.UtcNow.ToString("dd.MM.yyyy HH:mm:ss") + "> " + "Thread: " + transactionThreadId + " GATE IS CLOSED "); ThreadsGator.CloseGate(); //closing gate only if no deadlock situation //mreWriteTransactionLock.Reset(); //setting to signalled only in non-deadlock case } break; } } if (breakOuterLoop) { break; } } } catch (System.Exception ex) { this.UnregisterTransaction(transactionThreadId); throw DBreezeException.Throw(DBreezeException.eDBreezeExceptions.TRANSACTION_TABLE_WRITE_REGISTRATION_FAILED, ex); } finally { _sync_transactions.ExitReadLock(); } //if(true) this thread owns all table for modification lock if (!toWaitTillTransactionIsFinished) { //+++++++++++ Here we can clear all table names in the waiting reservation queue transactionUnit.ClearTransactionWriteTablesAwaitingReservation(tablesNames); //we have to reserve for our transaction all tables foreach (var tbn in tablesNames) { transactionUnit.AddTransactionWriteTable(tbn, null); } //Console.WriteLine(DateTime.UtcNow.ToString("dd.MM.yyyy HH:mm:ss") + "> SYNC OUT Thread: " + transactionThreadId + " Sync stop: " + transactionUnit.udtSyncStop.ToString("dd.MM.yyyy HH:mm:ss")); return; } }//end of lock if (deadlock) { this.UnregisterTransaction(transactionThreadId); throw DBreezeException.Throw(DBreezeException.eDBreezeExceptions.TRANSACTION_IN_DEADLOCK); } if (toWaitTillTransactionIsFinished) { //Console.WriteLine(DateTime.UtcNow.ToString("dd.MM.yyyy HH:mm:ss") + "> " + "Thread: " + transactionThreadId + " GATE IS PUT "); //blocking thread which requires busy tables for writing, till they are released //ThreadsGator.PutGateHere(20000); //every 20 second (or by Gate open we give a chance to re-try, for safety reasons of hanged threads, if programmer didn't dispose DBreeze process after the programm end) //#if ASYNC // await ThreadsGator.PutGateHere().ConfigureAwait(false); //#else // ThreadsGator.PutGateHere(); //#endif ThreadsGator.PutGateHere(); //mreWriteTransactionLock.WaitOne(); } }//eo while }
/// <summary> /// /// </summary> /// <param name="lockType"></param> /// <param name="tables"></param> /// <returns>false if thread grants access, false if thread is in a queue</returns> public bool AddSession(eTransactionTablesLockTypes lockType, string[] tables) { lock (lock_disposed) { if (disposed) { return(true); } } internSession iSession = null; bool ret = true; _sync.EnterWriteLock(); try { foreach (var ses in _acceptedSessions) { if (DbUserTables.TableNamesIntersect(ses.Value.tables.ToList(), tables.ToList())) { if (ses.Value.lockType == eTransactionTablesLockTypes.EXCLUSIVE || lockType == eTransactionTablesLockTypes.EXCLUSIVE) { //Lock ret = false; break; } } } if (!ret) { internSession xSes = null; foreach (var ses in _waitingSessionSequence) { if (ses == Environment.CurrentManagedThreadId) { break; } _waitingSessions.TryGetValue(ses, out xSes); if (DbUserTables.TableNamesIntersect(xSes.tables.ToList(), tables.ToList())) { if (xSes.lockType == eTransactionTablesLockTypes.EXCLUSIVE || lockType == eTransactionTablesLockTypes.EXCLUSIVE) { //Lock ret = false; break; } } } } if (_waitingSessions.TryGetValue(Environment.CurrentManagedThreadId, out iSession)) { //This session was in the waiting list once if (ret) { //We have to take away session from waiting list iSession.gator.Dispose(); iSession.gator = null; _waitingSessions.Remove(Environment.CurrentManagedThreadId); _waitingSessionSequence.Remove(Environment.CurrentManagedThreadId); } else { iSession.gator.CloseGate(); } } else { //Creating new session iSession = new internSession() { lockType = lockType, tables = tables }; if (!ret) { iSession.gator = new DbThreadsGator(false); _waitingSessions.Add(Environment.CurrentManagedThreadId, iSession); _waitingSessionSequence.Add(Environment.CurrentManagedThreadId); } } if (ret) { //Adding into accepted sessions _acceptedSessions.Add(Environment.CurrentManagedThreadId, iSession); } } finally { _sync.ExitWriteLock(); } if (!ret) { //putting gate iSession.gator.PutGateHere(); } return(ret); }
/// <summary> /// Returns table for READ, WRITE FUNC /// </summary> /// <param name="userTableName"></param> /// <returns></returns> internal LTrie GetTable(string userTableName) { string tableName = GetUserTableNameAsString(userTableName); //TODO pattern based mapping If table doesn't exist we create it with properties which could be supplied after db init as regex theme. //Schema protocol: 2 bytes - protocol version, other data //For protocol 1: first 8 bytes will be TheFileName, starting from db10000-dbN (0-N ulong). up to 10000 are reserved for dbreeze. //Table names are UTF-8 based, no limits ulong fileName = 0; OpenTable otl = null; _sync_openTablesHolder.EnterUpgradeableReadLock(); try { _openTablesHolder.TryGetValue(tableName, out otl); if (otl != null) { //Try to increase usage and return LTrie otl.Add(); return(otl.Trie); } //Probably table Exists in db but not in openTablesHolder _sync_openTablesHolder.EnterWriteLock(); try { //UpgradeableRead recheck _openTablesHolder.TryGetValue(tableName, out otl); if (otl != null) { //Try to increase usage and return LTrie otl.Add(); return(otl.Trie); } byte[] btTableName = GetUserTableNameAsByte(userTableName); //Trying to get fileName from cache fileName = this.cachedTableNames.GetFileName(tableName); // LTrieRow row = null; bool tableExists = false; if (fileName == 0) { LTrieRow row = LTrie.GetKey(btTableName, false, false); if (row.Exists) { tableExists = true; byte[] fullValue = row.GetFullValue(false); //Can be parsed different. First protocol version is 1 ushort schemeProtocol = fullValue.Substring(0, 2).To_UInt16_BigEndian(); switch (schemeProtocol) { case 1: fileName = fullValue.Substring(2, 8).To_UInt64_BigEndian(); break; default: throw DBreezeException.Throw(DBreezeException.eDBreezeExceptions.SCHEME_FILE_PROTOCOL_IS_UNKNOWN); } } else { tableExists = false; //Creating new table. //Checking table name validity //this will throw exception, if not valid DbUserTables.UserTableNameIsOk(userTableName); //Creating such table and renewing LastFileNumber counter //Adding to LastFileNumber LastFileNumber++; ////Deleting physical files related to the table, if they existed - normally they should not //DeleteAllReleatedTableFiles(Path.Combine(Engine.MainFolder, LastFileNumber.ToString())); byte[] lft = LastFileNumber.To_8_bytes_array_BigEndian(); //Writing this number to Schema file LTrie.Add(Encoding.UTF8.GetBytes(LastFileNumberKeyName), lft); //Creating table self and writing to Schema file LTrie.Add(btTableName, new byte[] { 0, 1 } //Protocol version 1 .Concat(lft)); //Number of the file //Committing both records LTrie.Commit(); fileName = LastFileNumber; this.cachedTableNames.Add(tableName, fileName); } } else { tableExists = true; } //Creating LTrie, adding it to _openTablesHolder //Seeting up Trie TableName, OTHER SETTINGS TrieSettings ts = new TrieSettings(); IStorage storage = null; ////Checking if default Flusg Disk behaviour was overriden //ts.DiskFlushBehaviour = Engine.Configuration.DiskFlushBehaviour; ////Checking if we have alternative DiskFlush behaviour //foreach (var pattern in Engine.Configuration.AlternativeDiskFlushBehaviour) //{ // //pattern.Key // if (DbUserTables.PatternsIntersect(pattern.Key, userTableName)) // { // ts.DiskFlushBehaviour = pattern.Value; // break; // } //} string alternativeTableLocation = String.Empty; if (CheckAlternativeTableLocationsIntersections(userTableName, out alternativeTableLocation)) { ts.StorageWasOverriden = true; if (alternativeTableLocation == String.Empty) { ts.AlternativeTableStorageType = DBreezeConfiguration.eStorage.MEMORY; storage = new StorageLayer(Path.Combine(Engine.MainFolder, fileName.ToString()), ts, Engine.Configuration); } else { ts.AlternativeTableStorageType = DBreezeConfiguration.eStorage.DISK; ts.AlternativeTableStorageFolder = alternativeTableLocation; DirectoryInfo diAlt = new DirectoryInfo(alternativeTableLocation); if (!diAlt.Exists) { diAlt.Create(); } if (!tableExists) { //Deleting physical files related to the table, if they existed - normally they should not DeleteAllReleatedTableFiles(Path.Combine(ts.AlternativeTableStorageFolder, LastFileNumber.ToString())); } storage = new StorageLayer(Path.Combine(ts.AlternativeTableStorageFolder, fileName.ToString()), ts, Engine.Configuration); } } else { if (!tableExists) { //Deleting physical files related to the table, if they existed - normally they should not DeleteAllReleatedTableFiles(Path.Combine(Engine.MainFolder, LastFileNumber.ToString())); } storage = new StorageLayer(Path.Combine(Engine.MainFolder, fileName.ToString()), ts, Engine.Configuration); } //storage = new StorageLayer(Path.Combine(Engine.MainFolder, fileName.ToString()), ts, Engine.Configuration); LTrie trie = new LTrie(storage); //Setting LTrie user table name trie.TableName = userTableName; //_openTablesHolder.Add(tableName, trie); //Automatically increased usage in OpenTable constructor _openTablesHolder.Add(tableName, new OpenTable(trie)); return(trie); } catch (System.Exception ex) { //CASCADE throw ex; } finally { _sync_openTablesHolder.ExitWriteLock(); } } catch (Exception ex) { throw DBreezeException.Throw(DBreezeException.eDBreezeExceptions.SCHEME_GET_TABLE_WRITE_FAILED, tableName, ex); } finally { _sync_openTablesHolder.ExitUpgradeableReadLock(); } }
/// <summary> /// Access synchronizer. /// All calls of the WRITE LOCATOR come over this function. /// </summary> /// <param name="transactionThreadId"></param> /// <param name="tablesNames"></param> public void RegisterWriteTablesForTransaction(int transactionThreadId, List <string> tablesNames, bool calledBySynchronizer) { //in every transaction unit we got a list of reserved for WRITING tables //if we have in tablesNames one of the tables which is in this list we have to stop the thread with mre bool toWaitTillTransactionIsFinished = false; bool breakOuterLoop = false; TransactionUnit transactionUnit = null; bool deadlock = false; Exception innerException = null; while (true) //loop till thread will get full access to write tables { toWaitTillTransactionIsFinished = false; breakOuterLoop = false; deadlock = false; //only tables required for writing or read-commited will have to go over this fast bottleneck lock (_sync_dl) { _sync_transactions.EnterReadLock(); try { this._transactions.TryGetValue(transactionThreadId, out transactionUnit); if (transactionUnit == null) { return; //transaction doesn't exist anymore, gracefully goes out } if (!calledBySynchronizer) { //Here we are in case if Registrator is called by WriteTableCall, so we check intersections //Between reserved Write tables and current table using patterns intersections technique. //If they intersect we let the thread to proceed if (DbUserTables.TableNamesIntersect(transactionUnit.GetTransactionWriteTablesNames(), tablesNames)) { return; } //Help for the programmer on the early stage to see problem with the possible deadlock if (_engine.Configuration.NotifyAhead_WhenWriteTablePossibleDeadlock) { if (transactionUnit.TransactionWriteTablesCount > 0) { throw new Exception("Put table \"" + tablesNames.FirstOrDefault() + "\" into tran.SynchronizeTables statement, because it will be modified"); } } } //iterating over all open transactions except self, finding out if desired tables are locked by other threads. foreach (var tu in this._transactions.Where(r => r.Value.TransactionThreadId != transactionThreadId)) { foreach (string tableName in tu.Value.GetTransactionWriteTablesNames()) { //if (tablesNames.Contains(tableName)) if (DbUserTables.TableNamesContains(tablesNames, tableName)) { // //++++++++++++++ here we can register all tables which are waiting for write lock release transactionUnit.AddTransactionWriteTablesAwaitingReservation(tablesNames); //++++++++++++++ if thread, who has locked this table has another table in a "waiting for reservation" blocked by this thread - it's a deadlock //if (transactionUnit.GetTransactionWriteTablesNames().Intersect(tu.Value.GetTransactionWriteTablesAwaitingReservation()).Count() > 0) if (DbUserTables.TableNamesIntersect(transactionUnit.GetTransactionWriteTablesNames(), tu.Value.GetTransactionWriteTablesAwaitingReservation())) { //we got deadlock, we will stop this transaction with an exception deadlock = true; } //other thread has reserved table for the transaction we have to wait toWaitTillTransactionIsFinished = true; breakOuterLoop = true; if (!deadlock) { ThreadsGator.CloseGate(); //closing gate only if no deadlock situation //mreWriteTransactionLock.Reset(); //setting to signalled only in non-deadlock case } break; } } if (breakOuterLoop) { break; } } } catch (System.Exception ex) { innerException = ex; //this.UnregisterTransaction(transactionThreadId); //throw DBreezeException.Throw(DBreezeException.eDBreezeExceptions.TRANSACTION_TABLE_WRITE_REGISTRATION_FAILED,ex); } finally { _sync_transactions.ExitReadLock(); } if (innerException != null) { this.UnregisterTransaction(transactionThreadId); throw DBreezeException.Throw(DBreezeException.eDBreezeExceptions.TRANSACTION_TABLE_WRITE_REGISTRATION_FAILED, innerException); } //if(true) this thread owns all table for modification lock if (!toWaitTillTransactionIsFinished) { //+++++++++++ Here we can clear all table names in the waiting reservation queue transactionUnit.ClearTransactionWriteTablesAwaitingReservation(tablesNames); //we have to reserve for our transaction all tables foreach (var tbn in tablesNames) { transactionUnit.AddTransactionWriteTable(tbn, null); } return; } }//end of lock if (deadlock) { this.UnregisterTransaction(transactionThreadId); throw DBreezeException.Throw(DBreezeException.eDBreezeExceptions.TRANSACTION_IN_DEADLOCK); } if (toWaitTillTransactionIsFinished) { //blocking thread which requires busy tables for writing, till they are released //ThreadsGator.PutGateHere(20000); //every 20 second (or by Gate open we give a chance to re-try, for safety reasons of hanged threads, if programmer didn't dispose DBreeze process after the programm end) ThreadsGator.PutGateHere(); //mreWriteTransactionLock.WaitOne(); } } }