mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
fix: don't abandon Action jobs waiting for approval (#11145)
On an open PR that is waiting for job approval, if jobs haven't been approved by the time the abandon timeout occurs they get marked as cancelled. This doesn't match the expectations of abandoned jobs in my opinion, which is that they were never able to be dispatched to a runner (no matching labels), but these jobs never got a chance. They should remain valid and blocked until approved. Discovered while testing #11125, but unrelated to the behaviour fixed there. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. *The decision if the pull request will be shown in the release notes is up to the mergers / release team.* The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11145 Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org> Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net> Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
This commit is contained in:
parent
13b7bdc741
commit
462ad7bb33
5 changed files with 119 additions and 8 deletions
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/container"
|
||||
"forgejo.org/modules/optional"
|
||||
"forgejo.org/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
|
@ -48,12 +49,13 @@ func (jobs ActionJobList) LoadAttributes(ctx context.Context, withRepo bool) err
|
|||
|
||||
type FindRunJobOptions struct {
|
||||
db.ListOptions
|
||||
RunID int64
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
RunID int64
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
CommitSHA string
|
||||
Statuses []Status
|
||||
UpdatedBefore timeutil.TimeStamp
|
||||
RunNeedsApproval optional.Option[bool]
|
||||
}
|
||||
|
||||
func (opts FindRunJobOptions) ToConds() builder.Cond {
|
||||
|
|
@ -76,5 +78,12 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
|
|||
if opts.UpdatedBefore > 0 {
|
||||
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
|
||||
}
|
||||
if opts.RunNeedsApproval.Has() {
|
||||
cond = cond.And(builder.Exists(builder.Select("id").From("action_run", "outer_run").
|
||||
Where(builder.Eq{
|
||||
"outer_run.need_approval": opts.RunNeedsApproval.Value(),
|
||||
"outer_run.id": builder.Expr("run_id"),
|
||||
})))
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
|
|
|||
25
services/actions/TestCancelAbandonedJobs/action_run.yml
Normal file
25
services/actions/TestCancelAbandonedJobs/action_run.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Supporting data for Case 600 - 603
|
||||
-
|
||||
id: 900
|
||||
title: "running"
|
||||
owner_id: 2
|
||||
repo_id: 63
|
||||
workflow_id: "running.yaml"
|
||||
index: 4
|
||||
trigger_event: "push"
|
||||
is_fork_pull_request: false
|
||||
trigger_user_id: 2
|
||||
need_approval: false
|
||||
|
||||
# Supporting data for Case 604
|
||||
-
|
||||
id: 901
|
||||
title: "running"
|
||||
owner_id: 2
|
||||
repo_id: 63
|
||||
workflow_id: "running.yaml"
|
||||
index: 5
|
||||
trigger_event: "push"
|
||||
is_fork_pull_request: false
|
||||
trigger_user_id: 2
|
||||
need_approval: true
|
||||
34
services/actions/TestCancelAbandonedJobs/action_run_job.yml
Normal file
34
services/actions/TestCancelAbandonedJobs/action_run_job.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Case 600 -- status waiting, too long, ready to be abandoned
|
||||
-
|
||||
id: 600
|
||||
run_id: 900
|
||||
status: 5 # waiting
|
||||
updated: 981228409
|
||||
|
||||
# Case 601 -- status blocked, too long, ready to be abandoned
|
||||
-
|
||||
id: 601
|
||||
run_id: 900
|
||||
status: 7 # blocked
|
||||
updated: 981228409
|
||||
|
||||
# Case 602 -- status blocked, *not* too long, not to be abandoned
|
||||
-
|
||||
id: 602
|
||||
run_id: 900
|
||||
status: 7 # blocked
|
||||
updated: 4105366009 # set to 2100-02-03 -- sorry to whoever fixes this 75 years from now
|
||||
|
||||
# Case 603 -- status running, not to be abandoned
|
||||
-
|
||||
id: 603
|
||||
run_id: 900
|
||||
status: 6 # running
|
||||
updated: 981228409
|
||||
|
||||
# Case 604 -- run needs approval (in action_run.yml), not to be abandoned
|
||||
-
|
||||
id: 604
|
||||
run_id: 901
|
||||
status: 5 # waiting
|
||||
updated: 981228409
|
||||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"forgejo.org/models/db"
|
||||
"forgejo.org/modules/actions"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/optional"
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/timeutil"
|
||||
)
|
||||
|
|
@ -75,8 +76,9 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
|
|||
// CancelAbandonedJobs cancels the jobs which have waiting status, but haven't been picked by a runner for a long time
|
||||
func CancelAbandonedJobs(ctx context.Context) error {
|
||||
jobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{
|
||||
Statuses: []actions_model.Status{actions_model.StatusWaiting, actions_model.StatusBlocked},
|
||||
UpdatedBefore: timeutil.TimeStamp(time.Now().Add(-setting.Actions.AbandonedJobTimeout).Unix()),
|
||||
Statuses: []actions_model.Status{actions_model.StatusWaiting, actions_model.StatusBlocked},
|
||||
UpdatedBefore: timeutil.TimeStamp(time.Now().Add(-setting.Actions.AbandonedJobTimeout).Unix()),
|
||||
RunNeedsApproval: optional.Some(false),
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("find abandoned tasks: %v", err)
|
||||
|
|
|
|||
41
services/actions/clear_tasks_test.go
Normal file
41
services/actions/clear_tasks_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
actions_model "forgejo.org/models/actions"
|
||||
"forgejo.org/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCancelAbandonedJobs(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("services/actions/TestCancelAbandonedJobs")()
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
require.NoError(t, CancelAbandonedJobs(t.Context()))
|
||||
|
||||
// status waiting, too long, ready to be abandoned
|
||||
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 600})
|
||||
assert.Equal(t, actions_model.StatusCancelled, job.Status)
|
||||
|
||||
// status blocked, too long, ready to be abandoned
|
||||
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 601})
|
||||
assert.Equal(t, actions_model.StatusCancelled, job.Status)
|
||||
|
||||
// status blocked, *not* too long, not to be abandoned
|
||||
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 602})
|
||||
assert.Equal(t, actions_model.StatusBlocked, job.Status)
|
||||
|
||||
// status running, not to be abandoned
|
||||
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 603})
|
||||
assert.Equal(t, actions_model.StatusRunning, job.Status)
|
||||
|
||||
// related run needs approval, not to be abandoned
|
||||
job = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 604})
|
||||
assert.Equal(t, actions_model.StatusWaiting, job.Status)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue