Skip to content

Commit bad07d5

Browse files
committed
chore: add files cache for reading template tar archives from db
1 parent c5b542a commit bad07d5

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

archive/fs/fs.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package archivefs
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"io/fs"
7+
)
8+
9+
type FS struct {
10+
files map[string]*File
11+
}
12+
13+
// FS implements fs.FS
14+
var _ fs.ReadDirFS = &FS{}
15+
16+
func (f *FS) Open(fileName string) (fs.File, error) {
17+
file := f.files[fileName]
18+
if file == nil {
19+
// Check if it's a directory
20+
21+
return nil, fs.ErrNotExist
22+
}
23+
openFile := OpenFile{
24+
info: file.info,
25+
Reader: bytes.NewReader(file.content),
26+
}
27+
return &openFile, nil
28+
}
29+
30+
func (f *FS) ReadDir(dirName string) ([]fs.DirEntry
31+
32+
type File struct {
33+
info fs.FileInfo
34+
content []byte
35+
}
36+
37+
type OpenFile struct {
38+
info fs.FileInfo
39+
io.Reader
40+
}
41+
42+
// OpenFile implements fs.File
43+
var _ fs.File = &OpenFile{}
44+
45+
func (o *OpenFile) Stat() (fs.FileInfo, error) {
46+
return o.info, nil
47+
}
48+
49+
func (o *OpenFile) Read(p []byte) (int, error) {
50+
return o.Reader.Read(p)
51+
}
52+
53+
func (o *OpenFile) Close() error {
54+
o.Reader = nil
55+
return nil
56+
}

archive/fs/tar.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package archivefs
2+
3+
import (
4+
"archive/tar"
5+
"io"
6+
"io/fs"
7+
"path"
8+
9+
"golang.org/x/xerrors"
10+
)
11+
12+
func FromTar(r tar.Reader) (fs.FS, error) {
13+
fs := FS{files: make(map[string]fs.File)}
14+
for {
15+
it, err := r.Next()
16+
17+
if err != nil {
18+
return nil, xerrors.Errorf("failed to read tar archive: %w", err)
19+
}
20+
21+
// bufio.NewReader(&r).
22+
content, err := io.ReadAll(&r)
23+
fs.files[it.Name] = &File{
24+
info: it.FileInfo(),
25+
content: content,
26+
}
27+
}
28+
29+
path.Split(path string)
30+
31+
return fs, nil
32+
}

coderd/files/cache.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package files
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"context"
7+
"fmt"
8+
"io"
9+
"io/fs"
10+
"sync"
11+
"time"
12+
13+
"github.com/coder/coder/v2/coderd/database"
14+
"github.com/coder/coder/v2/coderd/util/lazy"
15+
"github.com/google/uuid"
16+
"golang.org/x/xerrors"
17+
)
18+
19+
// Cache persists the files for template versions, and is used by dynamic
20+
// parameters to deduplicate the files in memory.
21+
// - The user connects to the dynamic parameters websocket with a given template
22+
// version id.
23+
// - template version -> provisioner job -> file
24+
// - We persist those files
25+
//
26+
// Requirements:
27+
// - Multiple template versions can share a single "file"
28+
// - Files should be "ref counted" so that they're released when no one is using
29+
// them
30+
// - You should be able to fetch multiple different files in parallel, but you
31+
// should not fetch the same file multiple times in parallel.
32+
type Cache struct {
33+
sync.Mutex
34+
data map[uuid.UUID]*lazy.Value[fs.FS]
35+
}
36+
37+
// type CacheEntry struct {
38+
// atomic.
39+
// }
40+
41+
// Acquire
42+
func (c *Cache) Acquire(fileID uuid.UUID) fs.FS {
43+
return c.fetch(fileID).Load()
44+
}
45+
46+
// fetch handles grabbing the lock, creating a new lazy.Value if necessary,
47+
// and returning it. The lock can be safely released because lazy.Value handles
48+
// its own synchronization, so multiple concurrent reads for the same fileID
49+
// will still only ever result in a single load being performed.
50+
func (c *Cache) fetch(fileID uuid.UUID) *lazy.Value[fs.FS] {
51+
c.Mutex.Lock()
52+
defer c.Mutex.Unlock()
53+
54+
entry := c.data[fileID]
55+
if entry == nil {
56+
entry = lazy.New(func() fs.FS {
57+
time.Sleep(5 * time.Second)
58+
return NilFS{}
59+
})
60+
c.data[fileID] = entry
61+
}
62+
63+
return entry
64+
}
65+
66+
func NewFromStore(store database.Store) Cache {
67+
_ = func(ctx context.Context, fileID uuid.UUID) (fs.FS, error) {
68+
file, err := store.GetFileByID(ctx, fileID)
69+
if err != nil {
70+
return nil, xerrors.Errorf("failed to read file from database: %w", err)
71+
}
72+
73+
reader := tar.NewReader(bytes.NewBuffer(file.Data))
74+
_, _ = io.ReadAll(reader)
75+
76+
return NilFS{}, nil
77+
}
78+
79+
return Cache{}
80+
}
81+
82+
type NilFS struct{}
83+
84+
var _ fs.FS = NilFS{}
85+
86+
func (t NilFS) Open(_ string) (fs.File, error) {
87+
return nil, fmt.Errorf("oh no")
88+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy