/// <summary> /// Attempt to get a lock for updating a persiston. Fails if another user has it locked already. /// Succeeds efficiently if this user already had it locked. /// </summary> /// <returns>success flag and error reason code</returns> public (bool, string) RequestLock(DatonKey datonKey, string version, string sessionKey) { //check if already locked by this server if (Locks.TryGetValue(datonKey, out LockInfo linfo)) { bool isLockedByMe = linfo.SessionKey == sessionKey; if (!isLockedByMe) { return(false, Constants.ERRCODE_LOCK); //someone else on this server has it locked } return(true, null); //this session already has it locked } //attempt lock on database using (var lockdb = GetLockConnection()) { if (RetroLock.Lock(lockdb, datonKey, version, sessionKey)) { //success Locks[datonKey] = new LockInfo { SessionKey = sessionKey, DatonKey = datonKey, OldVersion = version }; return(true, null); } //failed, so determine why (string verifiedVersion, _) = RetroLock.GetVersion(lockdb, datonKey); if (verifiedVersion != version) { return(false, Constants.ERRCODE_VERSION); //the most recent version is newer than the version known by the caller } return(false, Constants.ERRCODE_LOCK); //someone else on another server has it locked } }
/// <summary> /// Attempt to release a lock /// </summary> /// <returns>success flag and new version number (new version only provided if persiston was actually written)</returns> public (bool, string) ReleaseLock(DatonKey datonKey, string sessionKey) { //check if it is possible to unlock if (Locks.TryGetValue(datonKey, out LockInfo linfo)) { bool isLockedByMe = linfo.SessionKey == sessionKey; if (!isLockedByMe) { return(false, null); //can't unlock because someone else on this server has it locked } } else { return(false, null); //can't unlock because it is not locked by anyone on this server } //attempt unlock on database using (var lockdb = GetLockConnection()) { (bool unlockOK, string newVersion) = RetroLock.Unlock(lockdb, datonKey, sessionKey, linfo.WasWritten, ServerLifeNumber); Locks.TryRemove(datonKey, out _); //we forget in memory even if there was a database problem (should not happen) //propogate change (this is hooked up to code that pushes the change to subscribed sessions) if (linfo.WasWritten) { ChangePropogator?.Invoke(datonKey, newVersion); } return(unlockOK, newVersion); } }
/// <summary> /// Get the version of the persiston. This ALWAYS goes to the database so don't call this too much. /// The result is guaranteed and will assign a version if there was none recorded. /// </summary> public string GetVersion(DatonKey datonKey) { using (var lockdb = GetLockConnection()) { (string version, _) = RetroLock.GetVersion(lockdb, datonKey); return(version); } }
/// <summary> /// Update the database touched time for all current locks, so that other servers know that this server has not died. /// Then also obtain changes from other servers since last checked and return the new key and versions. /// Occasionally this also cleans out the lock table of old entries. /// </summary> public List <(DatonKey, string)> InterServerProcess() { var sinceUtc = LastCheckedOtherServers.AddSeconds(-5); //a little overlap LastCheckedOtherServers = DateTime.UtcNow; using (var lockdb = GetLockConnection()) { //touch records that are still locked foreach (var linfo in Locks.Values) { RetroLock.Touch(lockdb, linfo.DatonKey, linfo.SessionKey); } //cleanup every 12 hours if (DateTime.UtcNow > NextCleanup) { NextCleanup = DateTime.UtcNow.AddHours(12); RetroLock.Cleanup(lockdb); } //read from other servers return(RetroLock.GetRecentUpdatesByOtherServers(lockdb, sinceUtc, ServerLifeNumber)); } }