-
-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Improve attachments deletion #35103
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
base: main
Are you sure you want to change the base?
Improve attachments deletion #35103
Conversation
026b762
to
f3d0e51
Compare
4ae54c8
to
97556a8
Compare
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.
Overall doesn't look good .....
models/db/file_status.go
Outdated
// Copyright 2025 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package db |
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.
Why in db
package? No proper package for it?
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.
Because the enum can be reused for other storages in the future.
models/db/file_status.go
Outdated
type FileStatus int | ||
|
||
const ( | ||
FileStatusNormal FileStatus = iota // FileStatusNormal indicates the file is normal and exists on disk. |
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.
Zero values cause various problems in XORM
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.
models/issues/comment.go
Outdated
if res > 0 { | ||
var reviewComment Comment | ||
has, err := db.GetEngine(ctx).Where("review_id = ?", comment.ReviewID). | ||
And("type = ?", CommentTypeReview).Get(&reviewComment) |
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.
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.
models/issues/comment.go
Outdated
// delete review & review comment if the code comment is the last comment of the review | ||
if comment.Type == CommentTypeCode && comment.ReviewID > 0 { | ||
res, err := db.GetEngine(ctx).ID(comment.ReviewID). | ||
Where("NOT EXISTS (SELECT 1 FROM comment WHERE review_id = ? AND `type` = ?)", comment.ReviewID, CommentTypeCode). |
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.
It needs to test with all databases since raw SQL is used.
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.
Moved to #35133
models/issues/issue_update.go
Outdated
@@ -305,6 +305,9 @@ func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string) | |||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err) | |||
} | |||
for i := range attachments { | |||
if attachments[i].IssueID != 0 { |
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.
Will it cause problems if an attachment link is copied from issue-1 to issue-2?
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.
The attachment was uploaded with no issue_id assigned when creating a new issue. And then invoking this method to update the issue id when submit the issue. The change will prevent one attachment which has been assigned an issue id to be changed to another.
If an attachment link copied to another place or outside of Gitea site. It will return 404 if the attachment is deleted. I don't understand what's your concern.
models/migrations/v1_25/v321.go
Outdated
LastDeleteFailedTime timeutil.TimeStamp // Last time the deletion failed, used to prevent infinite loop | ||
Size int64 `xorm:"DEFAULT 0"` | ||
CreatedUnix timeutil.TimeStamp `xorm:"created"` | ||
CustomDownloadURL string `xorm:"-"` |
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.
Why a full copy-paste to the Attachment
struct?
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 remove this line. Keeping other columns there so that there is a snapshot of what the table should be at that time.
models/migrations/v1_25/v321.go
Outdated
return err | ||
} | ||
|
||
if _, err := x.Exec("UPDATE `attachment` SET status = ? WHERE status IS NULL", db.FileStatusNormal); 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.
Why "null-able"?
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.
services/attachment/attachment.go
Outdated
var cleanQueue *queue.WorkerPoolQueue[int64] | ||
|
||
func Init() error { | ||
cleanQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "attachments-clean", handler) |
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.
Why it needs to use a queue?
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.
A cleanup queue helps avoid immediate deletion of attachment files from disk, reducing blocking time for end users - especially when deleting large releases or repositories with many attachments. The associated cleanup cron task does not need to run frequently because of clean queue exist; cron task currently executes once every 24 hours. This cron task primarily handles rare edge cases, such as when a database transaction is successfully committed but the system restarts before the corresponding IDs are pushed to the queue. In most typical scenarios, the queue is processed shortly after the database transaction completes.
web_src/js/features/repo-issue.ts
Outdated
|
||
if (json.deletedReviewCommentHashTag) { | ||
document.querySelector(`#${json.deletedReviewCommentHashTag}`)?.remove(); | ||
} |
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.
#${json.deletedReviewCommentHashTag}
should be the parent of the "delete button"? Either you should use "closest", or use a "data-xxxx" to make the delete button can find the target, but not use the fragile backend generated ID as selector.
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.
moved to #35133
services/attachment/attachment.go
Outdated
lastID := int64(0) | ||
for { | ||
if err := db.GetEngine(ctx). | ||
Where("id > ? AND status = ?", lastID, db.FileStatusToBeDeleted). |
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.
It is very low slow. You need an index like this: (status, id)
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.
services/attachment/attachment.go
Outdated
if err := db.GetEngine(ctx). | ||
Where("id > ? AND status = ?", lastID, db.FileStatusToBeDeleted). | ||
Limit(100). | ||
Find(&attachments); 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.
It generates wrong result since you do not make them have a stable order.
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.
models/issues/comment.go
Outdated
|
||
var deletedReviewComment *Comment | ||
|
||
// delete review & review comment if the code comment is the last comment of the review |
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.
Why it can delete the review? What if the review is a "change request" or "approval"?
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.
moved to #35133
services/issue/comments.go
Outdated
} | ||
|
||
attachment.AddAttachmentsToCleanQueue(ctx, comment.Attachments) |
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.
You deleted 2 comments above, but only handle one comment's "attachments"?
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.
The review comment will be deleted if it contains no content or attachments, and its associated code comment is the last remaining one. If it has any attachments, it should not be deleted automatically.
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.
The reviewComment deletion has been moved to #35133, now only one comment deleted in this pull request.
models/issues/comment.go
Outdated
|
||
var deletedReviewComment *Comment | ||
|
||
// delete review & review comment if the code comment is the last comment of the review |
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.
There is no test for such a changed behavior?
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.
moved to #35133
Change my opinion: it looks wrong. |
I’ve pushed some new commits. Could you please review them again and let me know if there are any concerns? |
The design is wrong and not worth to review. If your plan is "the enum can be reused for other storages in the future", you need a separate table like "storage_file_deletion (storage_type, storage_path)", but not keep patching other tables. |
…torage could reuse the deletion infrastructure
@lafriks @wxiaoguang I reimplemented it and use a standalone table |
Refactor attachment deletion logic
The attachment of comment deletion process has been moved from the
AfterDelete
hook to the service layer. This change avoids scenarios where files are deleted while the database transaction is later rolled back. The new implementation introduces a two-stage deletion process using a status column to mark attachments as deleted. These marked attachments are excluded from all UI and API queries. A background cleanup queue is responsible for permanently deleting the files and then removing the corresponding database records. If file deletion fails repeatedly, a system notice will be issued to alert administrators.This will also improve performance when deleting a release with many files.
Removed unused functions
DeleteAttachmentsByIssue
andDeleteAttachmentsByComment
are removed because only tests need them.