Skip to content

Commit

Permalink
internal: track starting of tasks
Browse files Browse the repository at this point in the history
Update exported TaskState to include a "started" boolean. Record the
task started state in the database and show a different icon in the UI.

Tracking started enables several improvements. This change improves
resuming of workflows to mark tasks that started without finishing as
incomplete. This will prevent workflows resuming from an unknown state,
where a task may not be safe to resume by default, such as sending a
public announcement.

Also, this change makes minor tweaks to the UI for task ordering,
putting the most recently updated tasks at the top of the list. It also
fixes a minor but confusing bug around retrying "approval" tasks.

Updates golang/go#53382

Change-Id: Icff1c0df541a6e11a7bb0ab55beef60cd18a074a
Reviewed-on: https://go-review.googlesource.com/c/build/+/417221
Reviewed-by: Dmitri Shuralyov <[email protected]>
Reviewed-by: Heschi Kreinick <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Run-TryBot: Jenny Rakoczy <[email protected]>
Auto-Submit: Jenny Rakoczy <[email protected]>
  • Loading branch information
toothrot committed Jul 13, 2022
1 parent 78590fb commit 876116a
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 55 deletions.
1 change: 1 addition & 0 deletions internal/relui/db/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 56 additions & 19 deletions internal/relui/db/workflows.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/relui/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (l *PGListener) TaskStateChanged(workflowID uuid.UUID, taskName string, sta
_, err := q.UpsertTask(ctx, db.UpsertTaskParams{
WorkflowID: workflowID,
Name: taskName,
Started: state.Started,
Finished: state.Finished,
Result: sql.NullString{String: string(result), Valid: len(result) > 0},
Error: sql.NullString{String: state.Error, Valid: state.Error != ""},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Copyright 2022 The Go Authors. All rights reserved.
-- Use of this source code is governed by a BSD-style
-- license that can be found in the LICENSE file.

ALTER TABLE tasks
DROP COLUMN started;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Copyright 2022 The Go Authors. All rights reserved.
-- Use of this source code is governed by a BSD-style
-- license that can be found in the LICENSE file.

ALTER TABLE tasks
ADD COLUMN started bool NOT NULL DEFAULT FALSE;
30 changes: 21 additions & 9 deletions internal/relui/queries/workflows.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,36 @@ VALUES ($1, $2, $3, $4, $5)
RETURNING *;

-- name: CreateTask :one
INSERT INTO tasks (workflow_id, name, finished, result, error, created_at, updated_at, approved_at, ready_for_approval)
INSERT INTO tasks (workflow_id, name, finished, result, error, created_at, updated_at, approved_at,
ready_for_approval)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING *;

-- name: UpsertTask :one
INSERT INTO tasks (workflow_id, name, finished, result, error, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
INSERT INTO tasks (workflow_id, name, started, finished, result, error, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (workflow_id, name) DO UPDATE
SET workflow_id = excluded.workflow_id,
name = excluded.name,
started = excluded.started,
finished = excluded.finished,
result = excluded.result,
error = excluded.error,
updated_at = excluded.updated_at
RETURNING *;

-- name: Tasks :many
SELECT tasks.*
WITH most_recent_logs AS (
SELECT workflow_id, task_name, MAX(updated_at) AS updated_at
FROM task_logs
GROUP BY workflow_id, task_name
)
SELECT tasks.*,
GREATEST(most_recent_logs.updated_at, tasks.updated_at)::timestamptz AS most_recent_update
FROM tasks
ORDER BY updated_at;
LEFT JOIN most_recent_logs ON tasks.workflow_id = most_recent_logs.workflow_id AND
tasks.name = most_recent_logs.task_name
ORDER BY most_recent_update DESC;

-- name: TasksForWorkflow :many
SELECT tasks.*
Expand Down Expand Up @@ -85,10 +95,12 @@ RETURNING *;

-- name: ResetTask :one
UPDATE tasks
SET finished = FALSE,
result = DEFAULT,
error = DEFAULT,
updated_at = $3
SET finished = FALSE,
started = FALSE,
approved_at = DEFAULT,
result = DEFAULT,
error = DEFAULT,
updated_at = $3
WHERE workflow_id = $1
AND name = $2
RETURNING *;
Expand Down
1 change: 1 addition & 0 deletions internal/relui/static/images/pending_grey_24dp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 8 additions & 4 deletions internal/relui/templates/task_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@
class="TaskList-itemStateIcon"
alt="finished"
src="{{baseLink "/static/images/check_circle_green_24dp.svg"}}" />
{{else if $task.Started}}
<img
class="TaskList-itemStateIcon"
alt="started"
src="{{baseLink "/static/images/pending_yellow_24dp.svg"}}" />
{{else}}
<img
class="TaskList-itemStateIcon"
alt="pending"
src="{{baseLink "/static/images/pending_yellow_24dp.svg"}}" />
src="{{baseLink "/static/images/pending_grey_24dp.svg"}}" />
{{end}}
</td>
<td class="TaskList-itemCol TaskList-itemName">
Expand All @@ -51,7 +56,7 @@
{{$task.CreatedAt.UTC.Format "Mon Jan _2 2006 15:04:05"}}
</td>
<td class="TaskList-itemCol TaskList-itemUpdated">
{{$task.UpdatedAt.UTC.Format "Mon Jan _2 2006 15:04:05"}}
{{$task.MostRecentUpdate.UTC.Format "Mon Jan _2 2006 15:04:05"}}
</td>
<td class="TaskList-itemCol TaskList-itemResult">
{{if $task.ApprovedAt.Valid}}
Expand All @@ -68,8 +73,7 @@
<input class="Button Button--small" name="task.reset" type="submit" value="Retry" onclick="return this.form.reportValidity() && confirm('This will retry the task and clear workflow errors.\n\nReady to proceed?')" />
</form>
</div>
{{end}}
{{if and (not $task.ApprovedAt.Valid) ($task.ReadyForApproval)}}
{{else if and (not $task.ApprovedAt.Valid) ($task.ReadyForApproval)}}
<div class="TaskList-approveTask">
<form action="{{baseLink (printf "/workflows/%s/tasks/%s/approve" $workflow.ID $task.Name)}}" method="post">
<input type="hidden" id="workflow.id" name="workflow.id" value="{{$workflow.ID}}" />
Expand Down
2 changes: 1 addition & 1 deletion internal/relui/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (s *Server) BaseLink(target string) string {

type workflowDetail struct {
Workflow db.Workflow
Tasks []db.Task
Tasks []db.TasksRow
// TaskLogs is a map of all logs for a db.Task, keyed on
// (db.Task).Name
TaskLogs map[string][]db.TaskLog
Expand Down
15 changes: 13 additions & 2 deletions internal/relui/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,25 @@ func (w *Worker) Resume(ctx context.Context, id uuid.UUID) error {
}
taskStates := make(map[string]*workflow.TaskState)
for _, t := range tasks {
taskStates[t.Name] = &workflow.TaskState{
ts := &workflow.TaskState{
Name: t.Name,
Finished: t.Finished,
Error: t.Error.String,
}
// The worker may have crashed, or been re-deployed. Any
// started but unfinished tasks are in an unknown state.
// Mark them as such for human review.
if t.Started && !t.Finished {
ts.Finished = true
ts.Error = "task interrupted before completion"
if t.Error.Valid {
ts.Error = fmt.Sprintf("%s. Previous error: %s", ts.Error, t.Error.String)
}
}
if t.Result.Valid {
taskStates[t.Name].SerializedResult = []byte(t.Result.String)
ts.SerializedResult = []byte(t.Result.String)
}
taskStates[t.Name] = ts
}
res, err := workflow.Resume(d, state, taskStates)
if err != nil {
Expand Down
Loading

0 comments on commit 876116a

Please sign in to comment.