| @@ -0,0 +1,104 @@ | |||
| package downloader | |||
| import ( | |||
| "sort" | |||
| lru "github.com/hashicorp/golang-lru/v2" | |||
| "gitlink.org.cn/cloudream/common/utils/lo2" | |||
| ) | |||
| type LRUID int64 | |||
| type cacheFile struct { | |||
| Segments []*fileSegment | |||
| } | |||
| type fileSegment struct { | |||
| LRUID LRUID | |||
| Offset int64 | |||
| Data []byte | |||
| } | |||
| type lruEntry struct { | |||
| FileHash string | |||
| Offset int64 | |||
| } | |||
| type Cache struct { | |||
| lru *lru.Cache[LRUID, lruEntry] | |||
| nextLRUID LRUID | |||
| files map[string]*cacheFile | |||
| } | |||
| func NewCache(size int) *Cache { | |||
| c := &Cache{ | |||
| files: make(map[string]*cacheFile), | |||
| } | |||
| lru, _ := lru.NewWithEvict(size, c.onEvict) | |||
| c.lru = lru | |||
| return c | |||
| } | |||
| func (c *Cache) Put(fileHash string, offset int64, data []byte) { | |||
| file, ok := c.files[fileHash] | |||
| if !ok { | |||
| file = &cacheFile{} | |||
| c.files[fileHash] = file | |||
| } | |||
| idx := sort.Search(len(file.Segments), upperBound(file.Segments, offset)) | |||
| // 允许不同Segment之间有重叠,只在Offset相等时替换数据 | |||
| if idx < len(file.Segments) && file.Segments[idx].Offset == offset { | |||
| file.Segments[idx].Data = data | |||
| // Get一下更新LRU | |||
| c.lru.Get(file.Segments[idx].LRUID) | |||
| } else { | |||
| file.Segments = lo2.Insert(file.Segments, idx, &fileSegment{ | |||
| LRUID: c.nextLRUID, | |||
| Offset: offset, | |||
| Data: data, | |||
| }) | |||
| c.lru.Add(c.nextLRUID, lruEntry{ | |||
| FileHash: fileHash, | |||
| Offset: offset, | |||
| }) | |||
| c.nextLRUID++ | |||
| } | |||
| } | |||
| func (c *Cache) Get(fileHash string, offset int64) []byte { | |||
| file, ok := c.files[fileHash] | |||
| if !ok { | |||
| return nil | |||
| } | |||
| idx := sort.Search(len(file.Segments), upperBound(file.Segments, offset)) | |||
| if idx == 0 { | |||
| return nil | |||
| } | |||
| seg := file.Segments[idx-1] | |||
| // Get一下更新LRU | |||
| c.lru.Get(seg.LRUID) | |||
| return seg.Data[offset-seg.Offset:] | |||
| } | |||
| func (c *Cache) onEvict(key LRUID, value lruEntry) { | |||
| // 不应该找不到文件或者分片 | |||
| file := c.files[value.FileHash] | |||
| idx := sort.Search(len(file.Segments), upperBound(file.Segments, value.Offset)) | |||
| file.Segments = lo2.RemoveAt(file.Segments, idx) | |||
| if len(file.Segments) == 0 { | |||
| delete(c.files, value.FileHash) | |||
| } | |||
| } | |||
| // 使用此函数会找到第一个大于等于 target 的索引,如果找不到,则返回 len(seg) | |||
| func upperBound(seg []*fileSegment, target int64) func(int) bool { | |||
| return func(i int) bool { | |||
| return seg[i].Offset >= target | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| package downloader | |||
| type Config struct { | |||
| // EC模式的Object的条带缓存数量 | |||
| MaxStripCacheCount int `json:"maxStripCacheCount"` | |||
| // EC模式下,每个Object的条带的预取数量,最少为1 | |||
| ECStripPrefetchCount int `json:"ecStripPrefetchCount"` | |||
| } | |||
| @@ -0,0 +1,154 @@ | |||
| package downloader | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| lru "github.com/hashicorp/golang-lru/v2" | |||
| "gitlink.org.cn/cloudream/common/pkgs/iterator" | |||
| "gitlink.org.cn/cloudream/storage2/client/internal/db" | |||
| "gitlink.org.cn/cloudream/storage2/client/types" | |||
| stgglb "gitlink.org.cn/cloudream/storage2/common/globals" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/connectivity" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/downloader/strategy" | |||
| coormq "gitlink.org.cn/cloudream/storage2/common/pkgs/mq/coordinator" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/storage/agtpool" | |||
| ) | |||
| const ( | |||
| DefaultMaxStripCacheCount = 128 | |||
| ) | |||
| type DownloadIterator = iterator.Iterator[*Downloading] | |||
| type DownloadReqeust struct { | |||
| ObjectID types.ObjectID | |||
| Offset int64 | |||
| Length int64 | |||
| } | |||
| type downloadReqeust2 struct { | |||
| Detail *types.ObjectDetail | |||
| Raw DownloadReqeust | |||
| } | |||
| type Downloading struct { | |||
| Object *types.Object | |||
| File io.ReadCloser // 文件流,如果文件不存在,那么为nil | |||
| Request DownloadReqeust | |||
| } | |||
| type Downloader struct { | |||
| strips *StripCache | |||
| cfg Config | |||
| conn *connectivity.Collector | |||
| stgAgts *agtpool.AgentPool | |||
| selector *strategy.Selector | |||
| db *db.DB | |||
| } | |||
| func NewDownloader(cfg Config, conn *connectivity.Collector, stgAgts *agtpool.AgentPool, sel *strategy.Selector, db *db.DB) Downloader { | |||
| if cfg.MaxStripCacheCount == 0 { | |||
| cfg.MaxStripCacheCount = DefaultMaxStripCacheCount | |||
| } | |||
| ch, _ := lru.New[ECStripKey, ObjectECStrip](cfg.MaxStripCacheCount) | |||
| return Downloader{ | |||
| strips: ch, | |||
| cfg: cfg, | |||
| conn: conn, | |||
| stgAgts: stgAgts, | |||
| selector: sel, | |||
| db: db, | |||
| } | |||
| } | |||
| func (d *Downloader) DownloadObjects(reqs []DownloadReqeust) DownloadIterator { | |||
| coorCli, err := stgglb.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return iterator.FuseError[*Downloading](fmt.Errorf("new coordinator client: %w", err)) | |||
| } | |||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | |||
| objIDs := make([]types.ObjectID, len(reqs)) | |||
| for i, req := range reqs { | |||
| objIDs[i] = req.ObjectID | |||
| } | |||
| if len(objIDs) == 0 { | |||
| return iterator.Empty[*Downloading]() | |||
| } | |||
| // objDetails, err := coorCli.GetObjectDetails(coormq.ReqGetObjectDetails(objIDs)) | |||
| // if err != nil { | |||
| // return iterator.FuseError[*Downloading](fmt.Errorf("request to coordinator: %w", err)) | |||
| // } | |||
| objDetails, err := d.db.GetObjectDetails(objIDs) | |||
| if err != nil { | |||
| return iterator.FuseError[*Downloading](fmt.Errorf("request to db: %w", err)) | |||
| } | |||
| req2s := make([]downloadReqeust2, len(reqs)) | |||
| for i, req := range reqs { | |||
| req2s[i] = downloadReqeust2{ | |||
| Detail: objDetails.Objects[i], | |||
| Raw: req, | |||
| } | |||
| } | |||
| return NewDownloadObjectIterator(d, req2s) | |||
| } | |||
| func (d *Downloader) DownloadObjectByDetail(detail types.ObjectDetail, off int64, length int64) (*Downloading, error) { | |||
| req2s := []downloadReqeust2{{ | |||
| Detail: &detail, | |||
| Raw: DownloadReqeust{ | |||
| ObjectID: detail.Object.ObjectID, | |||
| Offset: off, | |||
| Length: length, | |||
| }, | |||
| }} | |||
| iter := NewDownloadObjectIterator(d, req2s) | |||
| return iter.MoveNext() | |||
| } | |||
| func (d *Downloader) DownloadPackage(pkgID types.PackageID) DownloadIterator { | |||
| coorCli, err := stgglb.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return iterator.FuseError[*Downloading](fmt.Errorf("new coordinator client: %w", err)) | |||
| } | |||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | |||
| pkgDetail, err := coorCli.GetPackageObjectDetails(coormq.ReqGetPackageObjectDetails(pkgID)) | |||
| if err != nil { | |||
| return iterator.FuseError[*Downloading](fmt.Errorf("request to coordinator: %w", err)) | |||
| } | |||
| req2s := make([]downloadReqeust2, len(pkgDetail.Objects)) | |||
| for i, objDetail := range pkgDetail.Objects { | |||
| dt := objDetail | |||
| req2s[i] = downloadReqeust2{ | |||
| Detail: &dt, | |||
| Raw: DownloadReqeust{ | |||
| ObjectID: objDetail.Object.ObjectID, | |||
| Offset: 0, | |||
| Length: objDetail.Object.Size, | |||
| }, | |||
| } | |||
| } | |||
| return NewDownloadObjectIterator(d, req2s) | |||
| } | |||
| type ObjectECStrip struct { | |||
| Data []byte | |||
| ObjectFileHash types.FileHash // 添加这条缓存时,Object的FileHash | |||
| } | |||
| type ECStripKey struct { | |||
| ObjectID types.ObjectID | |||
| StripIndex int64 | |||
| } | |||
| type StripCache = lru.Cache[ECStripKey, ObjectECStrip] | |||
| @@ -0,0 +1,204 @@ | |||
| package downloader | |||
| import ( | |||
| "context" | |||
| "fmt" | |||
| "io" | |||
| "reflect" | |||
| "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec" | |||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||
| "gitlink.org.cn/cloudream/common/utils/io2" | |||
| "gitlink.org.cn/cloudream/common/utils/math2" | |||
| stgglb "gitlink.org.cn/cloudream/storage2/common/globals" | |||
| stgmod "gitlink.org.cn/cloudream/storage2/common/models" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/downloader/strategy" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitch2" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitch2/parser" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/iterator" | |||
| ) | |||
| type downloadStorageInfo struct { | |||
| Storage stgmod.StorageDetail | |||
| ObjectPinned bool | |||
| Blocks []stgmod.ObjectBlock | |||
| Distance float64 | |||
| } | |||
| type DownloadContext struct { | |||
| Distlock *distlock.Service | |||
| } | |||
| type DownloadObjectIterator struct { | |||
| OnClosing func() | |||
| downloader *Downloader | |||
| reqs []downloadReqeust2 | |||
| currentIndex int | |||
| } | |||
| func NewDownloadObjectIterator(downloader *Downloader, downloadObjs []downloadReqeust2) *DownloadObjectIterator { | |||
| return &DownloadObjectIterator{ | |||
| downloader: downloader, | |||
| reqs: downloadObjs, | |||
| } | |||
| } | |||
| func (i *DownloadObjectIterator) MoveNext() (*Downloading, error) { | |||
| if i.currentIndex >= len(i.reqs) { | |||
| return nil, iterator.ErrNoMoreItem | |||
| } | |||
| req := i.reqs[i.currentIndex] | |||
| if req.Detail == nil { | |||
| return &Downloading{ | |||
| Object: nil, | |||
| File: nil, | |||
| Request: req.Raw, | |||
| }, nil | |||
| } | |||
| destHub := cdssdk.HubID(0) | |||
| if stgglb.Local.HubID != nil { | |||
| destHub = *stgglb.Local.HubID | |||
| } | |||
| strg, err := i.downloader.selector.Select(strategy.Request{ | |||
| Detail: *req.Detail, | |||
| Range: math2.NewRange(req.Raw.Offset, req.Raw.Length), | |||
| DestHub: destHub, | |||
| DestLocation: stgglb.Local.LocationID, | |||
| }) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("selecting download strategy: %w", err) | |||
| } | |||
| var reader io.ReadCloser | |||
| switch strg := strg.(type) { | |||
| case *strategy.DirectStrategy: | |||
| reader, err = i.downloadDirect(req, *strg) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("downloading object %v: %w", req.Raw.ObjectID, err) | |||
| } | |||
| case *strategy.ECReconstructStrategy: | |||
| reader, err = i.downloadECReconstruct(req, *strg) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("downloading ec object %v: %w", req.Raw.ObjectID, err) | |||
| } | |||
| case *strategy.LRCReconstructStrategy: | |||
| reader, err = i.downloadLRCReconstruct(req, *strg) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("downloading lrc object %v: %w", req.Raw.ObjectID, err) | |||
| } | |||
| default: | |||
| return nil, fmt.Errorf("unsupported strategy type: %v", reflect.TypeOf(strg)) | |||
| } | |||
| i.currentIndex++ | |||
| return &Downloading{ | |||
| Object: &req.Detail.Object, | |||
| File: reader, | |||
| Request: req.Raw, | |||
| }, nil | |||
| } | |||
| func (i *DownloadObjectIterator) Close() { | |||
| if i.OnClosing != nil { | |||
| i.OnClosing() | |||
| } | |||
| } | |||
| func (i *DownloadObjectIterator) downloadDirect(req downloadReqeust2, strg strategy.DirectStrategy) (io.ReadCloser, error) { | |||
| logger.Debugf("downloading object %v from storage %v", req.Raw.ObjectID, strg.Storage.Storage.String()) | |||
| var strHandle *exec.DriverReadStream | |||
| ft := ioswitch2.NewFromTo() | |||
| toExec, handle := ioswitch2.NewToDriver(ioswitch2.RawStream()) | |||
| toExec.Range = math2.Range{ | |||
| Offset: req.Raw.Offset, | |||
| } | |||
| if req.Raw.Length != -1 { | |||
| len := req.Raw.Length | |||
| toExec.Range.Length = &len | |||
| } | |||
| ft.AddFrom(ioswitch2.NewFromShardstore(req.Detail.Object.FileHash, *strg.Storage.MasterHub, strg.Storage, ioswitch2.RawStream())).AddTo(toExec) | |||
| strHandle = handle | |||
| plans := exec.NewPlanBuilder() | |||
| if err := parser.Parse(ft, plans); err != nil { | |||
| return nil, fmt.Errorf("parsing plan: %w", err) | |||
| } | |||
| exeCtx := exec.NewExecContext() | |||
| exec.SetValueByType(exeCtx, i.downloader.stgAgts) | |||
| exec := plans.Execute(exeCtx) | |||
| go exec.Wait(context.TODO()) | |||
| return exec.BeginRead(strHandle) | |||
| } | |||
| func (i *DownloadObjectIterator) downloadECReconstruct(req downloadReqeust2, strg strategy.ECReconstructStrategy) (io.ReadCloser, error) { | |||
| var logStrs []any = []any{fmt.Sprintf("downloading ec object %v from: ", req.Raw.ObjectID)} | |||
| for i, b := range strg.Blocks { | |||
| if i > 0 { | |||
| logStrs = append(logStrs, ", ") | |||
| } | |||
| logStrs = append(logStrs, fmt.Sprintf("%v@%v", b.Index, strg.Storages[i].Storage.String())) | |||
| } | |||
| logger.Debug(logStrs...) | |||
| downloadBlks := make([]downloadBlock, len(strg.Blocks)) | |||
| for i, b := range strg.Blocks { | |||
| downloadBlks[i] = downloadBlock{ | |||
| Block: b, | |||
| Storage: strg.Storages[i], | |||
| } | |||
| } | |||
| pr, pw := io.Pipe() | |||
| go func() { | |||
| readPos := req.Raw.Offset | |||
| totalReadLen := req.Detail.Object.Size - req.Raw.Offset | |||
| if req.Raw.Length >= 0 { | |||
| totalReadLen = math2.Min(req.Raw.Length, totalReadLen) | |||
| } | |||
| firstStripIndex := readPos / strg.Redundancy.StripSize() | |||
| stripIter := NewStripIterator(i.downloader, req.Detail.Object, downloadBlks, strg.Redundancy, firstStripIndex, i.downloader.strips, i.downloader.cfg.ECStripPrefetchCount) | |||
| defer stripIter.Close() | |||
| for totalReadLen > 0 { | |||
| strip, err := stripIter.MoveNext() | |||
| if err == iterator.ErrNoMoreItem { | |||
| pw.CloseWithError(io.ErrUnexpectedEOF) | |||
| return | |||
| } | |||
| if err != nil { | |||
| pw.CloseWithError(err) | |||
| return | |||
| } | |||
| readRelativePos := readPos - strip.Position | |||
| curReadLen := math2.Min(totalReadLen, strg.Redundancy.StripSize()-readRelativePos) | |||
| err = io2.WriteAll(pw, strip.Data[readRelativePos:readRelativePos+curReadLen]) | |||
| if err != nil { | |||
| pw.CloseWithError(err) | |||
| return | |||
| } | |||
| totalReadLen -= curReadLen | |||
| readPos += curReadLen | |||
| } | |||
| pw.Close() | |||
| }() | |||
| return pr, nil | |||
| } | |||
| @@ -0,0 +1,73 @@ | |||
| package downloader | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "gitlink.org.cn/cloudream/common/pkgs/iterator" | |||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||
| "gitlink.org.cn/cloudream/common/utils/io2" | |||
| "gitlink.org.cn/cloudream/common/utils/math2" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/downloader/strategy" | |||
| ) | |||
| func (iter *DownloadObjectIterator) downloadLRCReconstruct(req downloadReqeust2, strg strategy.LRCReconstructStrategy) (io.ReadCloser, error) { | |||
| var logStrs []any = []any{fmt.Sprintf("downloading lrc object %v from: ", req.Raw.ObjectID)} | |||
| for i, b := range strg.Blocks { | |||
| if i > 0 { | |||
| logStrs = append(logStrs, ", ") | |||
| } | |||
| logStrs = append(logStrs, fmt.Sprintf("%v@%v", b.Index, strg.Storages[i].Storage.String())) | |||
| } | |||
| logger.Debug(logStrs...) | |||
| downloadBlks := make([]downloadBlock, len(strg.Blocks)) | |||
| for i, b := range strg.Blocks { | |||
| downloadBlks[i] = downloadBlock{ | |||
| Block: b, | |||
| Storage: strg.Storages[i], | |||
| } | |||
| } | |||
| pr, pw := io.Pipe() | |||
| go func() { | |||
| readPos := req.Raw.Offset | |||
| totalReadLen := req.Detail.Object.Size - req.Raw.Offset | |||
| if req.Raw.Length >= 0 { | |||
| totalReadLen = math2.Min(req.Raw.Length, totalReadLen) | |||
| } | |||
| firstStripIndex := readPos / int64(strg.Redundancy.K) / int64(strg.Redundancy.ChunkSize) | |||
| stripIter := NewLRCStripIterator(iter.downloader, req.Detail.Object, downloadBlks, strg.Redundancy, firstStripIndex, iter.downloader.strips, iter.downloader.cfg.ECStripPrefetchCount) | |||
| defer stripIter.Close() | |||
| for totalReadLen > 0 { | |||
| strip, err := stripIter.MoveNext() | |||
| if err == iterator.ErrNoMoreItem { | |||
| pw.CloseWithError(io.ErrUnexpectedEOF) | |||
| return | |||
| } | |||
| if err != nil { | |||
| pw.CloseWithError(err) | |||
| return | |||
| } | |||
| readRelativePos := readPos - strip.Position | |||
| nextStripPos := strip.Position + int64(strg.Redundancy.K)*int64(strg.Redundancy.ChunkSize) | |||
| curReadLen := math2.Min(totalReadLen, nextStripPos-readPos) | |||
| err = io2.WriteAll(pw, strip.Data[readRelativePos:readRelativePos+curReadLen]) | |||
| if err != nil { | |||
| pw.CloseWithError(err) | |||
| return | |||
| } | |||
| totalReadLen -= curReadLen | |||
| readPos += curReadLen | |||
| } | |||
| pw.Close() | |||
| }() | |||
| return pr, nil | |||
| } | |||
| @@ -0,0 +1,199 @@ | |||
| package downloader | |||
| import ( | |||
| "context" | |||
| "io" | |||
| "sync" | |||
| "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec" | |||
| "gitlink.org.cn/cloudream/common/pkgs/iterator" | |||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||
| "gitlink.org.cn/cloudream/common/utils/math2" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitchlrc" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitchlrc/parser" | |||
| ) | |||
| type LRCStripIterator struct { | |||
| downloder *Downloader | |||
| object cdssdk.Object | |||
| blocks []downloadBlock | |||
| red cdssdk.LRCRedundancy | |||
| curStripIndex int64 | |||
| cache *StripCache | |||
| dataChan chan dataChanEntry | |||
| downloadingDone chan any | |||
| downloadingDoneOnce sync.Once | |||
| inited bool | |||
| } | |||
| func NewLRCStripIterator(downloder *Downloader, object cdssdk.Object, blocks []downloadBlock, red cdssdk.LRCRedundancy, beginStripIndex int64, cache *StripCache, maxPrefetch int) *LRCStripIterator { | |||
| if maxPrefetch <= 0 { | |||
| maxPrefetch = 1 | |||
| } | |||
| iter := &LRCStripIterator{ | |||
| downloder: downloder, | |||
| object: object, | |||
| blocks: blocks, | |||
| red: red, | |||
| curStripIndex: beginStripIndex, | |||
| cache: cache, | |||
| dataChan: make(chan dataChanEntry, maxPrefetch-1), | |||
| downloadingDone: make(chan any), | |||
| } | |||
| return iter | |||
| } | |||
| func (s *LRCStripIterator) MoveNext() (Strip, error) { | |||
| if !s.inited { | |||
| go s.downloading() | |||
| s.inited = true | |||
| } | |||
| // 先尝试获取一下,用于判断本次获取是否发生了等待 | |||
| select { | |||
| case entry, ok := <-s.dataChan: | |||
| if !ok || entry.Error == io.EOF { | |||
| return Strip{}, iterator.ErrNoMoreItem | |||
| } | |||
| if entry.Error != nil { | |||
| return Strip{}, entry.Error | |||
| } | |||
| s.curStripIndex++ | |||
| return Strip{Data: entry.Data, Position: entry.Position}, nil | |||
| default: | |||
| logger.Debugf("waitting for ec strip %v of object %v", s.curStripIndex, s.object.ObjectID) | |||
| } | |||
| // 发生了等待 | |||
| select { | |||
| case entry, ok := <-s.dataChan: | |||
| if !ok || entry.Error == io.EOF { | |||
| return Strip{}, iterator.ErrNoMoreItem | |||
| } | |||
| if entry.Error != nil { | |||
| return Strip{}, entry.Error | |||
| } | |||
| s.curStripIndex++ | |||
| return Strip{Data: entry.Data, Position: entry.Position}, nil | |||
| case <-s.downloadingDone: | |||
| return Strip{}, iterator.ErrNoMoreItem | |||
| } | |||
| } | |||
| func (s *LRCStripIterator) Close() { | |||
| s.downloadingDoneOnce.Do(func() { | |||
| close(s.downloadingDone) | |||
| }) | |||
| } | |||
| func (s *LRCStripIterator) downloading() { | |||
| var froms []ioswitchlrc.From | |||
| for _, b := range s.blocks { | |||
| stg := b.Storage | |||
| froms = append(froms, ioswitchlrc.NewFromStorage(b.Block.FileHash, *stg.MasterHub, stg.Storage, b.Block.Index)) | |||
| } | |||
| toExec, hd := ioswitchlrc.NewToDriverWithRange(-1, math2.Range{ | |||
| Offset: s.curStripIndex * int64(s.red.ChunkSize*s.red.K), | |||
| }) | |||
| plans := exec.NewPlanBuilder() | |||
| err := parser.ReconstructAny(froms, []ioswitchlrc.To{toExec}, plans) | |||
| if err != nil { | |||
| s.sendToDataChan(dataChanEntry{Error: err}) | |||
| return | |||
| } | |||
| exeCtx := exec.NewExecContext() | |||
| exec.SetValueByType(exeCtx, s.downloder.stgAgts) | |||
| exec := plans.Execute(exeCtx) | |||
| ctx, cancel := context.WithCancel(context.Background()) | |||
| go exec.Wait(ctx) | |||
| defer cancel() | |||
| str, err := exec.BeginRead(hd) | |||
| if err != nil { | |||
| s.sendToDataChan(dataChanEntry{Error: err}) | |||
| return | |||
| } | |||
| curStripIndex := s.curStripIndex | |||
| loop: | |||
| for { | |||
| stripBytesPos := curStripIndex * int64(s.red.K) * int64(s.red.ChunkSize) | |||
| if stripBytesPos >= s.object.Size { | |||
| s.sendToDataChan(dataChanEntry{Error: io.EOF}) | |||
| break | |||
| } | |||
| stripKey := ECStripKey{ | |||
| ObjectID: s.object.ObjectID, | |||
| StripIndex: curStripIndex, | |||
| } | |||
| item, ok := s.cache.Get(stripKey) | |||
| if ok { | |||
| if item.ObjectFileHash == s.object.FileHash { | |||
| if !s.sendToDataChan(dataChanEntry{Data: item.Data, Position: stripBytesPos}) { | |||
| break loop | |||
| } | |||
| curStripIndex++ | |||
| continue | |||
| } else { | |||
| // 如果Object的Hash和Cache的Hash不一致,说明Cache是无效的,需要重新下载 | |||
| s.cache.Remove(stripKey) | |||
| } | |||
| } | |||
| dataBuf := make([]byte, int64(s.red.K*s.red.ChunkSize)) | |||
| n, err := io.ReadFull(str, dataBuf) | |||
| if err == io.ErrUnexpectedEOF { | |||
| s.cache.Add(stripKey, ObjectECStrip{ | |||
| Data: dataBuf, | |||
| ObjectFileHash: s.object.FileHash, | |||
| }) | |||
| s.sendToDataChan(dataChanEntry{Data: dataBuf[:n], Position: stripBytesPos}) | |||
| s.sendToDataChan(dataChanEntry{Error: io.EOF}) | |||
| break loop | |||
| } | |||
| if err != nil { | |||
| s.sendToDataChan(dataChanEntry{Error: err}) | |||
| break loop | |||
| } | |||
| s.cache.Add(stripKey, ObjectECStrip{ | |||
| Data: dataBuf, | |||
| ObjectFileHash: s.object.FileHash, | |||
| }) | |||
| if !s.sendToDataChan(dataChanEntry{Data: dataBuf, Position: stripBytesPos}) { | |||
| break loop | |||
| } | |||
| curStripIndex++ | |||
| } | |||
| close(s.dataChan) | |||
| } | |||
| func (s *LRCStripIterator) sendToDataChan(entry dataChanEntry) bool { | |||
| select { | |||
| case s.dataChan <- entry: | |||
| return true | |||
| case <-s.downloadingDone: | |||
| return false | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package strategy | |||
| type Config struct { | |||
| // 当到下载节点的延迟高于这个值时,该节点在评估时会有更高的分数惩罚,单位:ms | |||
| HighLatencyHubMs float64 `json:"highLatencyHubMs"` | |||
| } | |||
| @@ -0,0 +1,337 @@ | |||
| package strategy | |||
| import ( | |||
| "fmt" | |||
| "math" | |||
| "reflect" | |||
| "time" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/pkgs/bitmap" | |||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||
| "gitlink.org.cn/cloudream/common/utils/math2" | |||
| "gitlink.org.cn/cloudream/common/utils/sort2" | |||
| "gitlink.org.cn/cloudream/storage2/common/consts" | |||
| stgmod "gitlink.org.cn/cloudream/storage2/common/models" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/metacache" | |||
| ) | |||
| type Request struct { | |||
| Detail stgmod.ObjectDetail | |||
| Range math2.Range | |||
| DestHub cdssdk.HubID // 可以为0。此字段不为0时,DestLocation字段无意义。 | |||
| DestLocation cdssdk.LocationID // 可以为0 | |||
| } | |||
| type Strategy interface { | |||
| GetDetail() stgmod.ObjectDetail | |||
| } | |||
| // 直接下载完整对象 | |||
| type DirectStrategy struct { | |||
| Detail stgmod.ObjectDetail | |||
| Storage stgmod.StorageDetail | |||
| } | |||
| func (s *DirectStrategy) GetDetail() stgmod.ObjectDetail { | |||
| return s.Detail | |||
| } | |||
| // 从指定对象重建对象 | |||
| type ECReconstructStrategy struct { | |||
| Detail stgmod.ObjectDetail | |||
| Redundancy cdssdk.ECRedundancy | |||
| Blocks []stgmod.ObjectBlock | |||
| Storages []stgmod.StorageDetail | |||
| } | |||
| func (s *ECReconstructStrategy) GetDetail() stgmod.ObjectDetail { | |||
| return s.Detail | |||
| } | |||
| type LRCReconstructStrategy struct { | |||
| Detail stgmod.ObjectDetail | |||
| Redundancy cdssdk.LRCRedundancy | |||
| Blocks []stgmod.ObjectBlock | |||
| Storages []stgmod.StorageDetail | |||
| } | |||
| func (s *LRCReconstructStrategy) GetDetail() stgmod.ObjectDetail { | |||
| return s.Detail | |||
| } | |||
| type Selector struct { | |||
| cfg Config | |||
| storageMeta *metacache.UserSpaceMeta | |||
| hubMeta *metacache.HubMeta | |||
| connectivity *metacache.Connectivity | |||
| } | |||
| func NewSelector(cfg Config, storageMeta *metacache.UserSpaceMeta, hubMeta *metacache.HubMeta, connectivity *metacache.Connectivity) *Selector { | |||
| return &Selector{ | |||
| cfg: cfg, | |||
| storageMeta: storageMeta, | |||
| hubMeta: hubMeta, | |||
| connectivity: connectivity, | |||
| } | |||
| } | |||
| func (s *Selector) Select(req Request) (Strategy, error) { | |||
| req2 := request2{ | |||
| Detail: req.Detail, | |||
| Range: req.Range, | |||
| DestLocation: req.DestLocation, | |||
| } | |||
| if req.DestHub != 0 { | |||
| req2.DestHub = s.hubMeta.Get(req.DestHub) | |||
| } | |||
| switch red := req.Detail.Object.Redundancy.(type) { | |||
| case *cdssdk.NoneRedundancy: | |||
| return s.selectForNoneOrRep(req2) | |||
| case *cdssdk.RepRedundancy: | |||
| return s.selectForNoneOrRep(req2) | |||
| case *cdssdk.ECRedundancy: | |||
| return s.selectForEC(req2, *red) | |||
| case *cdssdk.LRCRedundancy: | |||
| return s.selectForLRC(req2, *red) | |||
| } | |||
| return nil, fmt.Errorf("unsupported redundancy type: %v of object %v", reflect.TypeOf(req.Detail.Object.Redundancy), req.Detail.Object.ObjectID) | |||
| } | |||
| type downloadStorageInfo struct { | |||
| Storage stgmod.StorageDetail | |||
| ObjectPinned bool | |||
| Blocks []stgmod.ObjectBlock | |||
| Distance float64 | |||
| } | |||
| type downloadBlock struct { | |||
| Storage stgmod.StorageDetail | |||
| Block stgmod.ObjectBlock | |||
| } | |||
| type request2 struct { | |||
| Detail stgmod.ObjectDetail | |||
| Range math2.Range | |||
| DestHub *cdssdk.Hub | |||
| DestLocation cdssdk.LocationID | |||
| } | |||
| func (s *Selector) selectForNoneOrRep(req request2) (Strategy, error) { | |||
| sortedStgs := s.sortDownloadStorages(req) | |||
| if len(sortedStgs) == 0 { | |||
| return nil, fmt.Errorf("no storage available for download") | |||
| } | |||
| _, blks := s.getMinReadingBlockSolution(sortedStgs, 1) | |||
| if len(blks) == 0 { | |||
| return nil, fmt.Errorf("no block available for download") | |||
| } | |||
| return &DirectStrategy{ | |||
| Detail: req.Detail, | |||
| Storage: sortedStgs[0].Storage, | |||
| }, nil | |||
| } | |||
| func (s *Selector) selectForEC(req request2, red cdssdk.ECRedundancy) (Strategy, error) { | |||
| sortedStgs := s.sortDownloadStorages(req) | |||
| if len(sortedStgs) == 0 { | |||
| return nil, fmt.Errorf("no storage available for download") | |||
| } | |||
| bsc, blocks := s.getMinReadingBlockSolution(sortedStgs, red.K) | |||
| osc, stg := s.getMinReadingObjectSolution(sortedStgs, red.K) | |||
| if bsc < osc { | |||
| bs := make([]stgmod.ObjectBlock, len(blocks)) | |||
| ss := make([]stgmod.StorageDetail, len(blocks)) | |||
| for i, b := range blocks { | |||
| bs[i] = b.Block | |||
| ss[i] = b.Storage | |||
| } | |||
| return &ECReconstructStrategy{ | |||
| Detail: req.Detail, | |||
| Redundancy: red, | |||
| Blocks: bs, | |||
| Storages: ss, | |||
| }, nil | |||
| } | |||
| // bsc >= osc,如果osc是MaxFloat64,那么bsc也一定是,也就意味着没有足够块来恢复文件 | |||
| if osc == math.MaxFloat64 { | |||
| return nil, fmt.Errorf("no enough blocks to reconstruct the object %v , want %d, get only %d", req.Detail.Object.ObjectID, red.K, len(blocks)) | |||
| } | |||
| return &DirectStrategy{ | |||
| Detail: req.Detail, | |||
| Storage: stg, | |||
| }, nil | |||
| } | |||
| func (s *Selector) selectForLRC(req request2, red cdssdk.LRCRedundancy) (Strategy, error) { | |||
| sortedStgs := s.sortDownloadStorages(req) | |||
| if len(sortedStgs) == 0 { | |||
| return nil, fmt.Errorf("no storage available for download") | |||
| } | |||
| var blocks []downloadBlock | |||
| selectedBlkIdx := make(map[int]bool) | |||
| for _, stg := range sortedStgs { | |||
| for _, b := range stg.Blocks { | |||
| if b.Index >= red.M() || selectedBlkIdx[b.Index] { | |||
| continue | |||
| } | |||
| blocks = append(blocks, downloadBlock{ | |||
| Storage: stg.Storage, | |||
| Block: b, | |||
| }) | |||
| selectedBlkIdx[b.Index] = true | |||
| } | |||
| } | |||
| if len(blocks) < red.K { | |||
| return nil, fmt.Errorf("not enough blocks to download lrc object") | |||
| } | |||
| bs := make([]stgmod.ObjectBlock, len(blocks)) | |||
| ss := make([]stgmod.StorageDetail, len(blocks)) | |||
| for i, b := range blocks { | |||
| bs[i] = b.Block | |||
| ss[i] = b.Storage | |||
| } | |||
| return &LRCReconstructStrategy{ | |||
| Detail: req.Detail, | |||
| Redundancy: red, | |||
| Blocks: bs, | |||
| Storages: ss, | |||
| }, nil | |||
| } | |||
| func (s *Selector) sortDownloadStorages(req request2) []*downloadStorageInfo { | |||
| var stgIDs []cdssdk.StorageID | |||
| for _, id := range req.Detail.PinnedAt { | |||
| if !lo.Contains(stgIDs, id) { | |||
| stgIDs = append(stgIDs, id) | |||
| } | |||
| } | |||
| for _, b := range req.Detail.Blocks { | |||
| if !lo.Contains(stgIDs, b.StorageID) { | |||
| stgIDs = append(stgIDs, b.StorageID) | |||
| } | |||
| } | |||
| downloadStorageMap := make(map[cdssdk.StorageID]*downloadStorageInfo) | |||
| for _, id := range req.Detail.PinnedAt { | |||
| storage, ok := downloadStorageMap[id] | |||
| if !ok { | |||
| mod := s.storageMeta.Get(id) | |||
| if mod == nil || mod.MasterHub == nil { | |||
| continue | |||
| } | |||
| storage = &downloadStorageInfo{ | |||
| Storage: *mod, | |||
| ObjectPinned: true, | |||
| Distance: s.getStorageDistance(req, *mod), | |||
| } | |||
| downloadStorageMap[id] = storage | |||
| } | |||
| storage.ObjectPinned = true | |||
| } | |||
| for _, b := range req.Detail.Blocks { | |||
| storage, ok := downloadStorageMap[b.StorageID] | |||
| if !ok { | |||
| mod := s.storageMeta.Get(b.StorageID) | |||
| if mod == nil || mod.MasterHub == nil { | |||
| continue | |||
| } | |||
| storage = &downloadStorageInfo{ | |||
| Storage: *mod, | |||
| Distance: s.getStorageDistance(req, *mod), | |||
| } | |||
| downloadStorageMap[b.StorageID] = storage | |||
| } | |||
| storage.Blocks = append(storage.Blocks, b) | |||
| } | |||
| return sort2.Sort(lo.Values(downloadStorageMap), func(left, right *downloadStorageInfo) int { | |||
| return sort2.Cmp(left.Distance, right.Distance) | |||
| }) | |||
| } | |||
| func (s *Selector) getStorageDistance(req request2, src stgmod.StorageDetail) float64 { | |||
| if req.DestHub != nil { | |||
| if src.MasterHub.HubID == req.DestHub.HubID { | |||
| return consts.StorageDistanceSameStorage | |||
| } | |||
| if src.MasterHub.LocationID == req.DestHub.LocationID { | |||
| return consts.StorageDistanceSameLocation | |||
| } | |||
| latency := s.connectivity.Get(src.MasterHub.HubID, req.DestHub.HubID) | |||
| if latency == nil || *latency > time.Duration(float64(time.Millisecond)*s.cfg.HighLatencyHubMs) { | |||
| return consts.HubDistanceHighLatencyHub | |||
| } | |||
| return consts.StorageDistanceOther | |||
| } | |||
| if req.DestLocation != 0 { | |||
| if src.MasterHub.LocationID == req.DestLocation { | |||
| return consts.StorageDistanceSameLocation | |||
| } | |||
| } | |||
| return consts.StorageDistanceOther | |||
| } | |||
| func (s *Selector) getMinReadingBlockSolution(sortedStgs []*downloadStorageInfo, k int) (float64, []downloadBlock) { | |||
| gotBlocksMap := bitmap.Bitmap64(0) | |||
| var gotBlocks []downloadBlock | |||
| dist := float64(0.0) | |||
| for _, n := range sortedStgs { | |||
| for _, b := range n.Blocks { | |||
| if !gotBlocksMap.Get(b.Index) { | |||
| gotBlocks = append(gotBlocks, downloadBlock{ | |||
| Storage: n.Storage, | |||
| Block: b, | |||
| }) | |||
| gotBlocksMap.Set(b.Index, true) | |||
| dist += n.Distance | |||
| } | |||
| if len(gotBlocks) >= k { | |||
| return dist, gotBlocks | |||
| } | |||
| } | |||
| } | |||
| return math.MaxFloat64, gotBlocks | |||
| } | |||
| func (s *Selector) getMinReadingObjectSolution(sortedStgs []*downloadStorageInfo, k int) (float64, stgmod.StorageDetail) { | |||
| dist := math.MaxFloat64 | |||
| var downloadStg stgmod.StorageDetail | |||
| for _, n := range sortedStgs { | |||
| if n.ObjectPinned && float64(k)*n.Distance < dist { | |||
| dist = float64(k) * n.Distance | |||
| stg := n.Storage | |||
| downloadStg = stg | |||
| } | |||
| } | |||
| return dist, downloadStg | |||
| } | |||
| @@ -0,0 +1,241 @@ | |||
| package downloader | |||
| import ( | |||
| "context" | |||
| "io" | |||
| "sync" | |||
| "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec" | |||
| "gitlink.org.cn/cloudream/common/pkgs/iterator" | |||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||
| "gitlink.org.cn/cloudream/common/utils/math2" | |||
| stgmod "gitlink.org.cn/cloudream/storage2/common/models" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitch2" | |||
| "gitlink.org.cn/cloudream/storage2/common/pkgs/ioswitch2/parser" | |||
| ) | |||
| type downloadBlock struct { | |||
| Storage stgmod.StorageDetail | |||
| Block stgmod.ObjectBlock | |||
| } | |||
| type Strip struct { | |||
| Data []byte | |||
| Position int64 | |||
| } | |||
| type StripIterator struct { | |||
| downloader *Downloader | |||
| object cdssdk.Object | |||
| blocks []downloadBlock | |||
| red cdssdk.ECRedundancy | |||
| curStripIndex int64 | |||
| cache *StripCache | |||
| dataChan chan dataChanEntry | |||
| downloadingDone chan any | |||
| downloadingDoneOnce sync.Once | |||
| inited bool | |||
| downloadingStream io.ReadCloser | |||
| downloadingStripIndex int64 | |||
| downloadingPlanCtxCancel func() | |||
| } | |||
| type dataChanEntry struct { | |||
| Data []byte | |||
| Position int64 // 条带在文件中的位置。字节为单位 | |||
| Error error | |||
| } | |||
| func NewStripIterator(downloader *Downloader, object cdssdk.Object, blocks []downloadBlock, red cdssdk.ECRedundancy, beginStripIndex int64, cache *StripCache, maxPrefetch int) *StripIterator { | |||
| if maxPrefetch <= 0 { | |||
| maxPrefetch = 1 | |||
| } | |||
| iter := &StripIterator{ | |||
| downloader: downloader, | |||
| object: object, | |||
| blocks: blocks, | |||
| red: red, | |||
| curStripIndex: beginStripIndex, | |||
| cache: cache, | |||
| dataChan: make(chan dataChanEntry, maxPrefetch-1), | |||
| downloadingDone: make(chan any), | |||
| } | |||
| return iter | |||
| } | |||
| func (s *StripIterator) MoveNext() (Strip, error) { | |||
| if !s.inited { | |||
| go s.downloading(s.curStripIndex) | |||
| s.inited = true | |||
| } | |||
| // 先尝试获取一下,用于判断本次获取是否发生了等待 | |||
| select { | |||
| case entry, ok := <-s.dataChan: | |||
| if !ok || entry.Error == io.EOF { | |||
| return Strip{}, iterator.ErrNoMoreItem | |||
| } | |||
| if entry.Error != nil { | |||
| return Strip{}, entry.Error | |||
| } | |||
| s.curStripIndex++ | |||
| return Strip{Data: entry.Data, Position: entry.Position}, nil | |||
| default: | |||
| logger.Debugf("waitting for ec strip %v of object %v", s.curStripIndex, s.object.ObjectID) | |||
| } | |||
| // 发生了等待 | |||
| select { | |||
| case entry, ok := <-s.dataChan: | |||
| if !ok || entry.Error == io.EOF { | |||
| return Strip{}, iterator.ErrNoMoreItem | |||
| } | |||
| if entry.Error != nil { | |||
| return Strip{}, entry.Error | |||
| } | |||
| s.curStripIndex++ | |||
| return Strip{Data: entry.Data, Position: entry.Position}, nil | |||
| case <-s.downloadingDone: | |||
| return Strip{}, iterator.ErrNoMoreItem | |||
| } | |||
| } | |||
| func (s *StripIterator) Close() { | |||
| s.downloadingDoneOnce.Do(func() { | |||
| close(s.downloadingDone) | |||
| }) | |||
| } | |||
| func (s *StripIterator) downloading(startStripIndex int64) { | |||
| curStripIndex := startStripIndex | |||
| loop: | |||
| for { | |||
| stripBytesPos := curStripIndex * int64(s.red.K) * int64(s.red.ChunkSize) | |||
| if stripBytesPos >= s.object.Size { | |||
| s.sendToDataChan(dataChanEntry{Error: io.EOF}) | |||
| break | |||
| } | |||
| stripKey := ECStripKey{ | |||
| ObjectID: s.object.ObjectID, | |||
| StripIndex: curStripIndex, | |||
| } | |||
| item, ok := s.cache.Get(stripKey) | |||
| if ok { | |||
| if item.ObjectFileHash == s.object.FileHash { | |||
| if !s.sendToDataChan(dataChanEntry{Data: item.Data, Position: stripBytesPos}) { | |||
| break loop | |||
| } | |||
| curStripIndex++ | |||
| continue | |||
| } else { | |||
| // 如果Object的Hash和Cache的Hash不一致,说明Cache是无效的,需要重新下载 | |||
| s.cache.Remove(stripKey) | |||
| } | |||
| } | |||
| dataBuf := make([]byte, int64(s.red.K*s.red.ChunkSize)) | |||
| n, err := s.readStrip(curStripIndex, dataBuf) | |||
| if err == io.ErrUnexpectedEOF { | |||
| // dataBuf中的内容可能不足一个条带,但仍然将其完整放入cache中,外部应该自行计算该从这个buffer中读多少数据 | |||
| s.cache.Add(stripKey, ObjectECStrip{ | |||
| Data: dataBuf, | |||
| ObjectFileHash: s.object.FileHash, | |||
| }) | |||
| s.sendToDataChan(dataChanEntry{Data: dataBuf[:n], Position: stripBytesPos}) | |||
| s.sendToDataChan(dataChanEntry{Error: io.EOF}) | |||
| break loop | |||
| } | |||
| if err != nil { | |||
| s.sendToDataChan(dataChanEntry{Error: err}) | |||
| break loop | |||
| } | |||
| s.cache.Add(stripKey, ObjectECStrip{ | |||
| Data: dataBuf, | |||
| ObjectFileHash: s.object.FileHash, | |||
| }) | |||
| if !s.sendToDataChan(dataChanEntry{Data: dataBuf, Position: stripBytesPos}) { | |||
| break loop | |||
| } | |||
| curStripIndex++ | |||
| } | |||
| if s.downloadingStream != nil { | |||
| s.downloadingStream.Close() | |||
| s.downloadingPlanCtxCancel() | |||
| } | |||
| close(s.dataChan) | |||
| } | |||
| func (s *StripIterator) sendToDataChan(entry dataChanEntry) bool { | |||
| select { | |||
| case s.dataChan <- entry: | |||
| return true | |||
| case <-s.downloadingDone: | |||
| return false | |||
| } | |||
| } | |||
| func (s *StripIterator) readStrip(stripIndex int64, buf []byte) (int, error) { | |||
| // 如果需求的条带不当前正在下载的条带的位置不符合,则需要重新打开下载流 | |||
| if s.downloadingStream == nil || s.downloadingStripIndex != stripIndex { | |||
| if s.downloadingStream != nil { | |||
| s.downloadingStream.Close() | |||
| s.downloadingPlanCtxCancel() | |||
| } | |||
| ft := ioswitch2.NewFromTo() | |||
| ft.ECParam = &s.red | |||
| for _, b := range s.blocks { | |||
| stg := b.Storage | |||
| ft.AddFrom(ioswitch2.NewFromShardstore(b.Block.FileHash, *stg.MasterHub, stg, ioswitch2.ECStream(b.Block.Index))) | |||
| } | |||
| toExec, hd := ioswitch2.NewToDriverWithRange(ioswitch2.RawStream(), math2.Range{ | |||
| Offset: stripIndex * s.red.StripSize(), | |||
| }) | |||
| ft.AddTo(toExec) | |||
| plans := exec.NewPlanBuilder() | |||
| err := parser.Parse(ft, plans) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| exeCtx := exec.NewExecContext() | |||
| exec.SetValueByType(exeCtx, s.downloader.stgAgts) | |||
| exec := plans.Execute(exeCtx) | |||
| ctx, cancel := context.WithCancel(context.Background()) | |||
| go exec.Wait(ctx) | |||
| str, err := exec.BeginRead(hd) | |||
| if err != nil { | |||
| cancel() | |||
| return 0, err | |||
| } | |||
| s.downloadingStream = str | |||
| s.downloadingStripIndex = stripIndex | |||
| s.downloadingPlanCtxCancel = cancel | |||
| } | |||
| n, err := io.ReadFull(s.downloadingStream, buf) | |||
| s.downloadingStripIndex += 1 | |||
| return n, err | |||
| } | |||