2020-03-10 12:08:59 +00:00
|
|
|
package artifact
|
|
|
|
|
|
|
|
import (
|
2023-04-26 12:06:56 +00:00
|
|
|
"encoding/hex"
|
2020-03-10 12:08:59 +00:00
|
|
|
"errors"
|
2023-04-26 12:06:56 +00:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"sync"
|
2020-03-10 12:08:59 +00:00
|
|
|
|
2023-10-02 19:32:41 +00:00
|
|
|
"gitlab.com/manytask/itmo-go/private/distbuild/pkg/build"
|
2020-03-10 12:08:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-03-29 20:35:58 +00:00
|
|
|
ErrNotFound = errors.New("artifact not found")
|
2020-04-04 18:45:29 +00:00
|
|
|
ErrExists = errors.New("artifact exists")
|
2020-03-29 20:35:58 +00:00
|
|
|
ErrWriteLocked = errors.New("artifact is locked for write")
|
|
|
|
ErrReadLocked = errors.New("artifact is locked for read")
|
2020-03-10 12:08:59 +00:00
|
|
|
)
|
|
|
|
|
2020-03-12 22:33:54 +00:00
|
|
|
type Cache struct {
|
2023-04-26 12:06:56 +00:00
|
|
|
tmpDir string
|
|
|
|
cacheDir string
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
writeLocked map[build.ID]struct{}
|
|
|
|
readLocked map[build.ID]int
|
2020-03-12 22:33:54 +00:00
|
|
|
}
|
2020-03-10 12:08:59 +00:00
|
|
|
|
|
|
|
func NewCache(root string) (*Cache, error) {
|
2023-04-26 12:06:56 +00:00
|
|
|
tmpDir := filepath.Join(root, "tmp")
|
|
|
|
|
|
|
|
if err := os.RemoveAll(tmpDir); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-10-02 19:32:41 +00:00
|
|
|
if err := os.MkdirAll(tmpDir, 0o777); err != nil {
|
2023-04-26 12:06:56 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cacheDir := filepath.Join(root, "c")
|
2023-10-02 19:32:41 +00:00
|
|
|
if err := os.MkdirAll(cacheDir, 0o777); err != nil {
|
2023-04-26 12:06:56 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < 256; i++ {
|
|
|
|
d := hex.EncodeToString([]byte{uint8(i)})
|
2023-10-02 19:32:41 +00:00
|
|
|
if err := os.MkdirAll(filepath.Join(cacheDir, d), 0o777); err != nil {
|
2023-04-26 12:06:56 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Cache{
|
|
|
|
tmpDir: tmpDir,
|
|
|
|
cacheDir: cacheDir,
|
|
|
|
writeLocked: make(map[build.ID]struct{}),
|
|
|
|
readLocked: make(map[build.ID]int),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Cache) readLock(id build.ID) error {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
if _, ok := c.writeLocked[id]; ok {
|
|
|
|
return ErrWriteLocked
|
|
|
|
}
|
|
|
|
|
|
|
|
c.readLocked[id]++
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Cache) readUnlock(id build.ID) {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
c.readLocked[id]--
|
|
|
|
if c.readLocked[id] == 0 {
|
|
|
|
delete(c.readLocked, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Cache) writeLock(id build.ID, remove bool) error {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
_, err := os.Stat(filepath.Join(c.cacheDir, id.Path()))
|
|
|
|
if !os.IsNotExist(err) && err != nil {
|
|
|
|
return err
|
|
|
|
} else if err == nil && !remove {
|
|
|
|
return ErrExists
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := c.writeLocked[id]; ok {
|
|
|
|
return ErrWriteLocked
|
|
|
|
}
|
|
|
|
if c.readLocked[id] > 0 {
|
|
|
|
return ErrReadLocked
|
|
|
|
}
|
|
|
|
|
|
|
|
c.writeLocked[id] = struct{}{}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Cache) writeUnlock(id build.ID) {
|
|
|
|
c.mu.Lock()
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
delete(c.writeLocked, id)
|
2020-03-12 22:33:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Cache) Range(artifactFn func(artifact build.ID) error) error {
|
2023-04-26 12:06:56 +00:00
|
|
|
shards, err := ioutil.ReadDir(c.cacheDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, shard := range shards {
|
|
|
|
dirs, err := ioutil.ReadDir(filepath.Join(c.cacheDir, shard.Name()))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, d := range dirs {
|
|
|
|
var id build.ID
|
|
|
|
if err := id.UnmarshalText([]byte(d.Name())); err != nil {
|
|
|
|
return fmt.Errorf("invalid artifact name: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := artifactFn(id); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-03-10 12:08:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Cache) Remove(artifact build.ID) error {
|
2023-04-26 12:06:56 +00:00
|
|
|
if err := c.writeLock(artifact, true); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer c.writeUnlock(artifact)
|
|
|
|
|
|
|
|
return os.RemoveAll(filepath.Join(c.cacheDir, artifact.Path()))
|
2020-03-10 12:08:59 +00:00
|
|
|
}
|
|
|
|
|
2020-03-12 22:33:54 +00:00
|
|
|
func (c *Cache) Create(artifact build.ID) (path string, commit, abort func() error, err error) {
|
2023-04-26 12:06:56 +00:00
|
|
|
if err = c.writeLock(artifact, false); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
path = filepath.Join(c.tmpDir, artifact.String())
|
2023-10-02 19:32:41 +00:00
|
|
|
if err = os.MkdirAll(path, 0o777); err != nil {
|
2023-04-26 12:06:56 +00:00
|
|
|
c.writeUnlock(artifact)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
abort = func() error {
|
|
|
|
defer c.writeUnlock(artifact)
|
|
|
|
return os.RemoveAll(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
commit = func() error {
|
|
|
|
defer c.writeUnlock(artifact)
|
|
|
|
return os.Rename(path, filepath.Join(c.cacheDir, artifact.Path()))
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2020-03-10 12:08:59 +00:00
|
|
|
}
|
|
|
|
|
2020-03-12 22:33:54 +00:00
|
|
|
func (c *Cache) Get(artifact build.ID) (path string, unlock func(), err error) {
|
2023-04-26 12:06:56 +00:00
|
|
|
if err = c.readLock(artifact); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
path = filepath.Join(c.cacheDir, artifact.Path())
|
|
|
|
if _, err = os.Stat(path); err != nil {
|
|
|
|
c.readUnlock(artifact)
|
|
|
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
err = ErrNotFound
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
unlock = func() {
|
|
|
|
c.readUnlock(artifact)
|
|
|
|
}
|
|
|
|
return
|
2020-03-10 12:08:59 +00:00
|
|
|
}
|