Run the merge queue on a state-driven coordinator
This commit is contained in:
+34
-156
@@ -459,20 +459,6 @@ func (s *Store) SetTitle(ctx context.Context, id, title, actor string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) SetMergingBy(ctx context.Context, id, login string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
projectID := ""
|
||||
err := tx.QueryRowContext(ctx, `UPDATE tasks SET merging_by = ? WHERE id = ? AND discard_state = '' AND finalizing = '' RETURNING project_id`, login, id).Scan(&projectID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: "task.merge_started", ProjectId: &projectID, TaskId: &id, Actor: login, Payload: map[string]any{"id": id}})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) MergingBy(ctx context.Context, id string) (string, error) {
|
||||
login := ""
|
||||
err := s.db.QueryRowContext(ctx, `SELECT merging_by FROM tasks WHERE id = ?`, id).Scan(&login)
|
||||
@@ -545,61 +531,6 @@ func (s *Store) setFinalizing(ctx context.Context, id, commit, notes string, unc
|
||||
})
|
||||
}
|
||||
|
||||
type FinalizingMerge struct {
|
||||
Task api.Task
|
||||
By string
|
||||
Commit string
|
||||
Notes string
|
||||
Unchanged bool
|
||||
}
|
||||
|
||||
func (s *Store) FinalizingMerges(ctx context.Context) ([]FinalizingMerge, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT `+taskColumns+`, finalizing, finalizing_commit, finalizing_notes, finalizing_unchanged FROM tasks WHERE status = ? AND finalizing != ''`, string(api.TaskStatusActive))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
merges := []FinalizingMerge{}
|
||||
for rows.Next() {
|
||||
f := FinalizingMerge{}
|
||||
f.Task, err = scanTask(trailingScanner{rows, []any{&f.By, &f.Commit, &f.Notes, &f.Unchanged}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merges = append(merges, f)
|
||||
}
|
||||
return merges, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) MergingTasks(ctx context.Context) ([]api.Task, error) {
|
||||
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE status = ? AND merging_by != ''`, string(api.TaskStatusActive))
|
||||
}
|
||||
|
||||
func (s *Store) QueuedMerges(ctx context.Context, projectID string) ([]api.Task, error) {
|
||||
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE project_id = ? AND status = ? AND merge_queued_by != '' ORDER BY merge_queued_at`,
|
||||
projectID, string(api.TaskStatusActive))
|
||||
}
|
||||
|
||||
func (s *Store) ProjectsWithQueuedMerges(ctx context.Context) ([]string, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT DISTINCT project_id FROM tasks WHERE status = ? AND merge_queued_by != ''`, string(api.TaskStatusActive))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
ids := []string{}
|
||||
for rows.Next() {
|
||||
id := ""
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) queryTasks(ctx context.Context, query string, args ...any) ([]api.Task, error) {
|
||||
rows, err := s.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
@@ -674,21 +605,6 @@ func (s *Store) ParentMergeBy(ctx context.Context, id string) (string, error) {
|
||||
return login, err
|
||||
}
|
||||
|
||||
func (s *Store) QueueMerge(ctx context.Context, id, login string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
projectID := ""
|
||||
err := tx.QueryRowContext(ctx, `UPDATE tasks SET merge_queued_by = ?, merge_queued_at = ? WHERE id = ? AND status = ? AND merge_queued_by = '' AND discard_state = '' RETURNING project_id`,
|
||||
login, now(), id, string(api.TaskStatusActive)).Scan(&projectID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: "task.merge_queued", ProjectId: &projectID, TaskId: &id, Actor: login, Payload: map[string]any{"id": id}})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) TakeQueuedMerge(ctx context.Context, id string) (string, error) {
|
||||
login := ""
|
||||
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
@@ -712,54 +628,6 @@ func (s *Store) TakeQueuedMerge(ctx context.Context, id string) (string, error)
|
||||
return login, err
|
||||
}
|
||||
|
||||
func (s *Store) ClaimQueuedMerge(ctx context.Context, id string) (string, error) {
|
||||
login := ""
|
||||
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
projectID := ""
|
||||
err := tx.QueryRowContext(ctx, `SELECT project_id, merge_queued_by FROM tasks WHERE id = ?`, id).Scan(&projectID, &login)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if login == "" {
|
||||
return nil
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `UPDATE tasks SET merge_queued_by = '', merge_queued_at = '' WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = emit(api.Event{Kind: "task.merge_unqueued", ProjectId: &projectID, TaskId: &id, Actor: "byre", Payload: map[string]any{"id": id}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := tx.ExecContext(ctx, `UPDATE tasks SET merging_by = ?, merge_after_turn = 1 WHERE id = ? AND discard_state = '' AND finalizing = ''`, login, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
login = ""
|
||||
return nil
|
||||
}
|
||||
return emit(api.Event{Kind: "task.merge_started", ProjectId: &projectID, TaskId: &id, Actor: login, Payload: map[string]any{"id": id}})
|
||||
})
|
||||
return login, err
|
||||
}
|
||||
|
||||
func (s *Store) MergeClaim(ctx context.Context, id string) (string, error) {
|
||||
login := ""
|
||||
err := s.db.QueryRowContext(ctx, `SELECT merging_by FROM tasks WHERE id = ? AND merge_after_turn = 1`, id).Scan(&login)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", nil
|
||||
}
|
||||
return login, err
|
||||
}
|
||||
|
||||
func (s *Store) ReserveMergeTurn(ctx context.Context, id, sessionID string) (bool, error) {
|
||||
reserved := false
|
||||
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
@@ -784,25 +652,6 @@ func (s *Store) ReserveMergeTurn(ctx context.Context, id, sessionID string) (boo
|
||||
return reserved, err
|
||||
}
|
||||
|
||||
func (s *Store) MergeClaims(ctx context.Context) ([]string, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT id FROM tasks WHERE status = ? AND merge_after_turn = 1`, string(api.TaskStatusActive))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
ids := []string{}
|
||||
for rows.Next() {
|
||||
id := ""
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, rows.Err()
|
||||
}
|
||||
|
||||
type MergeAfterReview struct {
|
||||
By string
|
||||
ReviewID string
|
||||
@@ -1335,21 +1184,50 @@ func (s *Store) Broadcast(ev api.Event) {
|
||||
}
|
||||
|
||||
func (s *Store) CreateSession(ctx context.Context, sess api.AgentSession, projectID string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
return insertSession(ctx, tx, emit, sess, projectID)
|
||||
})
|
||||
}
|
||||
|
||||
func insertSession(ctx context.Context, tx *sql.Tx, emit emitFunc, sess api.AgentSession, projectID string) error {
|
||||
forkedFrom := ""
|
||||
forkedAt := ""
|
||||
if sess.ForkedFrom != nil && sess.ForkedAt != nil {
|
||||
forkedFrom = *sess.ForkedFrom
|
||||
forkedAt = *sess.ForkedAt
|
||||
}
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO agent_sessions (id, task_id, owner, vendor, model, vendor_session_id, state, role, member, created_by, created_at, forked_from, forked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, sess.Id, nullable(sess.TaskId), sess.Owner, string(sess.Vendor), sess.Model, sess.VendorSessionId, string(sess.State), string(sess.Role), sess.Member, sess.CreatedBy, formatTime(sess.CreatedAt), forkedFrom, forkedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(sessionEvent("session.created", projectID, sess.TaskId, sess.Owner, sess.Id, sess.CreatedBy, ToPayload(sess)))
|
||||
}
|
||||
|
||||
func (s *Store) ReplaceSession(ctx context.Context, oldID string, sess api.AgentSession, projectID, actor string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO agent_sessions (id, task_id, owner, vendor, model, vendor_session_id, state, role, member, created_by, created_at, forked_from, forked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, sess.Id, nullable(sess.TaskId), sess.Owner, string(sess.Vendor), sess.Model, sess.VendorSessionId, string(sess.State), string(sess.Role), sess.Member, sess.CreatedBy, formatTime(sess.CreatedAt), forkedFrom, forkedAt)
|
||||
status, discard := "", ""
|
||||
err := tx.QueryRowContext(ctx, `SELECT status, discard_state FROM tasks WHERE id = ?`, sess.TaskId).Scan(&status, &discard)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(sessionEvent("session.created", projectID, sess.TaskId, sess.Owner, sess.Id, sess.CreatedBy, ToPayload(sess)))
|
||||
if status != string(api.TaskStatusActive) {
|
||||
return fmt.Errorf("%w: task %s is %s", ErrNotFound, sess.TaskId, status)
|
||||
}
|
||||
if discard != "" {
|
||||
return fmt.Errorf("%w: task %s is being discarded", ErrStateChanged, sess.TaskId)
|
||||
}
|
||||
running := api.AgentSessionStateRunning
|
||||
err = transitionSessionTx(ctx, tx, emit, oldID, &running, api.AgentSessionStateCompleted, actor, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return insertSession(ctx, tx, emit, sess, projectID)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user