public InternalVaultExposition(VaultBase vault) { _vault = vault; PrevRecursiveExpositionsInternal = _internalLock.RecursiveReadCount; _lock.EnterReadLock(); _internalLock.EnterReadLock(); }
protected Element(VaultBase vault, String name, IEnumerable <Element> children) { Vault = vault.AssertNotNull(); Bind(Vault); // setting parent auto-adds the child to this collection // so it should be initialized as an empty one _children = new IndexedNodeCollection(this); (children ?? Enumerable.Empty <Element>()).ForEach(c => c.Parent = (Branch)this); Name = name; _metadata = new Metadata(this); }
public void Bind(VaultBase vault) { using (Vault.ExposeReadWrite()) { VerifyMutation(VPath); if (BoundVault == vault) { return; } if (BoundVault != null) { Unbind(); } BoundVault = vault; vault.Bind(this); } }
public VaultExposition(VaultBase vault, bool exposeReadOnly) { _vault = vault; _thisExpositionIsReadOnly = exposeReadOnly; PrevRecursiveExpositionsRO = _lock.RecursiveReadCount; PrevRecursiveExpositionsRW = _lock.RecursiveWriteCount; if (exposeReadOnly) { if (_lock.IsWriteLockHeld) { // do nothing - holding a write lock allows us to do any reading } else { _lock.EnterReadLock(); } } else { // letting read -> write upgrade to be an atomic operation would make us prone to deadlocks // consider the following example: // thread 1: [exposeRO -> [exposeRW (will wait for t2 to release exposeRO) => locked // thread 2: [exposeRO -> [exposeRW (needs to perform exposeRW in order to quit the using exposeRO block) => locked // so the implementation used below is not randomly coded up, but rather after some considerations _readLocksAbandoned = _lock.RecursiveReadCount; while (_lock.RecursiveReadCount != 0) { _lock.ExitReadLock(); } _lock.EnterWriteLock(); // yeah, I'm aware of the danger of such implementation quirk // for a split moment in-between read -> write transition, or, reverse during the write -> read transition // the thread totally loses control of the lock, and an alient thread might hijack the lock just at the moment // when read locks are abandoned but the write lock hasn't yet been acquired (or the reverse) // note. the code under danger in this situation // is the one that features split r/w expositions within an operation encapsulated in a r/o lock // an author could assume that when protecting the whole op with a r/o lock, he/she excludes the possibility // of someone writing into the vault for the entire duration of the whole op. // however, due to implementation details this is untrue, and there's a possibility for another thread to interpose // and change vault state in-between seemingly atomary operation. // to fix this trouble, it'd be cool to make every holder of a readonly lock temporarily lose their privileges // in favor of current thread which then freely upgrades to read/write. the following things need to be implemented then: // 1) when r/w request is issued, lock the lock, stop r/o threads from accessing the vault // note. ah, damn this won't work, since at that time alien threads might suddenly find out the vault changed // however after some thought we can conclude that dangerous scenarios made possible by this implementation quirk // are limited to the situation described above. and here's why: // * if the rule described in the n0te above is fulfilled, then no unexpected stuff might happen due to hijacking // and the only threat is leaving vault in inconsistent state // * the vault might be in inconsistent state only when it's r/w-exposed // * thus, if a thread is in read -> write transition, or, reverse, in the write -> read transition // it can at max have the r/o-exposition active, i.e. (if the code is designed correctly) at that moment // the vault is in consistent state, so it will be after the hijacker finishes (and before that the original // thread would be unable to acquire it's lock) // note. it would be nice if the stuff above worked, but it's untrue // counter-example: the "this[key]" accessor of some map exposes it as r/o, and checks whether the key/value is present // then when it makes sure that the r/w is necessary, the code exposes the map instance as r/w, and adds the new kvp. // error scenario: during the r/o -> r/w transition, an alien thread manages to hijack the lock and write some kvp // that has the same key as the one being added by an original thread. shortly after, the original crashes when attempting // to add the duplicate key to the map. // preventing such situations is a sole responsibility of DataVault users that should use a rule: // note. if code block A depends on the results of code block B, then the most enclosing r/w exposition for both A and B must be the same // examples: // 1) r/o > r/w > { a(); b(); } is fine // 2) r/o > { a(); r/w > b(); } fails the rule (see the map-related synchronization example above) // 3) r/o > { r/w > a(); r/w > b(); } fails the rule as well // some code of this codebase violates this principles in favor of robust Revision tracking // (e.g. Rename first exposes the vault for R/O, then checks the necessity of a modification, and only then uses R/W) // todo. fix the described issue through a careful code-review and replacing read acquisitions with upgradedable reads } }
public Branch(VaultBase vault, String name, IEnumerable <Element> children) : base(vault, name, children) { }
public Value(VaultBase vault, String name, Func <Stream> content) : base(vault, name, null) { _content = content ?? (() => null); }