2018-01-31 02:15:21 +01:00
|
|
|
package store
|
|
|
|
|
|
|
|
import (
|
2021-04-05 23:34:45 +02:00
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
2018-01-31 02:15:21 +01:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path"
|
2021-05-21 02:41:47 +02:00
|
|
|
"runtime"
|
2021-01-09 05:08:20 +01:00
|
|
|
"time"
|
2018-01-31 02:15:21 +01:00
|
|
|
|
2019-11-14 01:11:35 +01:00
|
|
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
|
|
|
"github.com/lbryio/lbry.go/v2/stream"
|
2021-05-21 01:59:50 +02:00
|
|
|
"github.com/lbryio/reflector.go/locks"
|
2021-01-09 05:08:20 +01:00
|
|
|
"github.com/lbryio/reflector.go/shared"
|
2020-11-02 20:48:56 +01:00
|
|
|
"github.com/lbryio/reflector.go/store/speedwalk"
|
2021-04-05 23:34:45 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
2021-04-07 04:46:18 +02:00
|
|
|
"go.uber.org/atomic"
|
2018-01-31 02:15:21 +01:00
|
|
|
)
|
|
|
|
|
2021-05-21 02:41:47 +02:00
|
|
|
func init() {
|
|
|
|
writeCh = make(chan writeRequest)
|
|
|
|
for i := 0; i < runtime.NumCPU(); i++ {
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case r := <-writeCh:
|
|
|
|
err := ioutil.WriteFile(r.filename, r.data, r.perm)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("could not write file %s to disk, failed with error: %s", r.filename, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var writeCh chan writeRequest
|
|
|
|
|
2020-10-22 19:12:31 +02:00
|
|
|
// DiskStore stores blobs on a local disk
|
|
|
|
type DiskStore struct {
|
2019-10-03 22:12:49 +02:00
|
|
|
// the location of blobs on disk
|
|
|
|
blobDir string
|
|
|
|
// store files in subdirectories based on the first N chars in the filename. 0 = don't create subdirectories.
|
|
|
|
prefixLength int
|
2018-01-31 02:15:21 +01:00
|
|
|
|
2020-10-21 23:31:15 +02:00
|
|
|
// true if initOnce ran, false otherwise
|
|
|
|
initialized bool
|
2021-04-07 04:46:18 +02:00
|
|
|
|
|
|
|
concurrentChecks atomic.Int32
|
2021-05-21 01:59:50 +02:00
|
|
|
lock locks.MultipleLock
|
2018-01-31 02:15:21 +01:00
|
|
|
}
|
|
|
|
|
2021-04-07 04:46:18 +02:00
|
|
|
const maxConcurrentChecks = 3
|
|
|
|
|
2020-10-22 19:12:31 +02:00
|
|
|
// NewDiskStore returns an initialized file disk store pointer.
|
|
|
|
func NewDiskStore(dir string, prefixLength int) *DiskStore {
|
|
|
|
return &DiskStore{
|
2020-10-21 23:31:15 +02:00
|
|
|
blobDir: dir,
|
|
|
|
prefixLength: prefixLength,
|
2021-05-21 01:59:50 +02:00
|
|
|
lock: locks.NewMultipleLock(),
|
2020-10-21 23:31:15 +02:00
|
|
|
}
|
2019-10-03 22:12:49 +02:00
|
|
|
}
|
|
|
|
|
2020-10-22 19:49:02 +02:00
|
|
|
const nameDisk = "disk"
|
2018-01-31 02:15:21 +01:00
|
|
|
|
2020-10-22 19:49:02 +02:00
|
|
|
// Name is the cache type name
|
|
|
|
func (d *DiskStore) Name() string { return nameDisk }
|
2018-01-31 02:15:21 +01:00
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
// Has returns T/F or Error if it the blob stored already. It will error with any IO disk error.
|
2020-10-22 19:12:31 +02:00
|
|
|
func (d *DiskStore) Has(hash string) (bool, error) {
|
2021-05-21 01:59:50 +02:00
|
|
|
d.lock.RLock(hash)
|
|
|
|
defer d.lock.RUnlock(hash)
|
2019-10-03 22:34:57 +02:00
|
|
|
err := d.initOnce()
|
2018-01-31 02:15:21 +01:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2020-11-02 20:35:04 +01:00
|
|
|
_, err = os.Stat(d.path(hash))
|
2020-10-22 18:18:31 +02:00
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, errors.Err(err)
|
|
|
|
}
|
|
|
|
return true, nil
|
2018-01-31 02:15:21 +01:00
|
|
|
}
|
|
|
|
|
2019-10-03 22:34:57 +02:00
|
|
|
// Get returns the blob or an error if the blob doesn't exist.
|
2021-01-09 05:08:20 +01:00
|
|
|
func (d *DiskStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
2021-05-21 01:59:50 +02:00
|
|
|
d.lock.RLock(hash)
|
|
|
|
defer d.lock.RUnlock(hash)
|
2021-01-09 05:08:20 +01:00
|
|
|
start := time.Now()
|
2019-10-03 22:34:57 +02:00
|
|
|
err := d.initOnce()
|
2018-01-31 02:15:21 +01:00
|
|
|
if err != nil {
|
2021-01-09 05:08:20 +01:00
|
|
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), err
|
2018-01-31 02:15:21 +01:00
|
|
|
}
|
|
|
|
|
2020-11-21 17:39:15 +01:00
|
|
|
blob, err := ioutil.ReadFile(d.path(hash))
|
2018-01-31 02:15:21 +01:00
|
|
|
if err != nil {
|
2018-02-07 21:21:20 +01:00
|
|
|
if os.IsNotExist(err) {
|
2021-01-09 05:08:20 +01:00
|
|
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), errors.Err(ErrBlobNotFound)
|
2018-02-07 21:21:20 +01:00
|
|
|
}
|
2021-01-09 05:08:20 +01:00
|
|
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), errors.Err(err)
|
2018-01-31 02:15:21 +01:00
|
|
|
}
|
2021-04-07 04:46:18 +02:00
|
|
|
|
|
|
|
// this is a rather poor yet effective way of throttling how many blobs can be checked concurrently
|
|
|
|
// poor because there is a possible race condition between the check and the actual +1
|
|
|
|
if d.concurrentChecks.Load() < maxConcurrentChecks {
|
|
|
|
d.concurrentChecks.Add(1)
|
|
|
|
defer d.concurrentChecks.Sub(1)
|
|
|
|
hashBytes := sha512.Sum384(blob)
|
|
|
|
readHash := hex.EncodeToString(hashBytes[:])
|
|
|
|
if hash != readHash {
|
|
|
|
message := fmt.Sprintf("[%s] found a broken blob while reading from disk. Actual hash: %s", hash, readHash)
|
|
|
|
log.Errorf("%s", message)
|
|
|
|
err := d.Delete(hash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), err
|
|
|
|
}
|
|
|
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), errors.Err(message)
|
2021-04-05 23:34:45 +02:00
|
|
|
}
|
|
|
|
}
|
2018-01-31 02:15:21 +01:00
|
|
|
|
2021-01-09 05:08:20 +01:00
|
|
|
return blob, shared.NewBlobTrace(time.Since(start), d.Name()), nil
|
2018-01-31 02:15:21 +01:00
|
|
|
}
|
|
|
|
|
2018-07-26 16:25:47 +02:00
|
|
|
// Put stores the blob on disk
|
2020-10-22 19:12:31 +02:00
|
|
|
func (d *DiskStore) Put(hash string, blob stream.Blob) error {
|
2021-05-21 01:59:50 +02:00
|
|
|
d.lock.Lock(hash)
|
|
|
|
defer d.lock.Unlock(hash)
|
2021-05-21 01:48:46 +02:00
|
|
|
start := time.Now()
|
|
|
|
defer func() {
|
|
|
|
if time.Since(start) > 100*time.Millisecond {
|
|
|
|
log.Infof("it took %s to write %s", time.Since(start), hash)
|
|
|
|
}
|
|
|
|
}()
|
2019-10-03 22:34:57 +02:00
|
|
|
err := d.initOnce()
|
2018-01-31 02:15:21 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-03 22:34:57 +02:00
|
|
|
err = d.ensureDirExists(d.dir(hash))
|
2019-10-03 22:12:49 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-21 01:48:46 +02:00
|
|
|
hashBytes := sha512.Sum384(blob)
|
|
|
|
readHash := hex.EncodeToString(hashBytes[:])
|
|
|
|
matchesBeforeWriting := readHash == hash
|
2021-05-21 02:41:47 +02:00
|
|
|
writeFile(d.path(hash), blob, 0644)
|
2021-05-21 01:48:46 +02:00
|
|
|
readBlob, err := ioutil.ReadFile(d.path(hash))
|
|
|
|
matchesAfterReading := false
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("for some fucking reasons I can't read the blob I just wrote %s", err.Error())
|
|
|
|
} else {
|
|
|
|
hashBytes = sha512.Sum384(readBlob)
|
|
|
|
readHash = hex.EncodeToString(hashBytes[:])
|
|
|
|
matchesAfterReading = readHash == hash
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof(`writing %s to disk: hash match: %t, error: %t
|
|
|
|
reading after writing: hash match: %t`, hash, matchesBeforeWriting, err == nil, matchesAfterReading)
|
|
|
|
|
2020-10-22 18:18:31 +02:00
|
|
|
return errors.Err(err)
|
2018-01-31 02:15:21 +01:00
|
|
|
}
|
2018-02-02 22:49:20 +01:00
|
|
|
|
2018-07-26 16:25:47 +02:00
|
|
|
// PutSD stores the sd blob on the disk
|
2020-10-22 19:12:31 +02:00
|
|
|
func (d *DiskStore) PutSD(hash string, blob stream.Blob) error {
|
2021-05-21 01:59:50 +02:00
|
|
|
d.lock.Lock(hash)
|
|
|
|
defer d.lock.Unlock(hash)
|
2019-10-03 22:34:57 +02:00
|
|
|
return d.Put(hash, blob)
|
2018-02-02 22:49:20 +01:00
|
|
|
}
|
2018-09-11 13:41:29 +02:00
|
|
|
|
|
|
|
// Delete deletes the blob from the store
|
2020-10-22 19:12:31 +02:00
|
|
|
func (d *DiskStore) Delete(hash string) error {
|
2021-05-21 01:59:50 +02:00
|
|
|
d.lock.Lock(hash)
|
|
|
|
defer d.lock.Unlock(hash)
|
2019-10-03 22:34:57 +02:00
|
|
|
err := d.initOnce()
|
2018-09-11 13:41:29 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-22 18:18:31 +02:00
|
|
|
has, err := d.Has(hash)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !has {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-02 20:35:04 +01:00
|
|
|
err = os.Remove(d.path(hash))
|
2020-10-22 18:18:31 +02:00
|
|
|
return errors.Err(err)
|
2019-11-14 00:50:49 +01:00
|
|
|
}
|
|
|
|
|
2020-10-26 17:27:27 +01:00
|
|
|
// list returns the hashes of blobs that already exist in the blobDir
|
2020-10-22 19:12:31 +02:00
|
|
|
func (d *DiskStore) list() ([]string, error) {
|
2020-10-26 17:27:27 +01:00
|
|
|
err := d.initOnce()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-11-02 20:48:56 +01:00
|
|
|
return speedwalk.AllFiles(d.blobDir, true)
|
2019-11-14 00:50:49 +01:00
|
|
|
}
|
2020-10-22 19:49:02 +02:00
|
|
|
|
|
|
|
func (d *DiskStore) dir(hash string) string {
|
|
|
|
if d.prefixLength <= 0 || len(hash) < d.prefixLength {
|
|
|
|
return d.blobDir
|
|
|
|
}
|
|
|
|
return path.Join(d.blobDir, hash[:d.prefixLength])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DiskStore) path(hash string) string {
|
|
|
|
return path.Join(d.dir(hash), hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DiskStore) ensureDirExists(dir string) error {
|
2020-11-02 20:35:04 +01:00
|
|
|
return errors.Err(os.MkdirAll(dir, 0755))
|
2020-10-22 19:49:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DiskStore) initOnce() error {
|
|
|
|
if d.initialized {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err := d.ensureDirExists(d.blobDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.initialized = true
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-23 06:04:42 +01:00
|
|
|
|
2021-05-21 02:41:47 +02:00
|
|
|
type writeRequest struct {
|
|
|
|
filename string
|
|
|
|
data []byte
|
|
|
|
perm os.FileMode
|
|
|
|
}
|
|
|
|
|
2020-12-23 06:04:42 +01:00
|
|
|
// Shutdown shuts down the store gracefully
|
|
|
|
func (d *DiskStore) Shutdown() {
|
|
|
|
return
|
|
|
|
}
|
2021-05-21 02:41:47 +02:00
|
|
|
|
|
|
|
func writeFile(filename string, data []byte, perm os.FileMode) {
|
|
|
|
writeCh <- writeRequest{
|
|
|
|
filename: filename,
|
|
|
|
data: data,
|
|
|
|
perm: perm,
|
|
|
|
}
|
|
|
|
}
|