@@ -40,6 +40,82 @@ type CommitStatus struct {
40
40
41
41
func init () {
42
42
db .RegisterModel (new (CommitStatus ))
43
+ db .RegisterModel (new (CommitStatusIndex ))
44
+ }
45
+
46
+ // upsertCommitStatusIndex the function will not return until it acquires the lock or receives an error.
47
+ func upsertCommitStatusIndex (e db.Engine , repoID int64 , sha string ) (err error ) {
48
+ // An atomic UPSERT operation (INSERT/UPDATE) is the only operation
49
+ // that ensures that the key is actually locked.
50
+ switch {
51
+ case setting .Database .UseSQLite3 || setting .Database .UsePostgreSQL :
52
+ _ , err = e .Exec ("INSERT INTO `commit_status_index` (repo_id, sha, max_index) " +
53
+ "VALUES (?,?,1) ON CONFLICT (repo_id,sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1" ,
54
+ repoID , sha )
55
+ case setting .Database .UseMySQL :
56
+ _ , err = e .Exec ("INSERT INTO `commit_status_index` (repo_id, sha, max_index) " +
57
+ "VALUES (?,?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1" ,
58
+ repoID , sha )
59
+ case setting .Database .UseMSSQL :
60
+ // https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
61
+ _ , err = e .Exec ("MERGE `commit_status_index` WITH (HOLDLOCK) as target " +
62
+ "USING (SELECT ? AS repo_id, ? AS sha) AS src " +
63
+ "ON src.repo_id = target.repo_id AND src.sha = target.sha " +
64
+ "WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 " +
65
+ "WHEN NOT MATCHED THEN INSERT (repo_id, sha, max_index) " +
66
+ "VALUES (src.repo_id, src.sha, 1);" ,
67
+ repoID , sha )
68
+ default :
69
+ return fmt .Errorf ("database type not supported" )
70
+ }
71
+ return
72
+ }
73
+
74
+ // GetNextCommitStatusIndex retried 3 times to generate a resource index
75
+ func GetNextCommitStatusIndex (repoID int64 , sha string ) (int64 , error ) {
76
+ for i := 0 ; i < db .MaxDupIndexAttempts ; i ++ {
77
+ idx , err := getNextCommitStatusIndex (repoID , sha )
78
+ if err == db .ErrResouceOutdated {
79
+ continue
80
+ }
81
+ if err != nil {
82
+ return 0 , err
83
+ }
84
+ return idx , nil
85
+ }
86
+ return 0 , db .ErrGetResourceIndexFailed
87
+ }
88
+
89
+ // getNextCommitStatusIndex return the next index
90
+ func getNextCommitStatusIndex (repoID int64 , sha string ) (int64 , error ) {
91
+ ctx , commiter , err := db .TxContext ()
92
+ if err != nil {
93
+ return 0 , err
94
+ }
95
+ defer commiter .Close ()
96
+
97
+ var preIdx int64
98
+ _ , err = ctx .Engine ().SQL ("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?" , repoID , sha ).Get (& preIdx )
99
+ if err != nil {
100
+ return 0 , err
101
+ }
102
+
103
+ if err := upsertCommitStatusIndex (ctx .Engine (), repoID , sha ); err != nil {
104
+ return 0 , err
105
+ }
106
+
107
+ var curIdx int64
108
+ has , err := ctx .Engine ().SQL ("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ? AND max_index=?" , repoID , sha , preIdx + 1 ).Get (& curIdx )
109
+ if err != nil {
110
+ return 0 , err
111
+ }
112
+ if ! has {
113
+ return 0 , db .ErrResouceOutdated
114
+ }
115
+ if err := commiter .Commit (); err != nil {
116
+ return 0 , err
117
+ }
118
+ return curIdx , nil
43
119
}
44
120
45
121
func (status * CommitStatus ) loadAttributes (e db.Engine ) (err error ) {
@@ -142,6 +218,14 @@ func sortCommitStatusesSession(sess *xorm.Session, sortType string) {
142
218
}
143
219
}
144
220
221
+ // CommitStatusIndex represents a table for commit status index
222
+ type CommitStatusIndex struct {
223
+ ID int64
224
+ RepoID int64 `xorm:"unique(repo_sha)"`
225
+ SHA string `xorm:"unique(repo_sha)"`
226
+ MaxIndex int64 `xorm:"index"`
227
+ }
228
+
145
229
// GetLatestCommitStatus returns all statuses with a unique context for a given commit.
146
230
func GetLatestCommitStatus (repoID int64 , sha string , listOptions ListOptions ) ([]* CommitStatus , error ) {
147
231
return getLatestCommitStatus (db .DefaultContext ().Engine (), repoID , sha , listOptions )
@@ -206,6 +290,12 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
206
290
return fmt .Errorf ("NewCommitStatus[%s, %s]: no user specified" , repoPath , opts .SHA )
207
291
}
208
292
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: %v" , err )
297
+ }
298
+
209
299
ctx , committer , err := db .TxContext ()
210
300
if err != nil {
211
301
return fmt .Errorf ("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v" , opts .Repo .ID , opts .Creator .ID , opts .SHA , err )
@@ -218,22 +308,7 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
218
308
opts .CommitStatus .SHA = opts .SHA
219
309
opts .CommitStatus .CreatorID = opts .Creator .ID
220
310
opts .CommitStatus .RepoID = opts .Repo .ID
221
-
222
- // Get the next Status Index
223
- var nextIndex int64
224
- lastCommitStatus := & CommitStatus {
225
- SHA : opts .SHA ,
226
- RepoID : opts .Repo .ID ,
227
- }
228
- has , err := ctx .Engine ().Desc ("index" ).Limit (1 ).Get (lastCommitStatus )
229
- if err != nil {
230
- return fmt .Errorf ("NewCommitStatus[%s, %s]: %v" , repoPath , opts .SHA , err )
231
- }
232
- if has {
233
- log .Debug ("NewCommitStatus[%s, %s]: found" , repoPath , opts .SHA )
234
- nextIndex = lastCommitStatus .Index
235
- }
236
- opts .CommitStatus .Index = nextIndex + 1
311
+ opts .CommitStatus .Index = idx
237
312
log .Debug ("NewCommitStatus[%s, %s]: %d" , repoPath , opts .SHA , opts .CommitStatus .Index )
238
313
239
314
opts .CommitStatus .ContextHash = hashCommitStatusContext (opts .CommitStatus .Context )
0 commit comments