Skip to content

models/issue.go NewIssue() could fail due to duplicate key #7887

Closed
@guillep2k

Description

@guillep2k

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:

  1. User A submits the form creating an issue for repo abc.
  2. User B submits the form creating another issue for repo abc.
  3. At issue.go, in the context of user A's request, getMaxIndexOfIssue() returns 99. It will be used at step 5.
  4. At issue.go, in the context of user B's request, getMaxIndexOfIssue() also returns 99. because at the moment that's the latest issue for the repo. It will be used at step 6.
  5. At issue.go, in the context of user A's request, newIssue() inserts A's issue with Index: 100.
  6. At issue.go, in the context of user B's request, newIssue() attempts to insert B's issue with Index: 100 and fails with duplicate key error, because 100 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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions