@@ -6,6 +6,7 @@ package git
6
6
import (
7
7
"context"
8
8
"crypto/sha1"
9
+ "errors"
9
10
"fmt"
10
11
"net/url"
11
12
"strings"
@@ -48,79 +49,49 @@ func init() {
48
49
db .RegisterModel (new (CommitStatusIndex ))
49
50
}
50
51
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
-
79
52
// 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 )
93
55
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 )
97
58
if err != nil {
98
59
return 0 , err
99
60
}
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 ()
104
62
if err != nil {
105
63
return 0 , err
106
64
}
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
+ }
110
83
}
111
84
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 )
114
88
if err != nil {
115
89
return 0 , err
116
90
}
117
91
if ! has {
118
- return 0 , db . ErrResouceOutdated
92
+ return 0 , errors . New ( "impossible error when GetNextCommitStatusIndex, upsert succeeded but no record can be selected" )
119
93
}
120
- if err := commiter .Commit (); err != nil {
121
- return 0 , err
122
- }
123
- return curIdx , nil
94
+ return newIdx , nil
124
95
}
125
96
126
97
func (status * CommitStatus ) loadAttributes (ctx context.Context ) (err error ) {
@@ -290,18 +261,18 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
290
261
return fmt .Errorf ("NewCommitStatus[%s, %s]: no user specified" , repoPath , opts .SHA )
291
262
}
292
263
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
-
299
264
ctx , committer , err := db .TxContext (db .DefaultContext )
300
265
if err != nil {
301
266
return fmt .Errorf ("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w" , opts .Repo .ID , opts .Creator .ID , opts .SHA , err )
302
267
}
303
268
defer committer .Close ()
304
269
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
+
305
276
opts .CommitStatus .Description = strings .TrimSpace (opts .CommitStatus .Description )
306
277
opts .CommitStatus .Context = strings .TrimSpace (opts .CommitStatus .Context )
307
278
opts .CommitStatus .TargetURL = strings .TrimSpace (opts .CommitStatus .TargetURL )
@@ -315,7 +286,7 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
315
286
316
287
// Insert new CommitStatus
317
288
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 )
319
290
}
320
291
321
292
return committer .Commit ()
0 commit comments