-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Revert index when error occur on issue/PR creation #21362
Conversation
- Currently when a error occur in `NewIssueWithIndex`, The functions `NewIssue` and `NewPullRequest` will not revert the index update, thus a index has been "allocated", but end up not being used. - Add a `UpsertDecrResourceIndex` function, which will try to decrease the index, only when nobody else tried to allocate a index in the mean time.
Why it is needed since the SQLs are in a transaction and the transaction would be rolled back if error occurs? |
At least on Codeberg, we have multiple repos with wrong indices now, most were related to some kind of error. What could also be an option: If I recall correctly, there is some function to recalculate the correct indices, used by doctor. It shouldn't cost too much to recalculate if the index is still correct after some kind of error occurs, and it would definitely fix every case. |
If database transaction should do the rollback work, why the code is needed? If database transaction doesn't work, then there must be some real underlying bug, the real bugs shouldn't be hidden by the patch. And I recall an old issue about another MariaDB bug for prepared statement: At that time if that bug was bypassed, there would be more problems. My question is: why the transaction doesn't reset the issue index when error occurs? |
Except caused by an error, I think an index skipped is a good design than making it always align. Because if you delete last issue, you should not reuse the allocated index for that issue. |
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{ | ||
Repo: repo, | ||
Issue: issue, | ||
LabelIDs: labelIDs, | ||
Attachments: uuids, | ||
}); err != nil { | ||
if err := onFail(); err != nil { | ||
return fmt.Errorf("couldn't gracefully handle error: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return fmt.Errorf("couldn't gracefully handle error: %v", err) | |
return fmt.Errorf("couldn't gracefully handle error: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a new code-style I haven't been aware of? Since when are we going to use the wrapping errors verb?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, as I've learned: go seems to do some magic if you use %w
instead of %v
:
https://stackoverflow.com/a/61287626
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we can wrap stuff my pr which tries to create a common hierarchy of errors will be a lot easier
committer.Close() | ||
// Try to revert the increase in resource index. | ||
if err := db.UpsertDecrResourceIndex(db.DefaultContext, "issue_index", repo.ID, idx); err != nil { | ||
return fmt.Errorf("UpsertDecrResourceIndex: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return fmt.Errorf("UpsertDecrResourceIndex: %v", err) | |
return fmt.Errorf("UpsertDecrResourceIndex: %w", err) |
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { | ||
return err | ||
} | ||
return fmt.Errorf("newIssue: %v", err) | ||
} | ||
|
||
if err = committer.Commit(); err != nil { | ||
if err := onFail(); err != nil { | ||
return fmt.Errorf("couldn't gracefully handle error: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return fmt.Errorf("couldn't gracefully handle error: %v", err) | |
return fmt.Errorf("couldn't gracefully handle error: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And wrap the rest of them too if possible please
if err := onFail(); err != nil { | ||
return fmt.Errorf("couldn't gracefully handle error: %v", err) | ||
} | ||
|
||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { | ||
return err | ||
} | ||
return fmt.Errorf("newIssue: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap errors!
return fmt.Errorf("newIssue: %v", err) | |
return fmt.Errorf("newIssue: %w", err) |
// Close commiter. | ||
committer.Close() | ||
// Try to revert the increase in resource index. | ||
if err := db.UpsertDecrResourceIndex(outerCtx, "issue_index", repo.ID, idx); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect that outerCtx isn't the correct thing here. In fact I'm certain it isn't.
db.DefaultContext
is needed here.
I suspect that the times where this would actually work are actually infrequent. The decrement can only work if the next ID hasn't been requested and I suspect that most failures occur when there is a double click new Issue/Pull and a double ID has been added. |
Hi all, Sorry for the late response. So a small background-story of why the code is currently as-is, the call to
Why align? The issue is never inserted into the database, so it's actually not-aligned if we don't try to rollback the update in
The transaction doesn't cover the call to
It's a bug non-the-less that should be fixed. It was only observed due to a custom patch in Codeberg's Gitea fork, which would make the error "more frequent". It's a best-try effort to decrement the issue index, if it's not possible then nothing we can do. Gusted |
Then the question is: why not make GetNextResourceIndex respect the transaction? It should be clear and correct.
Why the call to GetNextResourceIndex should be done separately, couldn't the code be refactored to use transaction correctly? |
Please see:
But if everyone feels fine with adding the appropriate comment to that function we can try and move the transaction handling to the caller of the function. |
Yup, I have read the comment but at that time I just couldn't understand why the code couldn't be refactored to use correct transaction. IMO database transaction is the proper approach for this problem, no need to re-invent wheels to reset the data. There are a lot of bugs in Gitea, I believe this is not the only one which misuses database transaction. Making good use of transaction everywhere will resolve problem fundamentally. |
Alright @wxiaoguang, I will refactor the code to let the caller take care of the transaction. |
Okay, seems like this doesn't seem as easy as expected, the code has a good reason to implement the transaction itself: Line 64 in 94037ad
It will retry multiple times if needed, unfortunately you can't cover that with one general transaction, it has to rollback immediately if the resource is outdated and try again. So looking around I found savepoints which can be used as replacements, such that it can be used in the transaction and rollback just that portion of the actions. But Xorm currently doesn't builtin function for Savepoint and I'd prefer to not mess around with raw SQL on something I don't understand that great. Another option would be if the @wxiaoguang What are your thoughts? |
I think the old code is wrong. A simple The first UPDATE will acquire the write lock, it's safe to do other operations on it then, no concurrency problem. |
I have no idea what that means. The PR that added this, seems to have the same discussion if the code is good. |
@wxiaoguang I'm still not necessary sure of what you mean(and which exact SQL actions needs to be done), would you like to create a PR for this? To simplify that function. Happy to review such PR, so this PR can also be simplified 😅. |
I will take a try |
You can ignore the diff and only take a look at the new code. If the code looks good to you, I can add some tests. |
…on (#21469) Related: * #21362 This PR uses a general and stable method to generate resource index (eg: Issue Index, PR Index) If the code looks good, I can add more tests ps: please skip the diff, only have a look at the new code. It's entirely re-written. Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
replaced by #21469 |
NewIssueWithIndex
, The functionsNewIssue
andNewPullRequest
will not revert the index update, thus a index has been "allocated", but end up not being used. This leaves users confusing, why a index has been skipped.UpsertDecrResourceIndex
function, which will try to decrease the index, only when nobody else tried to allocate a index in the mean time. If it has been the case someone else allocated a new index in the mean time, it's not worth it (complexity in code)to "revert" the index.