Skip to content

Commit 8981f6d

Browse files
GiteaBotwolfogre
andauthored
Fix content holes in Actions task logs file (#25560) (#25566)
Backport #25560 by @wolfogre Fix #25451. Bugfixes: - When stopping the zombie or endless tasks, set `LogInStorage` to true after transferring the file to storage. It was missing, it could write to a nonexistent file in DBFS because `LogInStorage` was false. - Always update `ActionTask.Updated` when there's a new state reported by the runner, even if there's no change. This is to avoid the task being judged as a zombie task. Enhancement: - Support `Stat()` for DBFS file. - `WriteLogs` refuses to write if it could result in content holes. Co-authored-by: Jason Song <i@wolfogre.com>
1 parent b2b5c80 commit 8981f6d

File tree

6 files changed

+98
-5
lines changed

6 files changed

+98
-5
lines changed

models/actions/task.go

+9
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
346346
return err
347347
}
348348

349+
// UpdateTaskByState updates the task by the state.
350+
// It will always update the task if the state is not final, even there is no change.
351+
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
349352
func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionTask, error) {
350353
stepStates := map[int64]*runnerv1.StepState{}
351354
for _, v := range state.Steps {
@@ -386,6 +389,12 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT
386389
}, nil); err != nil {
387390
return nil, err
388391
}
392+
} else {
393+
// Force update ActionTask.Updated to avoid the task being judged as a zombie task
394+
task.Updated = timeutil.TimeStampNow()
395+
if err := UpdateTask(ctx, task, "updated"); err != nil {
396+
return nil, err
397+
}
389398
}
390399

391400
if err := task.LoadAttributes(ctx); err != nil {

models/dbfs/dbfile.go

+18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"errors"
99
"io"
10+
"io/fs"
1011
"os"
1112
"path/filepath"
1213
"strconv"
@@ -21,6 +22,7 @@ var defaultFileBlockSize int64 = 32 * 1024
2122
type File interface {
2223
io.ReadWriteCloser
2324
io.Seeker
25+
fs.File
2426
}
2527

2628
type file struct {
@@ -193,10 +195,26 @@ func (f *file) Close() error {
193195
return nil
194196
}
195197

198+
func (f *file) Stat() (os.FileInfo, error) {
199+
if f.metaID == 0 {
200+
return nil, os.ErrInvalid
201+
}
202+
203+
fileMeta, err := findFileMetaByID(f.ctx, f.metaID)
204+
if err != nil {
205+
return nil, err
206+
}
207+
return fileMeta, nil
208+
}
209+
196210
func timeToFileTimestamp(t time.Time) int64 {
197211
return t.UnixMicro()
198212
}
199213

214+
func fileTimestampToTime(timestamp int64) time.Time {
215+
return time.UnixMicro(timestamp)
216+
}
217+
200218
func (f *file) loadMetaByPath() (*dbfsMeta, error) {
201219
var fileMeta dbfsMeta
202220
if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil {

models/dbfs/dbfs.go

+29
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ package dbfs
55

66
import (
77
"context"
8+
"io/fs"
89
"os"
10+
"path"
11+
"time"
912

1013
"code.gitea.io/gitea/models/db"
1114
)
@@ -100,3 +103,29 @@ func Remove(ctx context.Context, name string) error {
100103
defer f.Close()
101104
return f.delete()
102105
}
106+
107+
var _ fs.FileInfo = (*dbfsMeta)(nil)
108+
109+
func (m *dbfsMeta) Name() string {
110+
return path.Base(m.FullPath)
111+
}
112+
113+
func (m *dbfsMeta) Size() int64 {
114+
return m.FileSize
115+
}
116+
117+
func (m *dbfsMeta) Mode() fs.FileMode {
118+
return os.ModePerm
119+
}
120+
121+
func (m *dbfsMeta) ModTime() time.Time {
122+
return fileTimestampToTime(m.ModifyTimestamp)
123+
}
124+
125+
func (m *dbfsMeta) IsDir() bool {
126+
return false
127+
}
128+
129+
func (m *dbfsMeta) Sys() any {
130+
return nil
131+
}

models/dbfs/dbfs_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ func TestDbfsBasic(t *testing.T) {
111111

112112
_, err = OpenFile(db.DefaultContext, "test2.txt", os.O_RDONLY)
113113
assert.Error(t, err)
114+
115+
// test stat
116+
f, err = OpenFile(db.DefaultContext, "test/test.txt", os.O_RDWR|os.O_CREATE)
117+
assert.NoError(t, err)
118+
stat, err := f.Stat()
119+
assert.NoError(t, err)
120+
assert.EqualValues(t, "test.txt", stat.Name())
121+
assert.EqualValues(t, 0, stat.Size())
122+
_, err = f.Write([]byte("0123456789"))
123+
assert.NoError(t, err)
124+
stat, err = f.Stat()
125+
assert.NoError(t, err)
126+
assert.EqualValues(t, 10, stat.Size())
114127
}
115128

116129
func TestDbfsReadWrite(t *testing.T) {

modules/actions/log.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,28 @@ const (
2929
)
3030

3131
func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) {
32+
flag := os.O_WRONLY
33+
if offset == 0 {
34+
// Create file only if offset is 0, or it could result in content holes if the file doesn't exist.
35+
flag |= os.O_CREATE
36+
}
3237
name := DBFSPrefix + filename
33-
f, err := dbfs.OpenFile(ctx, name, os.O_WRONLY|os.O_CREATE)
38+
f, err := dbfs.OpenFile(ctx, name, flag)
3439
if err != nil {
3540
return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err)
3641
}
3742
defer f.Close()
43+
44+
stat, err := f.Stat()
45+
if err != nil {
46+
return nil, fmt.Errorf("dbfs Stat %q: %w", name, err)
47+
}
48+
if stat.Size() < offset {
49+
// If the size is less than offset, refuse to write, or it could result in content holes.
50+
// However, if the size is greater than offset, we can still write to overwrite the content.
51+
return nil, fmt.Errorf("size of %q is less than offset", name)
52+
}
53+
3854
if _, err := f.Seek(offset, io.SeekStart); err != nil {
3955
return nil, fmt.Errorf("dbfs Seek %q: %w", name, err)
4056
}

services/actions/clear_tasks.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,20 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
5656
return nil
5757
}); err != nil {
5858
log.Warn("Cannot stop task %v: %v", task.ID, err)
59-
// go on
60-
} else if remove, err := actions.TransferLogs(ctx, task.LogFilename); err != nil {
59+
continue
60+
}
61+
62+
remove, err := actions.TransferLogs(ctx, task.LogFilename)
63+
if err != nil {
6164
log.Warn("Cannot transfer logs of task %v: %v", task.ID, err)
62-
} else {
63-
remove()
65+
continue
66+
}
67+
task.LogInStorage = true
68+
if err := actions_model.UpdateTask(ctx, task, "log_in_storage"); err != nil {
69+
log.Warn("Cannot update task %v: %v", task.ID, err)
70+
continue
6471
}
72+
remove()
6573
}
6674

6775
CreateCommitStatus(ctx, jobs...)

0 commit comments

Comments
 (0)