public async Task Initialize(long headLo, long headHi, long tailLo, long tailHi, SqlConnection conn) { if (linkState.Initialized) { return; //We ignore duplicate initialize messages. } LinkState initialized; using (var initTransaction = conn.BeginTransaction()) { initialized = linkState.Initialize( InboxTable.Left(sourceKey, destinationKey), headLo, headHi, InboxTable.Right(sourceKey, destinationKey), tailLo, tailHi); await initialized.HeadSession.CreateConstraint(conn, initTransaction).ConfigureAwait(false); await initialized.TailSession.CreateConstraint(conn, initTransaction).ConfigureAwait(false); await linkStateTable.Update(sourceKey, initialized, conn, initTransaction).ConfigureAwait(false); initTransaction.Commit(); } UpdateCachedLinkState(initialized); }
public async Task Uninstall(string sourceKey, SqlConnection conn, SqlTransaction trans) { var linkStateTable = new LinkStateTable(destinationKey); await linkStateTable.Drop(conn, trans).ConfigureAwait(false); await new InboxTable(InboxTable.Left(sourceKey, destinationKey)).Drop(conn, trans).ConfigureAwait(false); await new InboxTable(InboxTable.Right(sourceKey, destinationKey)).Drop(conn, trans).ConfigureAwait(false); }
public async Task <DeduplicationResult> Deduplicate(string messageId, long seq, SqlConnection conn, SqlTransaction trans) { var cachedLinkState = linkState; if (cachedLinkState.IsDuplicate(seq)) { return(DeduplicationResult.Duplicate); } if (cachedLinkState.IsFromNextEpoch(seq)) { var freshLinkState = await linkStateTable.Get(sourceKey, conn, trans).ConfigureAwait(false); UpdateCachedLinkState(freshLinkState); cachedLinkState = linkState; } if (cachedLinkState.IsFromNextEpoch(seq)) { throw new ProcessCurrentMessageLaterException("The message requires advancing epoch. Moving it to the back of the queue."); } if (cachedLinkState.IsDuplicate(seq)) { return(DeduplicationResult.Duplicate); } var tableName = cachedLinkState.GetTableName(seq); var table = new InboxTable(tableName); try { await table.Insert(messageId, seq, conn, trans); return(DeduplicationResult.OK); } catch (SqlException e) { if (e.Number == 547) //Constraint violation { var freshLinkState = await linkStateTable.Get(sourceKey, conn, trans).ConfigureAwait(false); UpdateCachedLinkState(freshLinkState); if (freshLinkState.IsDuplicate(seq)) { return(DeduplicationResult.Duplicate); } throw new ProcessCurrentMessageLaterException("Link state is stale. Processing current message later."); } if (e.Number == 2627) //Unique index violation { return(DeduplicationResult.Duplicate); } throw; } }
public async Task <LinkState> Advance(long nextEpoch, long nextLo, long nextHi, SqlConnection conn) { //Let's actually check if our values are correct. var queriedLinkState = await linkStateTable.Get(sourceKey, conn, null); if (!queriedLinkState.Initialized) { throw new ProcessCurrentMessageLaterException($"Link state for {sourceKey} is not yet initialized. Cannot advance the epoch."); } var tableName = queriedLinkState.TailSession.Table; var table = new InboxTable(tableName); log.Debug($"Closing inbox table {tableName}."); LinkState newLinkState; using (var closeTransaction = conn.BeginTransaction()) { //Ensure only one process can enter here var lockedLinkState = await linkStateTable.Lock(sourceKey, conn, closeTransaction); if (lockedLinkState.Epoch != queriedLinkState.Epoch) { throw new ProcessCurrentMessageLaterException($"Link state for {sourceKey} does not match previously read value. Cannot advance the epoch."); } if (nextEpoch < lockedLinkState.Epoch + 1) { //This is an old message that we already processed. return(lockedLinkState); } if (nextEpoch > lockedLinkState.Epoch + 1) { throw new ProcessCurrentMessageLaterException($"The link state is at epoch {lockedLinkState.Epoch} and is not ready to transition to epoch {nextEpoch}."); } if (await table.HasHoles(queriedLinkState.TailSession, conn, closeTransaction).ConfigureAwait(false)) { throw new ProcessCurrentMessageLaterException($"Inbox table {tableName} seems to have holes in the sequence. Cannot close yet."); } newLinkState = lockedLinkState.Advance(nextLo, nextHi); await newLinkState.HeadSession.CreateConstraint(conn, closeTransaction); //Here we have all holes plugged and no possibility of inserting new rows. We can truncate log.Debug($"Truncating table {tableName}."); await table.Truncate(conn, closeTransaction).ConfigureAwait(false); await lockedLinkState.TailSession.DropConstraint(conn, closeTransaction).ConfigureAwait(false); log.Debug($"Updating link state for {sourceKey} to {newLinkState}."); await linkStateTable.Update(sourceKey, newLinkState, conn, closeTransaction).ConfigureAwait(false); closeTransaction.Commit(); } UpdateCachedLinkState(newLinkState); return(newLinkState); }