|
|
|
@@ -1,23 +1,50 @@ |
|
|
|
package cache |
|
|
|
|
|
|
|
import ( |
|
|
|
"context" |
|
|
|
"io" |
|
|
|
"os" |
|
|
|
"path/filepath" |
|
|
|
"sync" |
|
|
|
"time" |
|
|
|
|
|
|
|
"gitlink.org.cn/cloudream/common/pkgs/future" |
|
|
|
cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" |
|
|
|
"gitlink.org.cn/cloudream/common/utils/lo2" |
|
|
|
"gitlink.org.cn/cloudream/common/utils/math2" |
|
|
|
"gitlink.org.cn/cloudream/common/utils/serder" |
|
|
|
"gitlink.org.cn/cloudream/storage/client2/internal/mount/fuse" |
|
|
|
"gitlink.org.cn/cloudream/storage/common/pkgs/downloader" |
|
|
|
) |
|
|
|
|
|
|
|
type FileInfo struct { |
|
|
|
Dirty bool // 本文件是否有未提交的修改 |
|
|
|
Segments []Range // 数据段列表,按照段开始位置从小到大排列 |
|
|
|
ObjectID cdssdk.ObjectID // 文件对应的对象ID,仅在文件是一个缓存文件时才有值 |
|
|
|
Hash string // 如果本文件完全是一个缓存文件,那么这个字段记录了其内容的哈希值,用于在下载缓存数据时,检查远端文件是否被修改过 |
|
|
|
Size int64 // 文件大小。如果是缓存文件,那么这个字段记录了其内容的总大小,用于判断文件是否完整。如果是本地文件,那么这个字段记录了其实际大小。 |
|
|
|
ModTime time.Time // 文件的最后修改时间 |
|
|
|
Perm os.FileMode // 文件的权限 |
|
|
|
// 文件总大小。可能会超过对应的远端文件的大小。 |
|
|
|
// 此大小可能与本地缓存文件大小也不用,需要定时将本地缓存文件大小修正到与这个值相同。 |
|
|
|
Size int64 |
|
|
|
// 本文件是否有未提交的修改 |
|
|
|
Dirty bool |
|
|
|
// 数据段列表,按照段开始位置从小到大排列 |
|
|
|
Segments []Range |
|
|
|
// 文件对应的对象ID,仅在文件是一个缓存文件时才有值 |
|
|
|
ObjectID cdssdk.ObjectID |
|
|
|
// 文件对应的对象大小,仅在文件是一个缓存文件时才有值。 |
|
|
|
// 此值代表有多少数据应该从远端加载,所以可能会小于远端实际大小 |
|
|
|
ObjectSize int64 |
|
|
|
// 如果本文件完全是一个缓存文件,那么这个字段记录了其内容的哈希值,用于在下载缓存数据时,检查远端文件是否被修改过 |
|
|
|
Hash string |
|
|
|
// 文件的最后修改时间 |
|
|
|
ModTime time.Time |
|
|
|
// 文件的权限 |
|
|
|
Perm os.FileMode |
|
|
|
} |
|
|
|
|
|
|
|
// 返回值代表文件大小是否发生了变化 |
|
|
|
func (f *FileInfo) EnlargeTo(size int64) bool { |
|
|
|
if size > f.Size { |
|
|
|
f.Size = size |
|
|
|
return true |
|
|
|
} |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
type Range struct { |
|
|
|
@@ -25,16 +52,54 @@ type Range struct { |
|
|
|
Length int64 |
|
|
|
} |
|
|
|
|
|
|
|
func (r *Range) GetPosition() int64 { |
|
|
|
return r.Position |
|
|
|
} |
|
|
|
|
|
|
|
func (r *Range) GetLength() int64 { |
|
|
|
return r.Length |
|
|
|
} |
|
|
|
|
|
|
|
type ReadRequest struct { |
|
|
|
Position int64 |
|
|
|
Length int64 |
|
|
|
Callback *future.SetVoidFuture |
|
|
|
Zeros int64 // 如果>0,那么说明读到了一块全0的数据,值的大小代表这块数据的长度,此时Segment字段为nil |
|
|
|
Segment *FileSegment |
|
|
|
} |
|
|
|
|
|
|
|
func (r *ReadRequest) GetPosition() int64 { |
|
|
|
return r.Position |
|
|
|
} |
|
|
|
|
|
|
|
func (r *ReadRequest) GetLength() int64 { |
|
|
|
return r.Length |
|
|
|
} |
|
|
|
|
|
|
|
type WriteReqeust struct { |
|
|
|
Position int64 |
|
|
|
Length int64 |
|
|
|
Callback *future.SetVoidFuture |
|
|
|
Segment *FileSegment |
|
|
|
} |
|
|
|
|
|
|
|
// 所有读写过程共用同一个CacheFile对象。 |
|
|
|
// 不应该将此结构体保存到对象中 |
|
|
|
type CacheFile struct { |
|
|
|
pathComps []string |
|
|
|
name string |
|
|
|
info FileInfo |
|
|
|
lock sync.RWMutex |
|
|
|
segBuffer SegmentBuffer |
|
|
|
readers int |
|
|
|
writers int |
|
|
|
objDownloader *downloader.Downloader |
|
|
|
pathComps []string |
|
|
|
name string |
|
|
|
info FileInfo |
|
|
|
lock sync.Mutex |
|
|
|
segBuffer SegmentBuffer |
|
|
|
readers int |
|
|
|
writers int |
|
|
|
|
|
|
|
localLoaders []*LocalLoader |
|
|
|
remoteLoaders []*RemoteLoader |
|
|
|
pendingReadings []*ReadRequest |
|
|
|
pendingWritings []*WriteReqeust |
|
|
|
managerChan chan any |
|
|
|
} |
|
|
|
|
|
|
|
func loadCacheFile(pathComps []string, infoPath string) (*CacheFile, error) { |
|
|
|
@@ -109,9 +174,29 @@ func (f *CacheFile) Release() { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 一个文件段的数据被写入到本地了 |
|
|
|
func (f *CacheFile) OnSaved(seg *FileSegment) { |
|
|
|
// 一个文件段的数据被写入到本地了。err为nil表示成功,否则表示写入失败。 |
|
|
|
func (f *CacheFile) OnSaved(seg *FileSegment, err error) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 一个文件段的数据被加载到内存了。err为nil表示成功,否则表示加载失败。 |
|
|
|
func (f *CacheFile) OnLoaded(seg *FileSegment, err error) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
func (f *CacheFile) notifyManager() { |
|
|
|
select { |
|
|
|
case f.managerChan <- nil: |
|
|
|
break |
|
|
|
default: |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (f *CacheFile) managing() { |
|
|
|
for { |
|
|
|
<-f.managerChan |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
type CacheFileReadWriter struct { |
|
|
|
@@ -120,11 +205,143 @@ type CacheFileReadWriter struct { |
|
|
|
} |
|
|
|
|
|
|
|
func (f *CacheFileReadWriter) ReadAt(buf []byte, off int64) (int, error) { |
|
|
|
f.file.lock.Lock() |
|
|
|
|
|
|
|
if off >= f.file.info.Size { |
|
|
|
return 0, io.EOF |
|
|
|
} |
|
|
|
|
|
|
|
segIdx := f.file.segBuffer.FirstContains(off) |
|
|
|
|
|
|
|
if segIdx >= 0 { |
|
|
|
seg := f.file.segBuffer.Segment(segIdx) |
|
|
|
|
|
|
|
// 读取的数据在当前段内 |
|
|
|
if off >= seg.Position && off < seg.Position+seg.Length { |
|
|
|
readLen := math2.Min(int64(len(buf)), seg.Position+seg.Length-off) |
|
|
|
seg.RefCount++ |
|
|
|
f.file.lock.Unlock() |
|
|
|
|
|
|
|
copy(buf[:readLen], seg.SubSliceAbs(off, readLen)) |
|
|
|
|
|
|
|
f.file.lock.Lock() |
|
|
|
seg.RefCount-- |
|
|
|
seg.Type = SegmentDirty |
|
|
|
f.file.lock.Unlock() |
|
|
|
return int(readLen), nil |
|
|
|
} |
|
|
|
|
|
|
|
// if off >= f.file.info.ObjectSize { |
|
|
|
// readLen := int64(len(buf)) |
|
|
|
|
|
|
|
// if segIdx < f.file.segBuffer.BuzyCount()-1 { |
|
|
|
// nextSeg := f.file.segBuffer.Segment(segIdx + 1) |
|
|
|
// readLen = math2.Min(readLen, nextSeg.Position-off) |
|
|
|
// } else { |
|
|
|
// readLen = math2.Min(readLen, f.file.info.Size-off) |
|
|
|
// } |
|
|
|
// f.file.lock.Unlock() |
|
|
|
|
|
|
|
// clear(buf[:readLen]) |
|
|
|
// return int(readLen), nil |
|
|
|
// } |
|
|
|
} |
|
|
|
|
|
|
|
// 没有被缓存的数据,则需要通知加载器去加载 |
|
|
|
|
|
|
|
fut := future.NewSetVoid() |
|
|
|
req := &ReadRequest{ |
|
|
|
Position: off, |
|
|
|
Callback: fut, |
|
|
|
} |
|
|
|
insertIdx := FirstContains(f.file.pendingReadings, off) |
|
|
|
f.file.pendingReadings = lo2.Insert(f.file.pendingReadings, insertIdx+1, req) |
|
|
|
f.file.lock.Unlock() |
|
|
|
|
|
|
|
f.file.notifyManager() |
|
|
|
|
|
|
|
err := fut.Wait(context.Background()) |
|
|
|
if err != nil { |
|
|
|
return 0, err |
|
|
|
} |
|
|
|
|
|
|
|
if req.Segment != nil { |
|
|
|
// 这里不加锁,因为在引用计数大于0期间,Position不变,而Length虽然可能发生变化,但并发读写是未定义行为,所以不管读到多少数据都可以 |
|
|
|
readLen := math2.Min(int64(len(buf)), req.Segment.Position+req.Segment.Length-off) |
|
|
|
copy(buf[:readLen], req.Segment.SubSliceAbs(off, readLen)) |
|
|
|
|
|
|
|
// bufferManager线程会在调用回调之前,给引用计数+1 |
|
|
|
// TODO 这种做法容易粗心产生遗漏,且不利于实现超时机制 |
|
|
|
f.file.lock.Lock() |
|
|
|
req.Segment.RefCount-- |
|
|
|
f.file.lock.Unlock() |
|
|
|
return int(readLen), nil |
|
|
|
} |
|
|
|
|
|
|
|
if req.Zeros > 0 { |
|
|
|
clear(buf[:req.Zeros]) |
|
|
|
return int(req.Zeros), nil |
|
|
|
} |
|
|
|
|
|
|
|
return 0, io.EOF |
|
|
|
} |
|
|
|
|
|
|
|
func (f *CacheFileReadWriter) WriteAt(buf []byte, off int64) (int, error) { |
|
|
|
if f.readOnly { |
|
|
|
return 0, fuse.ErrPermission |
|
|
|
} |
|
|
|
|
|
|
|
f.file.lock.Lock() |
|
|
|
// 如果找到一个包含off位置的段,那么就先写满这个段 |
|
|
|
segIdx := f.file.segBuffer.FirstContains(off) |
|
|
|
if segIdx >= 0 { |
|
|
|
seg := f.file.segBuffer.Segment(segIdx) |
|
|
|
|
|
|
|
if off >= seg.Position && off < seg.Position+seg.Length { |
|
|
|
writeLen := math2.Min(int64(len(buf)), seg.BufferEnd()-off) |
|
|
|
seg.RefCount++ |
|
|
|
f.file.lock.Unlock() |
|
|
|
|
|
|
|
// 不管此时是不是有其他线程在读取数据,因为我们约定并发读写就是未定义行为。 |
|
|
|
copy(seg.SubSliceAbs(off, writeLen), buf[:writeLen]) |
|
|
|
|
|
|
|
f.file.lock.Lock() |
|
|
|
seg.RefCount-- |
|
|
|
seg.EnlargeTo(off + writeLen) |
|
|
|
seg.Type = SegmentDirty |
|
|
|
f.file.info.EnlargeTo(seg.AvailableEnd()) |
|
|
|
f.file.lock.Unlock() |
|
|
|
f.file.notifyManager() |
|
|
|
return int(writeLen), nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fut := future.NewSetVoid() |
|
|
|
req := &WriteReqeust{ |
|
|
|
Position: off, |
|
|
|
Callback: fut, |
|
|
|
} |
|
|
|
f.file.pendingWritings = append(f.file.pendingWritings, req) |
|
|
|
f.file.lock.Unlock() |
|
|
|
|
|
|
|
err := fut.Wait(context.Background()) |
|
|
|
if err != nil { |
|
|
|
return 0, err |
|
|
|
} |
|
|
|
|
|
|
|
// 一些说明参考ReadAt函数 |
|
|
|
// 由managing线程保证文件的同一个位置只会有一个段,因此这里不考虑在copy期间又有其他线程在写同一个段导致的产生新的重复段 |
|
|
|
writeLen := math2.Min(int64(len(buf)), req.Segment.BufferEnd()-off) |
|
|
|
copy(req.Segment.SubSliceAbs(off, writeLen), buf[:writeLen]) |
|
|
|
|
|
|
|
f.file.lock.Lock() |
|
|
|
req.Segment.RefCount-- |
|
|
|
req.Segment.EnlargeTo(off + writeLen) |
|
|
|
req.Segment.Type = SegmentDirty |
|
|
|
f.file.info.EnlargeTo(req.Segment.AvailableEnd()) |
|
|
|
f.file.lock.Unlock() |
|
|
|
f.file.notifyManager() |
|
|
|
return int(writeLen), nil |
|
|
|
} |
|
|
|
|
|
|
|
func (f *CacheFileReadWriter) Sync() error { |
|
|
|
|