fix: concurrency safety and API correctness from code review
B3 (HIGH): sora_generation_service.go - Add panic recovery to parallel S3 URL fetching goroutines. Without recovery, a panic in GetAccessURL would skip wg.Done() causing wg.Wait() to hang indefinitely. B2 (MEDIUM): subscription_service.go:549 - Replace bare goroutine with safego.Go() for consistent panic recovery pattern. All other async calls in this file already use safego. B4 (MEDIUM): admin/sora_handler.go - Change ClearUserStorage response from 200 no-op to 410 Gone. The per-user storage quota was fully removed; returning success was misleading to callers.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||||
@@ -167,8 +168,9 @@ func (h *SoraHandler) ListGenerations(c *gin.Context) {
|
|||||||
// ClearUserStorage 清除用户的 Sora 存储空间(已弃用)。
|
// ClearUserStorage 清除用户的 Sora 存储空间(已弃用)。
|
||||||
//
|
//
|
||||||
// Deprecated: Per-user storage tracking has been removed.
|
// Deprecated: Per-user storage tracking has been removed.
|
||||||
// This endpoint now returns a success no-op. It will be removed in a future version.
|
// This endpoint now returns 410 Gone. Per-user Sora storage quota tracking was
|
||||||
// Clients should stop calling this endpoint.
|
// fully removed in the Sora storage refactoring. Storage management is now
|
||||||
|
// handled at the system-default level via SoraQuotaService.
|
||||||
//
|
//
|
||||||
// DELETE /api/v1/admin/sora/users/:id/storage
|
// DELETE /api/v1/admin/sora/users/:id/storage
|
||||||
func (h *SoraHandler) ClearUserStorage(c *gin.Context) {
|
func (h *SoraHandler) ClearUserStorage(c *gin.Context) {
|
||||||
@@ -178,18 +180,20 @@ func (h *SoraHandler) ClearUserStorage(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置用户的存储使用量
|
// Verify user exists before responding
|
||||||
// NOTE: Per-user SoraStorageUsedBytes field removed.
|
ctx := c.Request.Context()
|
||||||
// Storage clearing now handled at the SoraGenerationService level if needed.
|
if _, err := h.userRepo.GetByID(ctx, userID); err != nil {
|
||||||
_, err = h.userRepo.GetByID(c.Request.Context(), userID)
|
|
||||||
if err != nil {
|
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement storage cleanup via SoraGenerationService
|
|
||||||
c.Header("Deprecation", "true")
|
c.Header("Deprecation", "true")
|
||||||
c.Header("Sunset", "2026-12-31")
|
c.Header("Sunset", "2026-12-31")
|
||||||
c.Header("Warning", `299 - "Deprecated API: use SoraGenerationService for storage management"`)
|
c.Header("Warning", `299 - "Gone: per-user storage tracking removed, see SoraQuotaService"`)
|
||||||
response.Success(c, gin.H{"message": "User Sora storage cleared (no-op: per-user tracking removed)", "deprecated": true})
|
c.JSON(http.StatusGone, gin.H{
|
||||||
|
"error": "This endpoint is no longer available",
|
||||||
|
"message": "Per-user Sora storage quota tracking has been removed. Storage is now managed at system level.",
|
||||||
|
"sunset": "2026-12-31",
|
||||||
|
"deprecated": true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,6 +308,15 @@ func (s *SoraGenerationService) ResolveMediaURLs(ctx context.Context, gen *SoraG
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(i int, objectKey string) {
|
go func(i int, objectKey string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
errMu.Lock()
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = fmt.Errorf("goroutine panic fetching S3 URL: %v", r)
|
||||||
|
}
|
||||||
|
errMu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
url, err := s.s3Storage.GetAccessURL(ctx, objectKey)
|
url, err := s.s3Storage.GetAccessURL(ctx, objectKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMu.Lock()
|
errMu.Lock()
|
||||||
|
|||||||
@@ -546,11 +546,11 @@ func (s *SubscriptionService) ExtendSubscription(ctx context.Context, subscripti
|
|||||||
s.InvalidateSubCache(sub.UserID, sub.GroupID)
|
s.InvalidateSubCache(sub.UserID, sub.GroupID)
|
||||||
if s.billingCacheService != nil {
|
if s.billingCacheService != nil {
|
||||||
userID, groupID := sub.UserID, sub.GroupID
|
userID, groupID := sub.UserID, sub.GroupID
|
||||||
go func() {
|
safego.Go("service.billing-cache-invalidate", func() {
|
||||||
cacheCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
cacheCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_ = s.billingCacheService.InvalidateSubscription(cacheCtx, userID, groupID)
|
_ = s.billingCacheService.InvalidateSubscription(cacheCtx, userID, groupID)
|
||||||
}()
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.userSubRepo.GetByID(ctx, subscriptionID)
|
return s.userSubRepo.GetByID(ctx, subscriptionID)
|
||||||
|
|||||||
Reference in New Issue
Block a user