-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a new table issue_index to store the max issue index so that issu…
…e could be deleted with no duplicated index (#15599) * Add a new table issue_index to store the max issue index so that issue could be deleted with no duplicated index * Fix pull index * Add tests for concurrent creating issues * Fix lint * Fix tests * Fix postgres test * Add test for migration v180 * Rename wrong test file name Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lauris BH <lauris@nix.lv>
- Loading branch information
1 parent
a005265
commit 0393a57
Showing
14 changed files
with
354 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
- | ||
group_id: 1 | ||
max_index: 5 | ||
- | ||
group_id: 2 | ||
max_index: 2 | ||
- | ||
group_id: 3 | ||
max_index: 2 | ||
- | ||
group_id: 10 | ||
max_index: 1 | ||
- | ||
group_id: 48 | ||
max_index: 1 | ||
- | ||
group_id: 42 | ||
max_index: 1 | ||
- | ||
group_id: 50 | ||
max_index: 1 | ||
- | ||
group_id: 51 | ||
max_index: 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Copyright 2021 The Gitea Authors. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package models | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"code.gitea.io/gitea/modules/setting" | ||
) | ||
|
||
// ResourceIndex represents a resource index which could be used as issue/release and others | ||
// We can create different tables i.e. issue_index, release_index and etc. | ||
type ResourceIndex struct { | ||
GroupID int64 `xorm:"unique"` | ||
MaxIndex int64 `xorm:"index"` | ||
} | ||
|
||
// IssueIndex represents the issue index table | ||
type IssueIndex ResourceIndex | ||
|
||
// upsertResourceIndex the function will not return until it acquires the lock or receives an error. | ||
func upsertResourceIndex(e Engine, tableName string, groupID int64) (err error) { | ||
// An atomic UPSERT operation (INSERT/UPDATE) is the only operation | ||
// that ensures that the key is actually locked. | ||
switch { | ||
case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL: | ||
_, err = e.Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+ | ||
"VALUES (?,1) ON CONFLICT (group_id) DO UPDATE SET max_index = %s.max_index+1", | ||
tableName, tableName), groupID) | ||
case setting.Database.UseMySQL: | ||
_, err = e.Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+ | ||
"VALUES (?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1", tableName), | ||
groupID) | ||
case setting.Database.UseMSSQL: | ||
// https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/ | ||
_, err = e.Exec(fmt.Sprintf("MERGE %s WITH (HOLDLOCK) as target "+ | ||
"USING (SELECT ? AS group_id) AS src "+ | ||
"ON src.group_id = target.group_id "+ | ||
"WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 "+ | ||
"WHEN NOT MATCHED THEN INSERT (group_id, max_index) "+ | ||
"VALUES (src.group_id, 1);", tableName), | ||
groupID) | ||
default: | ||
return fmt.Errorf("database type not supported") | ||
} | ||
return | ||
} | ||
|
||
var ( | ||
// ErrResouceOutdated represents an error when request resource outdated | ||
ErrResouceOutdated = errors.New("resource outdated") | ||
// ErrGetResourceIndexFailed represents an error when resource index retries 3 times | ||
ErrGetResourceIndexFailed = errors.New("get resource index failed") | ||
) | ||
|
||
const ( | ||
maxDupIndexAttempts = 3 | ||
) | ||
|
||
// GetNextResourceIndex retried 3 times to generate a resource index | ||
func GetNextResourceIndex(tableName string, groupID int64) (int64, error) { | ||
for i := 0; i < maxDupIndexAttempts; i++ { | ||
idx, err := getNextResourceIndex(tableName, groupID) | ||
if err == ErrResouceOutdated { | ||
continue | ||
} | ||
if err != nil { | ||
return 0, err | ||
} | ||
return idx, nil | ||
} | ||
return 0, ErrGetResourceIndexFailed | ||
} | ||
|
||
// deleteResouceIndex delete resource index | ||
func deleteResouceIndex(e Engine, tableName string, groupID int64) error { | ||
_, err := e.Exec(fmt.Sprintf("DELETE FROM %s WHERE group_id=?", tableName), groupID) | ||
return err | ||
} | ||
|
||
// getNextResourceIndex return the next index | ||
func getNextResourceIndex(tableName string, groupID int64) (int64, error) { | ||
sess := x.NewSession() | ||
defer sess.Close() | ||
if err := sess.Begin(); err != nil { | ||
return 0, err | ||
} | ||
var preIdx int64 | ||
_, err := sess.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&preIdx) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
if err := upsertResourceIndex(sess, tableName, groupID); err != nil { | ||
return 0, err | ||
} | ||
|
||
var curIdx int64 | ||
has, err := sess.SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ? AND max_index=?", tableName), groupID, preIdx+1).Get(&curIdx) | ||
if err != nil { | ||
return 0, err | ||
} | ||
if !has { | ||
return 0, ErrResouceOutdated | ||
} | ||
if err := sess.Commit(); err != nil { | ||
return 0, err | ||
} | ||
return curIdx, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright 2021 The Gitea Authors. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package models | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestResourceIndex(t *testing.T) { | ||
assert.NoError(t, PrepareTestDatabase()) | ||
|
||
var wg sync.WaitGroup | ||
for i := 0; i < 100; i++ { | ||
wg.Add(1) | ||
go func(i int) { | ||
testInsertIssue(t, fmt.Sprintf("issue %d", i+1), "my issue", 0) | ||
wg.Done() | ||
}(i) | ||
} | ||
wg.Wait() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.