simplify: drop SWR cache, always upload fresh to S3
Always overwrites S3 on every preview request — no stale content. Removed sync.Map cache and background goroutines. Blob-by-SHA paths still skip upload if already exists (immutable). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -18,19 +17,8 @@ import (
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// previewCache tracks known blob SHAs on S3 to skip Stat calls.
|
||||
// Key: S3 object path, Value: blob SHA that was uploaded.
|
||||
var previewCache sync.Map
|
||||
|
||||
// HTMLPreview serves an HTML file via S3 with stale-while-revalidate caching.
|
||||
//
|
||||
// Flow:
|
||||
// 1. Resolve the git blob for the requested file path
|
||||
// 2. Build the S3 object key: preview/{owner}/{repo}/{path}
|
||||
// 3. Check in-memory cache for the blob SHA
|
||||
// 4. HIT+fresh (SHA matches current blob) → redirect to S3 presigned URL
|
||||
// 5. HIT+stale (SHA mismatch) → redirect to S3 immediately, revalidate async
|
||||
// 6. MISS → upload synchronously, then redirect
|
||||
// HTMLPreview serves an HTML file via S3.
|
||||
// Always uploads the current blob (overwrite), then redirects to presigned S3 URL.
|
||||
func HTMLPreview(ctx *context.Context) {
|
||||
if !setting.HTMLPreview.Enabled {
|
||||
ctx.NotFound(fmt.Errorf("HTML preview is not enabled"))
|
||||
@@ -43,7 +31,6 @@ func HTMLPreview(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the blob
|
||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
@@ -59,34 +46,9 @@ func HTMLPreview(ctx *context.Context) {
|
||||
}
|
||||
|
||||
blob := entry.Blob()
|
||||
blobSHA := blob.ID.String()
|
||||
s3Key := buildPreviewS3Key(ctx)
|
||||
|
||||
// Check in-memory cache
|
||||
if cachedSHA, ok := previewCache.Load(s3Key); ok {
|
||||
if cachedSHA.(string) == blobSHA {
|
||||
// Cache HIT + fresh — redirect directly
|
||||
redirectToS3(ctx, s3Key, treePath)
|
||||
return
|
||||
}
|
||||
// Cache HIT + stale — redirect to old version, revalidate in background
|
||||
go revalidatePreview(s3Key, blob, blobSHA)
|
||||
redirectToS3(ctx, s3Key, treePath)
|
||||
return
|
||||
}
|
||||
|
||||
// Cache MISS — check if S3 has it (cold start)
|
||||
_, err = storage.HTMLPreview.Stat(s3Key)
|
||||
if err == nil {
|
||||
// S3 has a version — serve it and revalidate in background
|
||||
previewCache.Store(s3Key, "") // mark as known but unknown SHA
|
||||
go revalidatePreview(s3Key, blob, blobSHA)
|
||||
redirectToS3(ctx, s3Key, treePath)
|
||||
return
|
||||
}
|
||||
|
||||
// No cached version — upload synchronously
|
||||
if err := uploadPreview(s3Key, blob, blobSHA); err != nil {
|
||||
if err := uploadPreview(s3Key, blob); err != nil {
|
||||
ctx.ServerError("uploadPreview", err)
|
||||
return
|
||||
}
|
||||
@@ -111,16 +73,16 @@ func HTMLPreviewByID(ctx *context.Context) {
|
||||
}
|
||||
|
||||
blobSHA := blob.ID.String()
|
||||
// For blob-by-SHA, use a content-addressed key (immutable)
|
||||
s3Key := fmt.Sprintf("blobs/%s", blobSHA)
|
||||
|
||||
// Content-addressed: same SHA = same content, skip if exists
|
||||
_, err = storage.HTMLPreview.Stat(s3Key)
|
||||
if err == nil {
|
||||
redirectToS3(ctx, s3Key, blobSHA+".html")
|
||||
return
|
||||
}
|
||||
|
||||
if err := uploadPreview(s3Key, blob, blobSHA); err != nil {
|
||||
if err := uploadPreview(s3Key, blob); err != nil {
|
||||
ctx.ServerError("uploadPreview", err)
|
||||
return
|
||||
}
|
||||
@@ -140,7 +102,6 @@ func redirectToS3(ctx *context.Context, s3Key, name string) {
|
||||
ContentType: "text/html; charset=utf-8",
|
||||
})
|
||||
if err != nil {
|
||||
// Fallback: serve directly through Gitea
|
||||
servePreviewDirect(ctx, s3Key)
|
||||
return
|
||||
}
|
||||
@@ -171,7 +132,7 @@ func servePreviewDirect(ctx *context.Context, s3Key string) {
|
||||
_, _ = io.Copy(ctx.Resp, obj)
|
||||
}
|
||||
|
||||
func uploadPreview(s3Key string, blob *git.Blob, blobSHA string) error {
|
||||
func uploadPreview(s3Key string, blob *git.Blob) error {
|
||||
dataRc, err := blob.DataAsync()
|
||||
if err != nil {
|
||||
return fmt.Errorf("blob DataAsync: %w", err)
|
||||
@@ -183,15 +144,8 @@ func uploadPreview(s3Key string, blob *git.Blob, blobSHA string) error {
|
||||
return fmt.Errorf("S3 save: %w", err)
|
||||
}
|
||||
|
||||
previewCache.Store(s3Key, blobSHA)
|
||||
log.Debug("HTMLPreview: uploaded %s (blob %s)", s3Key, blobSHA[:8])
|
||||
log.Debug("HTMLPreview: uploaded %s (%d bytes)", s3Key, blob.Size())
|
||||
return nil
|
||||
}
|
||||
|
||||
func revalidatePreview(s3Key string, blob *git.Blob, blobSHA string) {
|
||||
if err := uploadPreview(s3Key, blob, blobSHA); err != nil {
|
||||
log.Error("HTMLPreview revalidate failed for %s: %v", s3Key, err)
|
||||
}
|
||||
}
|
||||
|
||||
// isHTMLTreePath is defined in view_file.go
|
||||
|
||||
Reference in New Issue
Block a user