Skip to content

Commit cd13f27

Browse files
guillep2ksapk
authored andcommitted
Transaction-aware retry create issue to cope with duplicate keys (#8307)
* Revert #7898 * Transaction-aware retry create issue to cope with duplicate keys * Restore INSERT ... WHERE usage * Rearrange code for clarity * Fix error return in newIssue() * Fix error message
1 parent c9f819e commit cd13f27

File tree

3 files changed

+57
-18
lines changed

3 files changed

+57
-18
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

+23-17
Original file line numberDiff line numberDiff line change
@@ -1104,21 +1104,10 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
11041104
}
11051105

11061106
// Milestone and assignee validation should happen before insert actual object.
1107-
1108-
// There's no good way to identify a duplicate key error in database/sql; brute force some retries
1109-
dupIndexAttempts := issueMaxDupIndexAttempts
1110-
for {
1111-
_, err := e.SetExpr("`index`", "coalesce(MAX(`index`),0)+1").
1112-
Where("repo_id=?", opts.Issue.RepoID).
1113-
Insert(opts.Issue)
1114-
if err == nil {
1115-
break
1116-
}
1117-
1118-
dupIndexAttempts--
1119-
if dupIndexAttempts <= 0 {
1120-
return err
1121-
}
1107+
if _, err := e.SetExpr("`index`", "coalesce(MAX(`index`),0)+1").
1108+
Where("repo_id=?", opts.Issue.RepoID).
1109+
Insert(opts.Issue); err != nil {
1110+
return ErrNewIssueInsert{err}
11221111
}
11231112

11241113
inserted, err := getIssueByID(e, opts.Issue.ID)
@@ -1201,6 +1190,24 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
12011190

12021191
// NewIssue creates new issue with labels for repository.
12031192
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) (err error) {
1193+
// Retry several times in case INSERT fails due to duplicate key for (repo_id, index); see #7887
1194+
i := 0
1195+
for {
1196+
if err = newIssueAttempt(repo, issue, labelIDs, assigneeIDs, uuids); err == nil {
1197+
return nil
1198+
}
1199+
if !IsErrNewIssueInsert(err) {
1200+
return err
1201+
}
1202+
if i++; i == issueMaxDupIndexAttempts {
1203+
break
1204+
}
1205+
log.Error("NewIssue: error attempting to insert the new issue; will retry. Original error: %v", err)
1206+
}
1207+
return fmt.Errorf("NewIssue: too many errors attempting to insert the new issue. Last error was: %v", err)
1208+
}
1209+
1210+
func newIssueAttempt(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) (err error) {
12041211
sess := x.NewSession()
12051212
defer sess.Close()
12061213
if err = sess.Begin(); err != nil {
@@ -1214,7 +1221,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
12141221
Attachments: uuids,
12151222
AssigneeIDs: assigneeIDs,
12161223
}); err != nil {
1217-
if IsErrUserDoesNotHaveAccessToRepo(err) {
1224+
if IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
12181225
return err
12191226
}
12201227
return fmt.Errorf("newIssue: %v", err)
@@ -1223,7 +1230,6 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
12231230
if err = sess.Commit(); err != nil {
12241231
return fmt.Errorf("Commit: %v", err)
12251232
}
1226-
sess.Close()
12271233

12281234
return nil
12291235
}

models/pull.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,24 @@ func (pr *PullRequest) testPatch(e Engine) (err error) {
657657

658658
// NewPullRequest creates new pull request with labels for repository.
659659
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte, assigneeIDs []int64) (err error) {
660+
// Retry several times in case INSERT fails due to duplicate key for (repo_id, index); see #7887
661+
i := 0
662+
for {
663+
if err = newPullRequestAttempt(repo, pull, labelIDs, uuids, pr, patch, assigneeIDs); err == nil {
664+
return nil
665+
}
666+
if !IsErrNewIssueInsert(err) {
667+
return err
668+
}
669+
if i++; i == issueMaxDupIndexAttempts {
670+
break
671+
}
672+
log.Error("NewPullRequest: error attempting to insert the new issue; will retry. Original error: %v", err)
673+
}
674+
return fmt.Errorf("NewPullRequest: too many errors attempting to insert the new issue. Last error was: %v", err)
675+
}
676+
677+
func newPullRequestAttempt(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte, assigneeIDs []int64) (err error) {
660678
sess := x.NewSession()
661679
defer sess.Close()
662680
if err = sess.Begin(); err != nil {
@@ -671,7 +689,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
671689
IsPull: true,
672690
AssigneeIDs: assigneeIDs,
673691
}); err != nil {
674-
if IsErrUserDoesNotHaveAccessToRepo(err) {
692+
if IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
675693
return err
676694
}
677695
return fmt.Errorf("newIssue: %v", err)

0 commit comments

Comments
 (0)