private async Task Upgrade <K, V>(UUID seriesId, PersistentSeries <K, V> series) { if (!series.IsWriter) { try { _writeSeriesLocks.Add(seriesId, Pid); series.IsWriter = true; LogAcquireLock(seriesId, series.Version); return; } catch (ArgumentException) { } } while (true) { // wait if a writer from the same repo releases lock var released = await series.LockReleaseEvent.WaitAsync(1000); if (released) { try { // TODO TryAdd atomic method _writeSeriesLocks.Add(seriesId, Pid); series.IsWriter = true; LogAcquireLock(seriesId, series.Version); break; } catch (ArgumentException) { Trace.WriteLine("Could not upgrade after lock release, some one jumped ahead of us"); } } else { int pid; if (_writeSeriesLocks.TryGetValue(seriesId, out pid)) { try { Process.GetProcessById(pid & ((1 << 16) - 1)); Trace.TraceWarning("Tried to steal a lock but the owner process was alive."); } catch (ArgumentException) { // pid is not running anymore, steal lock Trace.TraceWarning($"Current process {Pid} has stolen a lock left by a dead process {pid}. If you see this often then dispose SeriesRepository properly before application exit."); _writeSeriesLocks[seriesId] = Pid; series.IsWriter = true; LogAcquireLock(seriesId, series.Version); break; } } } } }
protected virtual async Task <PersistentSeries <K, V> > GetSeries <K, V>(string seriesId, bool isWriter, bool allowBatches = false) { seriesId = seriesId.ToLowerInvariant().Trim(); var exSeriesId = GetExtendedSeriesId <K, V>(seriesId); var uuid = new UUID(exSeriesId.UUID); IAcceptCommand series; if (_openStreams.TryGetValue(uuid, out series)) { var ps = (PersistentSeries <K, V>)series; Interlocked.Increment(ref ps.RefCounter); if (isWriter && !ps.IsWriter) { var hasLockAcquired = await RequestWriteLock(uuid); if (!hasLockAcquired) { throw new SingleWriterException(); } // Upgrade to writer ps.IsWriter = true; } return(ps); } // NB We restrict only opening more than once, once opened a series object could be modified by many threads if (isWriter) { var hasLockAcquired = await RequestWriteLock(uuid); if (!hasLockAcquired) { throw new SingleWriterException(); } } // NB: Writers now have lock acquired. Other logic is the same for both writers and readers. Action <bool, bool> disposeCallback = (remove, downgrade) => { IAcceptCommand temp; if (remove) { // NB this callback is called from temp.Dispose(); var removed = _openStreams.TryRemove(uuid, out temp); Trace.Assert(removed); } if (downgrade) { Downgrade(uuid); } }; var ipom = base.GetSeries <K, V>(exSeriesId.Id, exSeriesId.Version, false); var pSeries = new PersistentSeries <K, V>(_appendLog, _pid, uuid, ipom, allowBatches, isWriter, disposeCallback); // NB this is done in consturctor: pSeries.RefCounter++; _openStreams[uuid] = pSeries; await RequestSubscribeSynced(uuid, pSeries.Version, exSeriesId.ToString()); return(pSeries); }
protected virtual async Task <PersistentSeries <K, V> > GetSeries <K, V>(string seriesId, bool isWriter, bool allowBatches = false) { seriesId = seriesId.ToLowerInvariant().Trim(); var exSeriesId = GetExtendedSeriesId <K, V>(seriesId); var uuid = new UUID(exSeriesId.UUID); IAcceptCommand series; if (_openSeries.TryGetValue(uuid, out series)) { var ps = (PersistentSeries <K, V>)series; Interlocked.Increment(ref ps.RefCounter); if (isWriter && !ps.IsWriter) { await Upgrade(uuid, ps); } return(ps); } Action <bool, bool> disposeCallback = (remove, downgrade) => { IAcceptCommand temp; if (remove) { // NB this callback is called from temp.Dispose(); var removed = _openSeries.TryRemove(uuid, out temp); Trace.Assert(removed); } if (downgrade) { Downgrade(uuid); } }; var ipom = base.GetSeries <K, V>(exSeriesId.Id, exSeriesId.Version, false); var pSeries = new PersistentSeries <K, V>(_appendLog, _pid, uuid, ipom, allowBatches, isWriter, disposeCallback); // NB this is done in consturctor: pSeries.RefCounter++; _openSeries[uuid] = pSeries; LogSubscribe(uuid, pSeries.Version, exSeriesId.ToString()); if (isWriter) { try { _writeSeriesLocks.Add(uuid, Pid); LogAcquireLock(uuid, pSeries.Version); } catch (ArgumentException) { // NB do not wait // await Upgrade(uuid, pSeries); throw new InvalidOperationException("Series is already opened for write. Only single writer is allowed."); } } else { // wait for flush if there is a live writer int pid; if (_writeSeriesLocks.TryGetValue(uuid, out pid)) { try { Process.GetProcessById(pid & ((1 << 16) - 1)); await pSeries.FlushEvent.WaitAsync(-1); } catch (ArgumentException) { // pid is not running anymore, steal lock Trace.TraceWarning($"Current process {Pid} has removed a lock left by a dead process {pid}. If you see this often then dispose SeriesRepository properly before application exit."); _writeSeriesLocks.Remove(uuid); LogReleaseLock(uuid); } } } return(pSeries); }