From f7ade6de7c1c4991c650d6b96e6407053e6bdae7 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Fri, 2 Dec 2022 11:15:36 +0800
Subject: [PATCH] Fix generate index failure possibility on postgres (#21998)

@wxiaoguang Please review

Co-authored-by: silverwind <me@silverwind.io>
---
 models/db/index.go                     | 20 ++++++++++++++++++++
 models/git/commit_status.go            | 18 ++++++++++++++++++
 tests/integration/repo_commits_test.go |  4 ++--
 3 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/models/db/index.go b/models/db/index.go
index ca664c9cf1..f840a62c89 100644
--- a/models/db/index.go
+++ b/models/db/index.go
@@ -7,6 +7,9 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strconv"
+
+	"code.gitea.io/gitea/modules/setting"
 )
 
 // ResourceIndex represents a resource index which could be used as issue/release and others
@@ -55,8 +58,25 @@ func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxInd
 	return nil
 }
 
+func postgresGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
+	res, err := GetEngine(ctx).Query(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
+		"VALUES (?,1) ON CONFLICT (group_id) DO UPDATE SET max_index = %s.max_index+1 RETURNING max_index",
+		tableName, tableName), groupID)
+	if err != nil {
+		return 0, err
+	}
+	if len(res) == 0 {
+		return 0, ErrGetResourceIndexFailed
+	}
+	return strconv.ParseInt(string(res[0]["max_index"]), 10, 64)
+}
+
 // GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created
 func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
+	if setting.Database.UsePostgreSQL {
+		return postgresGetNextResourceIndex(ctx, tableName, groupID)
+	}
+
 	e := GetEngine(ctx)
 
 	// try to update the max_index to next value, and acquire the write-lock for the record
diff --git a/models/git/commit_status.go b/models/git/commit_status.go
index 6353bb2fa9..928f1771fe 100644
--- a/models/git/commit_status.go
+++ b/models/git/commit_status.go
@@ -9,6 +9,7 @@ import (
 	"errors"
 	"fmt"
 	"net/url"
+	"strconv"
 	"strings"
 	"time"
 
@@ -49,8 +50,25 @@ func init() {
 	db.RegisterModel(new(CommitStatusIndex))
 }
 
+func postgresGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
+	res, err := db.GetEngine(ctx).Query("INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
+		"VALUES (?,?,1) ON CONFLICT (repo_id, sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1 RETURNING max_index",
+		repoID, sha)
+	if err != nil {
+		return 0, err
+	}
+	if len(res) == 0 {
+		return 0, db.ErrGetResourceIndexFailed
+	}
+	return strconv.ParseInt(string(res[0]["max_index"]), 10, 64)
+}
+
 // GetNextCommitStatusIndex retried 3 times to generate a resource index
 func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
+	if setting.Database.UsePostgreSQL {
+		return postgresGetCommitStatusIndex(ctx, repoID, sha)
+	}
+
 	e := db.GetEngine(ctx)
 
 	// try to update the max_index to next value, and acquire the write-lock for the record
diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go
index 3840c508dc..0d794cec75 100644
--- a/tests/integration/repo_commits_test.go
+++ b/tests/integration/repo_commits_test.go
@@ -135,8 +135,8 @@ func TestRepoCommitsStatusParallel(t *testing.T) {
 	var wg sync.WaitGroup
 	for i := 0; i < 10; i++ {
 		wg.Add(1)
-		go func(t *testing.T, i int) {
-			t.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) {
+		go func(parentT *testing.T, i int) {
+			parentT.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) {
 				runBody := doAPICreateCommitStatus(NewAPITestContext(t, "user2", "repo1"), path.Base(commitURL), api.CommitStatusState("pending"))
 				runBody(t)
 				wg.Done()