Skip to content

Commit c786930

Browse files
committed
Transaction-aware retry create issue to cope with duplicate keys
1 parent 9a20c6a commit c786930

File tree

3 files changed

+60
-5
lines changed

3 files changed

+60
-5
lines changed

models/error.go

+15
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,21 @@ func (err ErrIssueLabelTemplateLoad) Error() string {
10891089
return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError)
10901090
}
10911091

1092+
// ErrNewIssueInsert is used when the INSERT statement in newIssue fails
1093+
type ErrNewIssueInsert struct {
1094+
OriginalError error
1095+
}
1096+
1097+
// IsErrNewIssueInsert checks if an error is a ErrNewIssueInsert.
1098+
func IsErrNewIssueInsert(err error) bool {
1099+
_, ok := err.(ErrNewIssueInsert)
1100+
return ok
1101+
}
1102+
1103+
func (err ErrNewIssueInsert) Error() string {
1104+
return err.OriginalError.Error()
1105+
}
1106+
10921107
// __________ .__ .__ __________ __
10931108
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
10941109
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\

models/issue.go

+28-4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ var (
7474

7575
const issueTasksRegexpStr = `(^\s*[-*]\s\[[\sx]\]\s.)|(\n\s*[-*]\s\[[\sx]\]\s.)`
7676
const issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[x]\]\s.)|(\n\s*[-*]\s\[[x]\]\s.)`
77+
const issueMaxDupIndexAttempts = 3
7778

7879
func 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.
12091210
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) {
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(),

models/pull.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,22 @@ func (pr *PullRequest) testPatch(e Engine) (err error) {
654654

655655
// NewPullRequest creates new pull request with labels for repository.
656656
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte, assigneeIDs []int64) (err error) {
657+
// Retry several times in case INSERT fails due to duplicate key for (repo_id, index); see #7887
658+
i := 0
659+
for {
660+
if err = newPullRequestAttempt(repo, pull, labelIDs, uuids, pr, patch, assigneeIDs); !IsErrNewIssueInsert(err) {
661+
// Usually err == nil
662+
return err
663+
}
664+
if i++; i == issueMaxDupIndexAttempts {
665+
break
666+
}
667+
log.Error("NewPullRequest: error attempting to insert the new issue; will retry. Original error: %v", err)
668+
}
669+
return fmt.Errorf("NewPullRequest: too many errors attempting to insert the new issue. Last error was: %v", err)
670+
}
671+
672+
func newPullRequestAttempt(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte, assigneeIDs []int64) (err error) {
657673
sess := x.NewSession()
658674
defer sess.Close()
659675
if err = sess.Begin(); err != nil {
@@ -668,7 +684,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
668684
IsPull: true,
669685
AssigneeIDs: assigneeIDs,
670686
}); err != nil {
671-
if IsErrUserDoesNotHaveAccessToRepo(err) {
687+
if IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
672688
return err
673689
}
674690
return fmt.Errorf("newIssue: %v", err)

0 commit comments

Comments
 (0)