refactor(coderd): collapse activityBumpWorkspace into a single query (#9652)

* Adds unit-style tests for activityBumpWorkspace
* Ports logic of activityBumpWorkspace to a SQL query
* Updates activityBumpWorkspace to call above query
This commit is contained in:
Cian Johnston
2023-09-14 09:09:51 +01:00
committed by GitHub
parent 38560dd922
commit 8b6e2862fd
9 changed files with 424 additions and 69 deletions

View File

@ -2,8 +2,6 @@ package coderd
import (
"context"
"database/sql"
"errors"
"time"
"github.com/google/uuid"
@ -11,7 +9,6 @@ import (
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
)
// activityBumpWorkspace automatically bumps the workspace's auto-off timer
@ -21,72 +18,7 @@ func activityBumpWorkspace(ctx context.Context, log slog.Logger, db database.Sto
// low priority operations fail first.
ctx, cancel := context.WithTimeout(ctx, time.Second*15)
defer cancel()
err := db.InTx(func(s database.Store) error {
build, err := s.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspaceID)
if errors.Is(err, sql.ErrNoRows) {
return nil
} else if err != nil {
return xerrors.Errorf("get latest workspace build: %w", err)
}
job, err := s.GetProvisionerJobByID(ctx, build.JobID)
if err != nil {
return xerrors.Errorf("get provisioner job: %w", err)
}
if build.Transition != database.WorkspaceTransitionStart || !job.CompletedAt.Valid {
return nil
}
if build.Deadline.IsZero() {
// Workspace shutdown is manual
return nil
}
workspace, err := s.GetWorkspaceByID(ctx, workspaceID)
if err != nil {
return xerrors.Errorf("get workspace: %w", err)
}
var (
// We bump by the original TTL to prevent counter-intuitive behavior
// as the TTL wraps. For example, if I set the TTL to 12 hours, sign off
// work at midnight, come back at 10am, I would want another full day
// of uptime. In the prior implementation, the workspace would enter
// a state of always expiring 1 hour in the future
bumpAmount = time.Duration(workspace.Ttl.Int64)
// DB writes are expensive so we only bump when 5% of the deadline
// has elapsed.
bumpEvery = bumpAmount / 20
timeSinceLastBump = bumpAmount - time.Until(build.Deadline)
)
if timeSinceLastBump < bumpEvery {
return nil
}
if bumpAmount == 0 {
return nil
}
newDeadline := dbtime.Now().Add(bumpAmount)
if !build.MaxDeadline.IsZero() && newDeadline.After(build.MaxDeadline) {
newDeadline = build.MaxDeadline
}
if err := s.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
ID: build.ID,
UpdatedAt: dbtime.Now(),
ProvisionerState: build.ProvisionerState,
Deadline: newDeadline,
MaxDeadline: build.MaxDeadline,
}); err != nil {
return xerrors.Errorf("update workspace build: %w", err)
}
return nil
}, nil)
if err != nil {
if err := db.ActivityBumpWorkspace(ctx, workspaceID); err != nil {
if !xerrors.Is(err, context.Canceled) && !database.IsQueryCanceledError(err) {
// Bump will fail if the context is canceled, but this is ok.
log.Error(ctx, "bump failed", slog.Error(err),