diff --git a/src/Runtime/XSharp.SQLRdd/Classes/Connection.prg b/src/Runtime/XSharp.SQLRdd/Classes/Connection.prg index 9dc87680fd..0cfe9289ad 100644 --- a/src/Runtime/XSharp.SQLRdd/Classes/Connection.prg +++ b/src/Runtime/XSharp.SQLRdd/Classes/Connection.prg @@ -1045,33 +1045,50 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable _lockTimer:Enabled := true return + /// + /// The timer runs on a thread pool thread, while the application uses the connection on its own + /// thread. An ADO.NET connection must not be used by two threads at once: a refresh that came + /// during a longer statement (e.g. CREATE INDEX on a big table) made one of them fail with + /// "requires an open and available Connection". So the refresh uses a connection of its own. + /// method LockTimerElapsedEvent(sender as object, e as System.Timers.ElapsedEventArgs) as void - var parameterName1 := SELF:Provider:ParameterPrefix + "p1" - - // Refresh my Locks - using var cmdRefresh := SqlDbCommand{"RefreshLockTable", SELF, false} - if !cmdRefresh:Connection:IsOpen - cmdRefresh:Connection:ForceOpen() - endif - var updateStatement := SELF:Provider:UpdateStatement:Replace(SqlDbProvider.TableNameMacro, LockTableName) - updateStatement := updateStatement:Replace(SqlDbProvider.ColumnsMacro, "lockdatetime = " + SELF:Provider:CurrentDateTime) - updateStatement := updateStatement:Replace(SqlDbProvider.WhereMacro, "connectionid = " + parameterName1) - cmdRefresh:Parameters := List{} - cmdRefresh:Parameters:Add(SqlDbParameter{parameterName1, SELF:ConnectionId:ToString()}) - cmdRefresh:CommandText := updateStatement - cmdRefresh:ExecuteNonQuery() - - // Clear all old locks - using var cmdClear := SqlDbCommand{"ClearLockTable", SELF, false} - if !cmdClear:Connection:IsOpen - cmdClear:Connection:ForceOpen() + if !SELF:UseLocks + // nothing is written to the lock table + return endif - cmdClear:CommandText := SELF:Provider:DeleteStatement:Replace(SqlDbProvider.TableNameMacro, LockTableName):Replace(SqlDbProvider.WhereMacro, "LockDateTime < " + parameterName1) - cmdClear:Parameters := List{} - cmdClear:Parameters:Add(SqlDbParameter{parameterName1, DateTime.Now.AddSeconds(-SELF:LockStaleThreshold)}) - cmdClear:ExecuteNonQuery() + try + var parameterName1 := SELF:Provider:ParameterPrefix + "p1" + using var oConn := SELF:Provider:CreateConnection() + oConn:ConnectionString := SELF:ConnectionString + oConn:Open() + + // Refresh my Locks + using var cmdRefresh := oConn:CreateCommand() + var updateStatement := SELF:Provider:UpdateStatement:Replace(SqlDbProvider.TableNameMacro, LockTableName) + updateStatement := updateStatement:Replace(SqlDbProvider.ColumnsMacro, "lockdatetime = " + SELF:Provider:CurrentDateTime) + updateStatement := updateStatement:Replace(SqlDbProvider.WhereMacro, "connectionid = " + parameterName1) + cmdRefresh:CommandText := updateStatement + SELF:_AddParameter(cmdRefresh, parameterName1, SELF:ConnectionId:ToString()) + cmdRefresh:ExecuteNonQuery() + + // Clear all old locks + using var cmdClear := oConn:CreateCommand() + cmdClear:CommandText := SELF:Provider:DeleteStatement:Replace(SqlDbProvider.TableNameMacro, LockTableName):Replace(SqlDbProvider.WhereMacro, "LockDateTime < " + parameterName1) + SELF:_AddParameter(cmdClear, parameterName1, DateTime.Now.AddSeconds(-SELF:LockStaleThreshold)) + cmdClear:ExecuteNonQuery() + catch ex as Exception + // a failed refresh must not take the application down; the next tick tries again + System.Diagnostics.Debug.WriteLine("SQLRDD lock table refresh: " + ex:Message) + end try return + private method _AddParameter(oCmd as DbCommand, cName as string, oValue as object) as void + var oParam := SELF:Provider:CreateParameter() + oParam:ParameterName := cName + oParam:Value := oValue + oCmd:Parameters:Add(oParam) + return + #endregion end class