@@ -6,6 +6,7 @@ package git
66import (
77 "context"
88 "crypto/sha1"
9+ "errors"
910 "fmt"
1011 "net/url"
1112 "strings"
@@ -48,79 +49,49 @@ func init() {
4849 db .RegisterModel (new (CommitStatusIndex ))
4950}
5051
51- // upsertCommitStatusIndex the function will not return until it acquires the lock or receives an error.
52- func upsertCommitStatusIndex (ctx context.Context , repoID int64 , sha string ) (err error ) {
53- // An atomic UPSERT operation (INSERT/UPDATE) is the only operation
54- // that ensures that the key is actually locked.
55- switch {
56- case setting .Database .UseSQLite3 || setting .Database .UsePostgreSQL :
57- _ , err = db .Exec (ctx , "INSERT INTO `commit_status_index` (repo_id, sha, max_index) " +
58- "VALUES (?,?,1) ON CONFLICT (repo_id,sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1" ,
59- repoID , sha )
60- case setting .Database .UseMySQL :
61- _ , err = db .Exec (ctx , "INSERT INTO `commit_status_index` (repo_id, sha, max_index) " +
62- "VALUES (?,?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1" ,
63- repoID , sha )
64- case setting .Database .UseMSSQL :
65- // https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
66- _ , err = db .Exec (ctx , "MERGE `commit_status_index` WITH (HOLDLOCK) as target " +
67- "USING (SELECT ? AS repo_id, ? AS sha) AS src " +
68- "ON src.repo_id = target.repo_id AND src.sha = target.sha " +
69- "WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 " +
70- "WHEN NOT MATCHED THEN INSERT (repo_id, sha, max_index) " +
71- "VALUES (src.repo_id, src.sha, 1);" ,
72- repoID , sha )
73- default :
74- return fmt .Errorf ("database type not supported" )
75- }
76- return err
77- }
78-
7952// GetNextCommitStatusIndex retried 3 times to generate a resource index
80- func GetNextCommitStatusIndex (repoID int64 , sha string ) (int64 , error ) {
81- for i := 0 ; i < db .MaxDupIndexAttempts ; i ++ {
82- idx , err := getNextCommitStatusIndex (repoID , sha )
83- if err == db .ErrResouceOutdated {
84- continue
85- }
86- if err != nil {
87- return 0 , err
88- }
89- return idx , nil
90- }
91- return 0 , db .ErrGetResourceIndexFailed
92- }
53+ func GetNextCommitStatusIndex (ctx context.Context , repoID int64 , sha string ) (int64 , error ) {
54+ e := db .GetEngine (ctx )
9355
94- // getNextCommitStatusIndex return the next index
95- func getNextCommitStatusIndex (repoID int64 , sha string ) (int64 , error ) {
96- ctx , commiter , err := db .TxContext (db .DefaultContext )
56+ // try to update the max_index to next value, and acquire the write-lock for the record
57+ res , err := e .Exec ("UPDATE `commit_status_index` SET max_index=max_index+1 WHERE repo_id=? AND sha=?" , repoID , sha )
9758 if err != nil {
9859 return 0 , err
9960 }
100- defer commiter .Close ()
101-
102- var preIdx int64
103- _ , err = db .GetEngine (ctx ).SQL ("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?" , repoID , sha ).Get (& preIdx )
61+ affected , err := res .RowsAffected ()
10462 if err != nil {
10563 return 0 , err
10664 }
107-
108- if err := upsertCommitStatusIndex (ctx , repoID , sha ); err != nil {
109- return 0 , err
65+ if affected == 0 {
66+ // this slow path is only for the first time of creating a resource index
67+ _ , errIns := e .Exec ("INSERT INTO `commit_status_index` (repo_id, sha, max_index) VALUES (?, ?, 0)" , repoID , sha )
68+ res , err = e .Exec ("UPDATE `commit_status_index` SET max_index=max_index+1 WHERE repo_id=? AND sha=?" , repoID , sha )
69+ if err != nil {
70+ return 0 , err
71+ }
72+ affected , err = res .RowsAffected ()
73+ if err != nil {
74+ return 0 , err
75+ }
76+ // if the update still can not update any records, the record must not exist and there must be some errors (insert error)
77+ if affected == 0 {
78+ if errIns == nil {
79+ return 0 , errors .New ("impossible error when GetNextCommitStatusIndex, insert and update both succeeded but no record is updated" )
80+ }
81+ return 0 , errIns
82+ }
11083 }
11184
112- var curIdx int64
113- has , err := db .GetEngine (ctx ).SQL ("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ? AND max_index=?" , repoID , sha , preIdx + 1 ).Get (& curIdx )
85+ // now, the new index is in database (protected by the transaction and write-lock)
86+ var newIdx int64
87+ has , err := e .SQL ("SELECT max_index FROM `commit_status_index` WHERE repo_id=? AND sha=?" , repoID , sha ).Get (& newIdx )
11488 if err != nil {
11589 return 0 , err
11690 }
11791 if ! has {
118- return 0 , db . ErrResouceOutdated
92+ return 0 , errors . New ( "impossible error when GetNextCommitStatusIndex, upsert succeeded but no record can be selected" )
11993 }
120- if err := commiter .Commit (); err != nil {
121- return 0 , err
122- }
123- return curIdx , nil
94+ return newIdx , nil
12495}
12596
12697func (status * CommitStatus ) loadAttributes (ctx context.Context ) (err error ) {
@@ -290,18 +261,18 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
290261 return fmt .Errorf ("NewCommitStatus[%s, %s]: no user specified" , repoPath , opts .SHA )
291262 }
292263
293- // Get the next Status Index
294- idx , err := GetNextCommitStatusIndex (opts .Repo .ID , opts .SHA )
295- if err != nil {
296- return fmt .Errorf ("generate commit status index failed: %w" , err )
297- }
298-
299264 ctx , committer , err := db .TxContext (db .DefaultContext )
300265 if err != nil {
301266 return fmt .Errorf ("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w" , opts .Repo .ID , opts .Creator .ID , opts .SHA , err )
302267 }
303268 defer committer .Close ()
304269
270+ // Get the next Status Index
271+ idx , err := GetNextCommitStatusIndex (ctx , opts .Repo .ID , opts .SHA )
272+ if err != nil {
273+ return fmt .Errorf ("generate commit status index failed: %w" , err )
274+ }
275+
305276 opts .CommitStatus .Description = strings .TrimSpace (opts .CommitStatus .Description )
306277 opts .CommitStatus .Context = strings .TrimSpace (opts .CommitStatus .Context )
307278 opts .CommitStatus .TargetURL = strings .TrimSpace (opts .CommitStatus .TargetURL )
@@ -315,7 +286,7 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
315286
316287 // Insert new CommitStatus
317288 if _ , err = db .GetEngine (ctx ).Insert (opts .CommitStatus ); err != nil {
318- return fmt .Errorf ("Insert CommitStatus[%s, %s]: %w" , repoPath , opts .SHA , err )
289+ return fmt .Errorf ("insert CommitStatus[%s, %s]: %w" , repoPath , opts .SHA , err )
319290 }
320291
321292 return committer .Commit ()
0 commit comments