Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-daemon/pkg/controller/housekeeping.go
2500 views
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("%s", 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
}
127
128