7474
7575const issueTasksRegexpStr = `(^\s*[-*]\s\[[\sx]\]\s.)|(\n\s*[-*]\s\[[\sx]\]\s.)`
7676const issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[x]\]\s.)|(\n\s*[-*]\s\[[x]\]\s.)`
77+ const issueMaxDupIndexAttempts = 3
7778
7879func init () {
7980 issueTasksPat = regexp .MustCompile (issueTasksRegexpStr )
@@ -1132,7 +1133,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
11321133
11331134 // Milestone and assignee validation should happen before insert actual object.
11341135 if _ , err = e .Insert (opts .Issue ); err != nil {
1135- return err
1136+ return ErrNewIssueInsert { err }
11361137 }
11371138
11381139 if opts .Issue .MilestoneID > 0 {
@@ -1207,6 +1208,24 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
12071208
12081209// NewIssue creates new issue with labels for repository.
12091210func 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 ) {
12101229 sess := x .NewSession ()
12111230 defer sess .Close ()
12121231 if err = sess .Begin (); err != nil {
@@ -1220,7 +1239,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
12201239 Attachments : uuids ,
12211240 AssigneeIDs : assigneeIDs ,
12221241 }); err != nil {
1223- if IsErrUserDoesNotHaveAccessToRepo (err ) {
1242+ if IsErrUserDoesNotHaveAccessToRepo (err ) || IsErrNewIssueInsert ( err ) {
12241243 return err
12251244 }
12261245 return fmt .Errorf ("newIssue: %v" , err )
@@ -1231,7 +1250,12 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
12311250 }
12321251 sess .Close ()
12331252
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 {
12351259 ActUserID : issue .Poster .ID ,
12361260 ActUser : issue .Poster ,
12371261 OpType : ActionCreateIssue ,
@@ -1244,7 +1268,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
12441268 }
12451269
12461270 mode , _ := AccessLevel (issue .Poster , issue .Repo )
1247- if err = PrepareWebhooks (repo , HookEventIssues , & api.IssuePayload {
1271+ if err : = PrepareWebhooks (repo , HookEventIssues , & api.IssuePayload {
12481272 Action : api .HookIssueOpened ,
12491273 Index : issue .Index ,
12501274 Issue : issue .APIFormat (),
0 commit comments