-
Notifications
You must be signed in to change notification settings - Fork 595
/
activitybump.go
101 lines (86 loc) · 2.84 KB
/
activitybump.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package coderd
import (
"context"
"database/sql"
"errors"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/coder/coderd/database"
)
// activityBumpWorkspace automatically bumps the workspace's auto-off timer
// if it is set to expire soon.
func activityBumpWorkspace(ctx context.Context, log slog.Logger, db database.Store, workspaceID uuid.UUID) {
// We set a short timeout so if the app is under load, these
// 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 := database.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: database.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 !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),
slog.F("workspace_id", workspaceID),
)
}
return
}
log.Debug(ctx, "bumped deadline from activity",
slog.F("workspace_id", workspaceID),
)
}