watchtower: update DeleteCommittedUpdate to delete all
This commit updates the DeleteCommittedUpdate DB method to delete all of a given session's committed updates instead of just one at a time. The reason for this is that in an upcoming commit, we will introduce a "Terminal" session state - once we have deleted a committed update for a session it should be considered "Terminal" and there is never a case where we would only want to delete one committed update and not the rest. So we want these two actions (deleting committed updates of a session and setting it's status to terminal) to be atomic.
This commit is contained in:
4 files changed
+55
-51
No files matched your search
@@ -136,9 +136,9 @@ type DB interface {
|
|||||||
// space.
|
// space.
|
||||||
GetDBQueue(namespace []byte) wtdb.Queue[*wtdb.BackupID]
|
GetDBQueue(namespace []byte) wtdb.Queue[*wtdb.BackupID]
|
||||||
|
|
||||||
// DeleteCommittedUpdate deletes the committed update belonging to the
|
// DeleteCommittedUpdates deletes all the committed updates belonging to
|
||||||
// given session and with the given sequence number from the db.
|
// the given session from the db.
|
||||||
DeleteCommittedUpdate(id *wtdb.SessionID, seqNum uint16) error
|
DeleteCommittedUpdates(id *wtdb.SessionID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthDialer connects to a remote node using an authenticated transport, such
|
// AuthDialer connects to a remote node using an authenticated transport, such
|
||||||
|
|||||||
@@ -211,14 +211,13 @@ func (q *sessionQueue) Stop(final bool) error {
|
|||||||
update.BackupID, err)
|
update.BackupID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = q.cfg.DB.DeleteCommittedUpdate(
|
if final {
|
||||||
q.ID(), update.SeqNum,
|
err = q.cfg.DB.DeleteCommittedUpdates(q.ID())
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("could not delete committed "+
|
log.Errorf("could not delete committed "+
|
||||||
"update %d for session %s",
|
"updates for session %s", q.ID())
|
||||||
update.SeqNum, q.ID())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -196,9 +196,9 @@ var (
|
|||||||
// session has updates for channels that are still open.
|
// session has updates for channels that are still open.
|
||||||
errSessionHasOpenChannels = errors.New("session has open channels")
|
errSessionHasOpenChannels = errors.New("session has open channels")
|
||||||
|
|
||||||
// errSessionHasUnackedUpdates is an error used to indicate that a
|
// ErrSessionHasUnackedUpdates is an error used to indicate that a
|
||||||
// session has un-acked updates.
|
// session has un-acked updates.
|
||||||
errSessionHasUnackedUpdates = errors.New("session has un-acked updates")
|
ErrSessionHasUnackedUpdates = errors.New("session has un-acked updates")
|
||||||
|
|
||||||
// errChannelHasMoreSessions is an error used to indicate that a channel
|
// errChannelHasMoreSessions is an error used to indicate that a channel
|
||||||
// has updates in other non-closed sessions.
|
// has updates in other non-closed sessions.
|
||||||
@@ -1798,22 +1798,21 @@ func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
|
|||||||
return false, ErrSessionNotFound
|
return false, ErrSessionNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since the DeleteCommittedUpdates method deletes the cSessionCommits
|
||||||
|
// bucket in one go, it is possible for the session to be closable even
|
||||||
|
// if this bucket no longer exists.
|
||||||
commitsBkt := sessBkt.NestedReadBucket(cSessionCommits)
|
commitsBkt := sessBkt.NestedReadBucket(cSessionCommits)
|
||||||
if commitsBkt == nil {
|
if commitsBkt != nil {
|
||||||
// If the session has no cSessionCommits bucket then we can be
|
// If the session has any un-acked updates, then it is not yet
|
||||||
// sure that no updates have ever been committed to the session
|
// closable.
|
||||||
// and so it is not yet exhausted.
|
err := commitsBkt.ForEach(func(_, _ []byte) error {
|
||||||
return false, nil
|
return ErrSessionHasUnackedUpdates
|
||||||
}
|
})
|
||||||
|
if errors.Is(err, ErrSessionHasUnackedUpdates) {
|
||||||
// If the session has any un-acked updates, then it is not yet closable.
|
return false, nil
|
||||||
err := commitsBkt.ForEach(func(_, _ []byte) error {
|
} else if err != nil {
|
||||||
return errSessionHasUnackedUpdates
|
return false, err
|
||||||
})
|
}
|
||||||
if errors.Is(err, errSessionHasUnackedUpdates) {
|
|
||||||
return false, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := getClientSessionBody(sessionsBkt, id[:])
|
session, err := getClientSessionBody(sessionsBkt, id[:])
|
||||||
@@ -2215,9 +2214,9 @@ func (c *ClientDB) GetDBQueue(namespace []byte) Queue[*BackupID] {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCommittedUpdate deletes the committed update with the given sequence
|
// DeleteCommittedUpdates deletes all the committed updates for the given
|
||||||
// number from the given session.
|
// session.
|
||||||
func (c *ClientDB) DeleteCommittedUpdate(id *SessionID, seqNum uint16) error {
|
func (c *ClientDB) DeleteCommittedUpdates(id *SessionID) error {
|
||||||
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
|
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
|
||||||
sessions := tx.ReadWriteBucket(cSessionBkt)
|
sessions := tx.ReadWriteBucket(cSessionBkt)
|
||||||
if sessions == nil {
|
if sessions == nil {
|
||||||
@@ -2231,23 +2230,40 @@ func (c *ClientDB) DeleteCommittedUpdate(id *SessionID, seqNum uint16) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the commits sub-bucket doesn't exist, there can't possibly
|
// If the commits sub-bucket doesn't exist, there can't possibly
|
||||||
// be a corresponding update to remove.
|
// be corresponding updates to remove.
|
||||||
sessionCommits := sessionBkt.NestedReadWriteBucket(
|
sessionCommits := sessionBkt.NestedReadWriteBucket(
|
||||||
cSessionCommits,
|
cSessionCommits,
|
||||||
)
|
)
|
||||||
if sessionCommits == nil {
|
if sessionCommits == nil {
|
||||||
return ErrCommittedUpdateNotFound
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var seqNumBuf [2]byte
|
// errFoundUpdates is an error we will use to exit early from
|
||||||
byteOrder.PutUint16(seqNumBuf[:], seqNum)
|
// the ForEach loop. The return of this error means that at
|
||||||
|
// least one committed update exists.
|
||||||
|
var errFoundUpdates = fmt.Errorf("found committed updates")
|
||||||
|
err := sessionCommits.ForEach(func(k, v []byte) error {
|
||||||
|
return errFoundUpdates
|
||||||
|
})
|
||||||
|
switch {
|
||||||
|
// If the errFoundUpdates signal error was returned then there
|
||||||
|
// are some updates that need to be deleted.
|
||||||
|
case errors.Is(err, errFoundUpdates):
|
||||||
|
|
||||||
if sessionCommits.Get(seqNumBuf[:]) == nil {
|
// If no error is returned then the ForEach call back was never
|
||||||
return ErrCommittedUpdateNotFound
|
// entered meaning that there are no un-acked committed updates.
|
||||||
|
// So we can exit now as there is nothing left to do.
|
||||||
|
case err == nil:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// If an expected error is returned, return that error.
|
||||||
|
default:
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the corresponding committed update.
|
// Delete all the committed updates in one go by deleting the
|
||||||
return sessionCommits.Delete(seqNumBuf[:])
|
// session commits bucket.
|
||||||
|
return sessionBkt.DeleteNestedBucket(cSessionCommits)
|
||||||
}, func() {})
|
}, func() {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,12 +194,12 @@ func (h *clientDBHarness) ackUpdate(id *wtdb.SessionID, seqNum uint16,
|
|||||||
require.ErrorIs(h.t, err, expErr)
|
require.ErrorIs(h.t, err, expErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *clientDBHarness) deleteCommittedUpdate(id *wtdb.SessionID,
|
func (h *clientDBHarness) deleteCommittedUpdates(id *wtdb.SessionID,
|
||||||
seqNum uint16, expErr error) {
|
expErr error) {
|
||||||
|
|
||||||
h.t.Helper()
|
h.t.Helper()
|
||||||
|
|
||||||
err := h.db.DeleteCommittedUpdate(id, seqNum)
|
err := h.db.DeleteCommittedUpdates(id)
|
||||||
require.ErrorIs(h.t, err, expErr)
|
require.ErrorIs(h.t, err, expErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,18 +660,7 @@ func testCommitUpdate(h *clientDBHarness) {
|
|||||||
|
|
||||||
// We will now also test that the DeleteCommittedUpdates method also
|
// We will now also test that the DeleteCommittedUpdates method also
|
||||||
// works.
|
// works.
|
||||||
// First, try to delete a committed update that does not exist.
|
h.deleteCommittedUpdates(&session.ID, nil)
|
||||||
h.deleteCommittedUpdate(
|
|
||||||
&session.ID, update4.SeqNum, wtdb.ErrCommittedUpdateNotFound,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Now delete an existing committed update and ensure that it succeeds.
|
|
||||||
h.deleteCommittedUpdate(&session.ID, update1.SeqNum, nil)
|
|
||||||
h.assertUpdates(session.ID, []wtdb.CommittedUpdate{
|
|
||||||
*update2,
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
h.deleteCommittedUpdate(&session.ID, update2.SeqNum, nil)
|
|
||||||
h.assertUpdates(session.ID, []wtdb.CommittedUpdate{}, nil)
|
h.assertUpdates(session.ID, []wtdb.CommittedUpdate{}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user