/// <summary> /// Unions the other items into this. Does not include commutes! /// </summary> public void UnionWith(TransItems other) { Enlisted.UnionWith(other.Enlisted); HasChanges = HasChanges || other.HasChanges; ListMerge(ref Fx, other.Fx); ListMerge(ref SyncFx, other.SyncFx); }
/// <summary> /// Executes the commutes, returning through the out param the set of items that the /// commutes had accessed. /// Increases the current start stamp, and leaves the commuted items unmerged with the /// main transaction items! /// </summary> static void RunCommutes(out TransItems commutedItems) { var ctx = _context; var oldItems = ctx.Items; var commutes = oldItems.Commutes; Shield._blockCommute = true; try { while (true) { ctx.ReadTicket = VersionList.GetUntrackedReadStamp(); ctx.Items = commutedItems = new TransItems(); try { commutes.ForEach(comm => comm.Perform()); return; } catch (TransException) { commutedItems.Enlisted.Rollback(); commutedItems = null; } } } finally { ctx.Items = oldItems; Shield._blockCommute = false; } }
public static void Fire(TransItems items) { var theList = _whenCommitingSubs; if (theList == null) { return; } var fields = items.GetFields(); theList.Select(cs => (Action)(() => cs.Act(fields))).SafeRun(); }
/// <summary> /// Performs the commit check, returning the outcome. Prepares the write ticket in the context. /// The ticket is obtained before leaving the lock here, so that any thread which /// later conflicts with us will surely (in its retry) read the version we are now /// writing. The idea is to avoid senseless repetitions, retries after which a /// thread would again read old data and be doomed to fail again. /// It is critical that this ticket be marked when complete (i.e. Changes set to /// something non-null), because trimming will not go past it until this happens. /// </summary> static bool CommitCheck() { var ctx = _context; var items = ctx.Items; if (!items.HasChanges) { return(true); } TransItems commutedItems = null; var oldReadTicket = ctx.ReadTicket; bool commit = false; bool brokeInCommutes = items.Commutes != null && items.Commutes.Count > 0; if (CommitSubscriptionContext.PreCommit.Count > 0) { // if any commute would trigger a pre-commit check, this check could, if executed // in the commute sub-transaction, see newer values in fields which // were read (but not written to) by the main transaction. commutes are normally // very isolated to prevent this, but pre-commits we cannot isolate. // so, commutes trigger them now, and they cause the commutes to degenerate. CommitSubscriptionContext.PreCommit .Trigger(brokeInCommutes ? items.Enlisted.Where(HasChanges).Concat( items.Commutes.SelectMany(c => c.Affecting)) : items.Enlisted.Where(HasChanges)) .Run(); // in case a new commute sub was made brokeInCommutes = items.Commutes != null && items.Commutes.Count > 0; } try { repeatCommutes : if (brokeInCommutes) { RunCommutes(out commutedItems); #if DEBUG if (items.Enlisted.Overlaps(commutedItems.Enlisted)) { throw new InvalidOperationException("Invalid commute - conflict with transaction."); } #endif } var writeStamp = ctx.WriteStamp = new WriteStamp(ctx); lock (_checkLock) { try { if (brokeInCommutes) { if (!commutedItems.Enlisted.CanCommit(writeStamp)) { goto repeatCommutes; } } ctx.ReadTicket = oldReadTicket; brokeInCommutes = false; if (!items.Enlisted.CanCommit(writeStamp)) { return(false); } commit = true; } finally { if (!commit) { if (commutedItems != null) { commutedItems.Enlisted.Rollback(); } if (!brokeInCommutes) { items.Enlisted.Rollback(); } } else { VersionList.NewVersion(writeStamp, out ctx.WriteTicket); } } } return(true); } finally { ctx.ReadTicket = oldReadTicket; ctx.CommitCheckDone = true; // note that this changes the _localItems.Enlisted hashset to contain the // commute-enlists as well, regardless of the check outcome. if (commutedItems != null) { items.UnionWith(commutedItems); } } }