Skip to content

Commit 8871aed

Browse files
authored
[wsman-mk2] Garbage collection for mk2 workspaces (#17021)
1 parent a28bde6 commit 8871aed

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License.AGPL.txt in the project root for license information.
4+
5+
package controller
6+
7+
import (
8+
"context"
9+
"errors"
10+
"fmt"
11+
"io/fs"
12+
"os"
13+
"path/filepath"
14+
"strings"
15+
"time"
16+
17+
"github.com/gitpod-io/gitpod/common-go/log"
18+
"github.com/gitpod-io/gitpod/common-go/tracing"
19+
"github.com/opentracing/opentracing-go"
20+
)
21+
22+
const minContentGCAge = 1 * time.Hour
23+
24+
type Housekeeping struct {
25+
Location string
26+
Interval time.Duration
27+
}
28+
29+
func NewHousekeeping(location string, interval time.Duration) *Housekeeping {
30+
return &Housekeeping{
31+
Location: location,
32+
Interval: interval,
33+
}
34+
}
35+
36+
func (h *Housekeeping) Start(ctx context.Context) {
37+
span, ctx := opentracing.StartSpanFromContext(ctx, "Housekeeping.Start")
38+
defer tracing.FinishSpan(span, nil)
39+
log.WithField("interval", h.Interval.String()).Debug("started workspace housekeeping")
40+
41+
ticker := time.NewTicker(h.Interval)
42+
defer ticker.Stop()
43+
44+
run := true
45+
for run {
46+
var errs []error
47+
select {
48+
case <-ticker.C:
49+
errs = h.doHousekeeping(ctx)
50+
case <-ctx.Done():
51+
run = false
52+
}
53+
54+
for _, err := range errs {
55+
log.WithError(err).Error("error during housekeeping")
56+
}
57+
}
58+
59+
span.Finish()
60+
log.Debug("stopping workspace housekeeping")
61+
}
62+
63+
func (h *Housekeeping) doHousekeeping(ctx context.Context) (errs []error) {
64+
span, _ := opentracing.StartSpanFromContext(ctx, "doHousekeeping")
65+
defer func() {
66+
msgs := make([]string, len(errs))
67+
for i, err := range errs {
68+
msgs[i] = err.Error()
69+
}
70+
71+
var err error
72+
if len(msgs) > 0 {
73+
err = fmt.Errorf(strings.Join(msgs, ". "))
74+
}
75+
tracing.FinishSpan(span, &err)
76+
}()
77+
78+
errs = make([]error, 0)
79+
80+
// Find workspace directories which are left over.
81+
files, err := os.ReadDir(h.Location)
82+
if err != nil {
83+
return []error{fmt.Errorf("cannot list existing workspaces content directory: %w", err)}
84+
}
85+
86+
for _, f := range files {
87+
if !f.IsDir() {
88+
continue
89+
}
90+
91+
// If this is the -daemon directory, make sure we assume the correct state file name
92+
name := f.Name()
93+
name = strings.TrimSuffix(name, string(filepath.Separator))
94+
name = strings.TrimSuffix(name, "-daemon")
95+
96+
if _, err := os.Stat(filepath.Join(h.Location, fmt.Sprintf("%s.workspace.json", name))); !errors.Is(err, fs.ErrNotExist) {
97+
continue
98+
}
99+
100+
// We have found a workspace content directory without a workspace state file, which means we don't manage this folder.
101+
// Within the working area/location of a session store we must be the only one who creates directories, because we want to
102+
// make sure we don't leak files over time.
103+
104+
// For good measure we wait a while before deleting that directory.
105+
nfo, err := f.Info()
106+
if err != nil {
107+
log.WithError(err).Warn("Found workspace content directory without a corresponding state file, but could not retrieve its info")
108+
errs = append(errs, err)
109+
continue
110+
}
111+
if time.Since(nfo.ModTime()) < minContentGCAge {
112+
continue
113+
}
114+
115+
err = os.RemoveAll(filepath.Join(h.Location, f.Name()))
116+
if err != nil {
117+
log.WithError(err).Warn("Found workspace content directory without a corresponding state file, but could not delete the content directory")
118+
errs = append(errs, err)
119+
continue
120+
}
121+
122+
log.WithField("directory", f.Name()).Info("deleted workspace content directory without corresponding state file")
123+
}
124+
125+
return errs
126+
}

components/ws-daemon/pkg/daemon/daemon.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ func NewDaemon(config Config) (*Daemon, error) {
221221
if err != nil {
222222
return nil, err
223223
}
224+
225+
housekeeping := controller.NewHousekeeping(contentCfg.WorkingArea, 5*time.Minute)
226+
go housekeeping.Start(context.Background())
224227
}
225228

226229
dsptch, err := dispatch.NewDispatch(containerRuntime, clientset, config.Runtime.KubernetesNamespace, nodename, listener...)

0 commit comments

Comments
 (0)