public void SetStreamBlock(StreamBlock newBlock) { // The new block is already rented from shared memory, // this object owns the reference on behalf of the current process. // Proxy usage is tracked by _rc field. lock (this) { if (AtomicCounter.GetIsDisposed(ref _rc)) { // because we inside the lock that protects from concurrent call to dispose, // the proxy could be disposed by now and the new block is not needed #pragma warning disable 618 newBlock.DisposeFree(); #pragma warning restore 618 } else { var previous = Block; Block = newBlock; if (previous.IsValid) { #pragma warning disable 618 previous.DisposeFree(); #pragma warning restore 618 } } } }
public void Dispose(bool disposing) { if (!_isSharedMemory) { DoDispose(); } else { lock (this) { DoDispose(); } } void DoDispose() { if (!disposing) { // Cache has a weak reference, so finalizer could start running while a handle is still in the cache // As if _rc was 1 and we called DecrementIfOne - if successful, no resurrect is possible // because Retain uses IncrementIfRetained. var current = Volatile.Read(ref _rc); var existing = Interlocked.CompareExchange(ref _rc, 0, current); if (existing != current) { // Resurrected while we tried to set rc to zero. // What if rc was wrong and not 1? At some point all new users will // dispose the proxy and it will be in the cache with // positive rc but without GC root, then it will be // collected and finalized and will return to this // place where we will try to set rc to 0 again. // TODO trace this condition, it indicates dropped proxies // From user code it could be possible only when manually using cursors // and forgetting to dispose them, so all blame is on users, but we should not fail. ThrowHelper.AssertFailFast(existing > 1, "existing > 1 when resurrected"); return; } } else { var remaining = AtomicCounter.Decrement(ref _rc); if (remaining > 1) { return; } if (AtomicCounter.DecrementIfOne(ref _rc) != 0) { return; } } ThrowHelper.AssertFailFast(_rc == 0, "_rc must be 0 to proceed with proxy disposal"); try { // remove self from cache _cache._blocks.TryRemove(_key, out var handle); if (handle.IsAllocated) { handle.Free(); } } finally { // If we are shutting down, e.g. unhandled exception in other threads // increase the chances we do release shared memory ref. #pragma warning disable 618 // ReSharper disable once InconsistentlySynchronizedField Block.DisposeFree(); #pragma warning restore 618 } // Do not pool finalized objects. // TODO (review) proxy does not have ref type fields, // probably could add to pool without thinking about GC/finalization order. // However, this case should be very rare (e.g. unhandled exception) // and we care about releasing RC of shared memory above all. if (disposing) { GC.SuppressFinalize(this); // ReSharper disable once InconsistentlySynchronizedField Block = default; _key = default; AtomicCounter.Dispose(ref _rc); _isSharedMemory = default; } } }