Closed
Description
The current code for models/issue.go
roughly does:
func getMaxIndexOfIssue(e Engine, repoID int64) (int64, error) {
var (
maxIndex int64
has bool
err error
)
has, err = e.SQL("SELECT COALESCE((SELECT MAX(`index`) FROM issue WHERE repo_id = ?),0)", repoID).Get(&maxIndex)
[...]
return maxIndex, nil
}
func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
maxIndex, err := getMaxIndexOfIssue(e, opts.Issue.RepoID)
if err != nil {
return err
}
opts.Issue.Index = maxIndex + 1
[...]
if _, err = e.Insert(opts.Issue); err != nil {
return err
}
[...]
}
It's possible that under heavy loads (e.g. a small server) two sessions may attempt to use the same index number to create issues; one of the two will fail due to a race condition:
- User A submits the form creating an issue for repo abc.
- User B submits the form creating another issue for repo abc.
- At
issue.go
, in the context of user A's request,getMaxIndexOfIssue()
returns99
. It will be used at step 5. - At
issue.go
, in the context of user B's request,getMaxIndexOfIssue()
also returns99
. because at the moment that's the latest issue for the repo. It will be used at step 6. - At
issue.go
, in the context of user A's request,newIssue()
inserts A's issue withIndex: 100
. - At
issue.go
, in the context of user B's request,newIssue()
attempts to insert B's issue withIndex: 100
and fails withduplicate key error
, because100
was used at step 5.
This will of course depend on the database locking model.
I think the best way to handle this is to use a mutex, thus covering most scenarios.
Metadata
Metadata
Assignees
Labels
No labels