74
74
75
75
const issueTasksRegexpStr = `(^\s*[-*]\s\[[\sx]\]\s.)|(\n\s*[-*]\s\[[\sx]\]\s.)`
76
76
const issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[x]\]\s.)|(\n\s*[-*]\s\[[x]\]\s.)`
77
+ const issueMaxDupIndexAttempts = 3
77
78
78
79
func init () {
79
80
issueTasksPat = regexp .MustCompile (issueTasksRegexpStr )
@@ -1132,7 +1133,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
1132
1133
1133
1134
// Milestone and assignee validation should happen before insert actual object.
1134
1135
if _ , err = e .Insert (opts .Issue ); err != nil {
1135
- return err
1136
+ return ErrNewIssueInsert { err }
1136
1137
}
1137
1138
1138
1139
if opts .Issue .MilestoneID > 0 {
@@ -1207,6 +1208,24 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
1207
1208
1208
1209
// NewIssue creates new issue with labels for repository.
1209
1210
func NewIssue (repo * Repository , issue * Issue , labelIDs []int64 , assigneeIDs []int64 , uuids []string ) (err error ) {
1211
+ // Retry several times in case INSERT fails due to duplicate key for (repo_id, index); see #7887
1212
+ i := 0
1213
+ for {
1214
+ if err = newIssueAttempt (repo , issue , labelIDs , assigneeIDs , uuids ); err == nil {
1215
+ return newIssuePostInsert (issue , repo )
1216
+ }
1217
+ if ! IsErrNewIssueInsert (err ) {
1218
+ return err
1219
+ }
1220
+ if i ++ ; i == issueMaxDupIndexAttempts {
1221
+ break
1222
+ }
1223
+ log .Error ("NewPullRequest: error attempting to insert the new issue; will retry. Original error: %v" , err )
1224
+ }
1225
+ return fmt .Errorf ("NewPullRequest: too many errors attempting to insert the new issue. Last error was: %v" , err )
1226
+ }
1227
+
1228
+ func newIssueAttempt (repo * Repository , issue * Issue , labelIDs []int64 , assigneeIDs []int64 , uuids []string ) (err error ) {
1210
1229
sess := x .NewSession ()
1211
1230
defer sess .Close ()
1212
1231
if err = sess .Begin (); err != nil {
@@ -1220,7 +1239,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
1220
1239
Attachments : uuids ,
1221
1240
AssigneeIDs : assigneeIDs ,
1222
1241
}); err != nil {
1223
- if IsErrUserDoesNotHaveAccessToRepo (err ) {
1242
+ if IsErrUserDoesNotHaveAccessToRepo (err ) || IsErrNewIssueInsert ( err ) {
1224
1243
return err
1225
1244
}
1226
1245
return fmt .Errorf ("newIssue: %v" , err )
@@ -1231,7 +1250,12 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
1231
1250
}
1232
1251
sess .Close ()
1233
1252
1234
- if err = NotifyWatchers (& Action {
1253
+ return nil
1254
+ }
1255
+
1256
+ // newIssuePostInsert performs issue creation operations that need to be separate transactions
1257
+ func newIssuePostInsert (issue * Issue , repo * Repository ) error {
1258
+ if err := NotifyWatchers (& Action {
1235
1259
ActUserID : issue .Poster .ID ,
1236
1260
ActUser : issue .Poster ,
1237
1261
OpType : ActionCreateIssue ,
@@ -1244,7 +1268,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
1244
1268
}
1245
1269
1246
1270
mode , _ := AccessLevel (issue .Poster , issue .Repo )
1247
- if err = PrepareWebhooks (repo , HookEventIssues , & api.IssuePayload {
1271
+ if err : = PrepareWebhooks (repo , HookEventIssues , & api.IssuePayload {
1248
1272
Action : api .HookIssueOpened ,
1249
1273
Index : issue .Index ,
1250
1274
Issue : issue .APIFormat (),
0 commit comments