Commit 0d06714a92

0d06714a922332eca48f3ca81fe3c721af687115

parent: 8375b863f5

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-26 18:48 UTC

Keep recently modified stale derivative dirs during prune

A request holding an older library snapshot may still be writing into a
stale version directory. Skip directories modified within the last five
minutes; a later prune removes them.

Closes #1

Layout: unified · split

internal/render/render.go +8
@@ -8,6 +8,7 @@ import (
8 "path/filepath" 8 "path/filepath"
9 "slices" 9 "slices"
10 "strconv" 10 "strconv"
11 "time"
11 12
12 "golang.org/x/sync/singleflight" 13 "golang.org/x/sync/singleflight"
13 14
@@ -84,6 +85,10 @@ func (r *Renderer) generate(it *library.Item, w int, dst string) error {
84 return os.Rename(tmp.Name(), dst) 85 return os.Rename(tmp.Name(), dst)
85} 86}
86 87
88// pruneGrace keeps stale directories modified this recently, since a request
89// holding an older library snapshot may still be writing into them.
90const pruneGrace = 5 * time.Minute
91
87// Prune removes derivative directories for items no longer in lib or whose source changed. 92// Prune removes derivative directories for items no longer in lib or whose source changed.
88func (r *Renderer) Prune(lib *library.Library) error { 93func (r *Renderer) Prune(lib *library.Library) error {
89 keep := map[string]bool{} 94 keep := map[string]bool{}
@@ -99,6 +104,9 @@ func (r *Renderer) Prune(lib *library.Library) error {
99 var firstErr error 104 var firstErr error
100 for _, v := range versions { 105 for _, v := range versions {
101 if !keep[v] { 106 if !keep[v] {
107 if fi, err := os.Stat(v); err == nil && time.Since(fi.ModTime()) < pruneGrace {
108 continue
109 }
102 if err := os.RemoveAll(v); err != nil { 110 if err := os.RemoveAll(v); err != nil {
103 log.Printf("prune %s: %v", v, err) 111 log.Printf("prune %s: %v", v, err)
104 if firstErr == nil { 112 if firstErr == nil {
internal/render/render_test.go +19
@@ -151,6 +151,10 @@ func TestPrune(t *testing.T) {
151 t.Fatal(err) 151 t.Fatal(err)
152 } 152 }
153 } 153 }
154 past := time.Now().Add(-2 * pruneGrace)
155 for _, it := range []*library.Item{cur, old, gone} {
156 os.Chtimes(filepath.Dir(r.Path(it, 480)), past, past)
157 }
154 lib := &library.Library{Albums: []*library.Album{{Slug: "tokyo", Items: []*library.Item{cur}}}} 158 lib := &library.Library{Albums: []*library.Album{{Slug: "tokyo", Items: []*library.Item{cur}}}}
155 if err := r.Prune(lib); err != nil { 159 if err := r.Prune(lib); err != nil {
156 t.Fatal(err) 160 t.Fatal(err)
@@ -164,3 +168,18 @@ func TestPrune(t *testing.T) {
164 } 168 }
165 } 169 }
166} 170}
171
172func TestPruneKeepsRecentlyModifiedStaleDir(t *testing.T) {
173 f := &fakeFormat{}
174 r := New(t.TempDir(), 1)
175 old := item(f)
176 if _, err := r.Get(old, 480); err != nil {
177 t.Fatal(err)
178 }
179 if err := r.Prune(&library.Library{}); err != nil {
180 t.Fatal(err)
181 }
182 if _, err := os.Stat(r.Path(old, 480)); err != nil {
183 t.Fatalf("recently written stale dir removed: %v", err)
184 }
185}