fix file upload PartNotSequential error #445

This commit is contained in:
tickstep 2024-08-30 11:49:43 +08:00
parent 0b5bd60702
commit 47baab44a7
7 changed files with 135 additions and 41 deletions

View File

@ -30,6 +30,7 @@ type (
io.Seeker io.Seeker
Range() transfer.Range Range() transfer.Range
Left() int64 Left() int64
ResetReader(readerAt io.ReaderAt)
} }
fileBlock struct { fileBlock struct {
@ -147,3 +148,8 @@ func (fb *fileBlock) Range() transfer.Range {
func (fb *fileBlock) Readed() int64 { func (fb *fileBlock) Readed() int64 {
return fb.readed return fb.readed
} }
func (fb *fileBlock) ResetReader(readerAt io.ReaderAt) {
fb.readerAt = readerAt
fb.readed = 0
}

View File

@ -18,8 +18,10 @@ import "fmt"
var ( var (
UploadUrlExpired = fmt.Errorf("UrlExpired") UploadUrlExpired = fmt.Errorf("UrlExpired")
UploadPartNotSeq = fmt.Errorf("PartNotSequential") UploadPartNotSeq = fmt.Errorf("PartNotSequential")
UploadNoSuchUpload = fmt.Errorf("NoSuchUpload")
UploadTerminate = fmt.Errorf("UploadErrorTerminate") UploadTerminate = fmt.Errorf("UploadErrorTerminate")
UploadPartAlreadyExist = fmt.Errorf("PartAlreadyExist") UploadPartAlreadyExist = fmt.Errorf("PartAlreadyExist")
UploadHttpError = fmt.Errorf("HttpError")
) )
type ( type (
@ -33,5 +35,8 @@ type (
) )
func (me *MultiError) Error() string { func (me *MultiError) Error() string {
if me.Err != nil {
return me.Err.Error() return me.Err.Error()
}
return ""
} }

View File

@ -42,15 +42,11 @@ func (muer *MultiUploader) getWorkerListByInstanceState(is *InstanceState) worke
uploadDone: false, uploadDone: false,
}) })
} else { } else {
// 已经完成的, 也要加入 (可继续优化) // 已经完成的, 也要加入
workers = append(workers, &worker{ workers = append(workers, &worker{
id: blockState.ID, id: blockState.ID,
partOffset: blockState.Range.Begin, partOffset: blockState.Range.Begin,
splitUnit: &fileBlock{ splitUnit: NewBufioSplitUnit(muer.file, blockState.Range, muer.speedsStat, muer.rateLimit, muer.globalSpeedsStat),
readRange: blockState.Range,
readed: blockState.Range.End - blockState.Range.Begin,
readerAt: muer.file,
},
uploadDone: true, uploadDone: true,
}) })
} }

View File

@ -16,6 +16,7 @@ package uploader
import ( import (
"context" "context"
"github.com/tickstep/aliyunpan-api/aliyunpan" "github.com/tickstep/aliyunpan-api/aliyunpan"
"github.com/tickstep/aliyunpan/internal/config"
"github.com/tickstep/aliyunpan/internal/utils" "github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/library-go/converter" "github.com/tickstep/library-go/converter"
"github.com/tickstep/library-go/logger" "github.com/tickstep/library-go/logger"
@ -61,6 +62,7 @@ type (
// 网盘上传参数 // 网盘上传参数
UploadOpEntity *aliyunpan.CreateFileUploadResult `json:"uploadOpEntity"` UploadOpEntity *aliyunpan.CreateFileUploadResult `json:"uploadOpEntity"`
panClient *config.PanClient
} }
// MultiUploaderConfig 多线程上传配置 // MultiUploaderConfig 多线程上传配置
@ -72,12 +74,13 @@ type (
) )
// NewMultiUploader 初始化上传 // NewMultiUploader 初始化上传
func NewMultiUploader(multiUpload MultiUpload, file rio.ReaderAtLen64, config *MultiUploaderConfig, uploadOpEntity *aliyunpan.CreateFileUploadResult, globalSpeedsStat *speeds.Speeds) *MultiUploader { func NewMultiUploader(multiUpload MultiUpload, file rio.ReaderAtLen64, config *MultiUploaderConfig, uploadOpEntity *aliyunpan.CreateFileUploadResult, panClient *config.PanClient, globalSpeedsStat *speeds.Speeds) *MultiUploader {
return &MultiUploader{ return &MultiUploader{
multiUpload: multiUpload, multiUpload: multiUpload,
file: file, file: file,
config: config, config: config,
UploadOpEntity: uploadOpEntity, UploadOpEntity: uploadOpEntity,
panClient: panClient,
globalSpeedsStat: globalSpeedsStat, globalSpeedsStat: globalSpeedsStat,
} }
} }
@ -137,13 +140,13 @@ func (muer *MultiUploader) Execute() error {
// 分配任务 // 分配任务
if muer.instanceState != nil { if muer.instanceState != nil {
muer.workers = muer.getWorkerListByInstanceState(muer.instanceState) muer.workers = muer.getWorkerListByInstanceState(muer.instanceState)
logger.Verboseln("upload task CREATED from instance state\n") logger.Verbosef("upload task CREATED from instance state\n")
} else { } else {
muer.workers = muer.getWorkerListByInstanceState(&InstanceState{ muer.workers = muer.getWorkerListByInstanceState(&InstanceState{
BlockList: SplitBlock(muer.file.Len(), muer.config.BlockSize), BlockList: SplitBlock(muer.file.Len(), muer.config.BlockSize),
}) })
logger.Verboseln("upload task CREATED: block size: %d, num: %d\n", muer.config.BlockSize, len(muer.workers)) logger.Verbosef("upload task CREATED: block size: %d, num: %d\n", muer.config.BlockSize, len(muer.workers))
} }
// 开始上传 // 开始上传
@ -195,32 +198,32 @@ func (muer *MultiUploader) Cancel() {
close(muer.canceled) close(muer.canceled)
} }
//OnExecute 设置开始上传事件 // OnExecute 设置开始上传事件
func (muer *MultiUploader) OnExecute(onExecuteEvent requester.Event) { func (muer *MultiUploader) OnExecute(onExecuteEvent requester.Event) {
muer.onExecuteEvent = onExecuteEvent muer.onExecuteEvent = onExecuteEvent
} }
//OnSuccess 设置成功上传事件 // OnSuccess 设置成功上传事件
func (muer *MultiUploader) OnSuccess(onSuccessEvent requester.Event) { func (muer *MultiUploader) OnSuccess(onSuccessEvent requester.Event) {
muer.onSuccessEvent = onSuccessEvent muer.onSuccessEvent = onSuccessEvent
} }
//OnFinish 设置结束上传事件 // OnFinish 设置结束上传事件
func (muer *MultiUploader) OnFinish(onFinishEvent requester.Event) { func (muer *MultiUploader) OnFinish(onFinishEvent requester.Event) {
muer.onFinishEvent = onFinishEvent muer.onFinishEvent = onFinishEvent
} }
//OnCancel 设置取消上传事件 // OnCancel 设置取消上传事件
func (muer *MultiUploader) OnCancel(onCancelEvent requester.Event) { func (muer *MultiUploader) OnCancel(onCancelEvent requester.Event) {
muer.onCancelEvent = onCancelEvent muer.onCancelEvent = onCancelEvent
} }
//OnError 设置上传发生错误事件 // OnError 设置上传发生错误事件
func (muer *MultiUploader) OnError(onErrorEvent requester.EventOnError) { func (muer *MultiUploader) OnError(onErrorEvent requester.EventOnError) {
muer.onErrorEvent = onErrorEvent muer.onErrorEvent = onErrorEvent
} }
//OnUploadStatusEvent 设置上传状态事件 // OnUploadStatusEvent 设置上传状态事件
func (muer *MultiUploader) OnUploadStatusEvent(f UploadStatusFunc) { func (muer *MultiUploader) OnUploadStatusEvent(f UploadStatusFunc) {
muer.onUploadStatusEvent = f muer.onUploadStatusEvent = f
} }

View File

@ -15,11 +15,12 @@ package uploader
import ( import (
"context" "context"
"errors"
"github.com/oleiade/lane" "github.com/oleiade/lane"
"github.com/tickstep/aliyunpan/internal/waitgroup" "github.com/tickstep/aliyunpan/internal/waitgroup"
"github.com/tickstep/library-go/logger" "github.com/tickstep/library-go/logger"
"github.com/tickstep/library-go/requester" "github.com/tickstep/library-go/requester"
"os" "io"
"strconv" "strconv"
) )
@ -89,7 +90,7 @@ func (muer *MultiUploader) upload() (uperr error) {
) )
go func() { go func() {
if !wer.uploadDone { if !wer.uploadDone {
logger.Verboseln("begin to upload part: " + strconv.Itoa(wer.id)) logger.Verboseln("begin to upload part num: " + strconv.Itoa(wer.id+1))
uploadDone, terr = muer.multiUpload.UploadFile(ctx, int(wer.id), wer.partOffset, wer.splitUnit.Range().End, wer.splitUnit, uploadClient) uploadDone, terr = muer.multiUpload.UploadFile(ctx, int(wer.id), wer.partOffset, wer.splitUnit.Range().End, wer.splitUnit, uploadClient)
} else { } else {
uploadDone = true uploadDone = true
@ -102,11 +103,11 @@ func (muer *MultiUploader) upload() (uperr error) {
return return
case <-doneChan: case <-doneChan:
// continue // continue
logger.Verboseln("multiUpload worker upload file done") logger.Verboseln("multiUpload worker upload file http action done")
} }
cancel() cancel()
if terr != nil { if terr != nil {
logger.Verboseln("upload file part err: %+v", terr) logger.Verbosef("upload file part err: %+v\n", terr)
if me, ok := terr.(*MultiError); ok { if me, ok := terr.(*MultiError); ok {
if me.Terminated { // 终止 if me.Terminated { // 终止
muer.closeCanceledOnce.Do(func() { // 只关闭一次 muer.closeCanceledOnce.Do(func() { // 只关闭一次
@ -115,18 +116,21 @@ func (muer *MultiUploader) upload() (uperr error) {
uperr = me.Err uperr = me.Err
return return
} else if me.NeedStartOver { } else if me.NeedStartOver {
logger.Verboseln("upload start over: %d\n", wer.id) logger.Verbosef("upload start over: %d\n", wer.id)
// 从头开始上传 // 从头开始上传
muer.closeCanceledOnce.Do(func() { // 只关闭一次 muer.closeCanceledOnce.Do(func() { // 只关闭一次
close(muer.canceled) close(muer.canceled)
}) })
uperr = me.Err uperr = me.Err
return return
} else {
uperr = me.Err
return
} }
} }
logger.Verboseln("upload err: %s, id: %d\n", terr, wer.id) logger.Verbosef("upload worker err: %s, id: %d\n", terr, wer.id)
wer.splitUnit.Seek(0, os.SEEK_SET) wer.splitUnit.Seek(0, io.SeekStart)
uploadDeque.Prepend(wer) // 放回上传队列首位 uploadDeque.Prepend(wer) // 放回上传队列首位
return return
} }
@ -139,9 +143,10 @@ func (muer *MultiUploader) upload() (uperr error) {
}() }()
wg.Wait() wg.Wait()
if uperr != nil { if uperr != nil {
if uperr == UploadPartNotSeq { if errors.Is(uperr, UploadPartNotSeq) {
// 分片出现乱序,需要重新上传,取消本次所有剩余的分片的上传 // 分片出现乱序,停止上传
break // 清空数据,准备重新上传
uploadDeque = lane.NewDeque() // 清空待上传列表
} }
} }
// 没有任务了 // 没有任务了
@ -153,6 +158,11 @@ func (muer *MultiUploader) upload() (uperr error) {
// 释放链路 // 释放链路
uploadClient.CloseIdleConnections() uploadClient.CloseIdleConnections()
// 返回错误,通知上层客户端
if errors.Is(uperr, UploadPartNotSeq) {
return uperr
}
select { select {
case <-muer.canceled: case <-muer.canceled:
if uperr != nil { if uperr != nil {

View File

@ -129,12 +129,20 @@ func (pu *PanUpload) UploadFile(ctx context.Context, partseq int, partOffset int
errResp := &apierror.ErrorXmlResp{} errResp := &apierror.ErrorXmlResp{}
if err := xml.Unmarshal(buf, errResp); err == nil { if err := xml.Unmarshal(buf, errResp); err == nil {
if errResp.Code != "" { if errResp.Code != "" {
if "PartNotSequential" == errResp.Code || "NoSuchUpload" == errResp.Code { if "PartNotSequential" == errResp.Code {
respError = uploader.UploadPartNotSeq respError = uploader.UploadPartNotSeq
respErr = &uploader.MultiError{ respErr = &uploader.MultiError{
Err: uploader.UploadPartNotSeq, Err: uploader.UploadPartNotSeq,
Terminated: false, Terminated: false,
NeedStartOver: true, NeedStartOver: false,
}
return resp, respError
} else if "NoSuchUpload" == errResp.Code {
respError = uploader.UploadNoSuchUpload
respErr = &uploader.MultiError{
Err: uploader.UploadNoSuchUpload,
Terminated: true,
NeedStartOver: false,
} }
return resp, respError return resp, respError
} else if "AccessDenied" == errResp.Code && "Request has expired." == errResp.Message { } else if "AccessDenied" == errResp.Code && "Request has expired." == errResp.Message {
@ -153,6 +161,14 @@ func (pu *PanUpload) UploadFile(ctx context.Context, partseq int, partOffset int
return resp, respError return resp, respError
} }
} }
} else {
respError = uploader.UploadHttpError
respErr = &uploader.MultiError{
Err: uploader.UploadHttpError,
Terminated: false,
NeedStartOver: false,
}
return resp, respError
} }
} }
} else { } else {
@ -204,7 +220,7 @@ func (pu *PanUpload) UploadFile(ctx context.Context, partseq int, partOffset int
// success // success
return true, nil return true, nil
} else if respErr.Err == uploader.UploadPartNotSeq { } else if respErr.Err == uploader.UploadPartNotSeq {
// 上传分片乱序了需要重新从0分片开始上传 // 上传分片乱序了
// 先直接返回,后续再优化 // 先直接返回,后续再优化
return false, respErr return false, respErr
} else { } else {

View File

@ -14,6 +14,7 @@
package panupload package panupload
import ( import (
"errors"
"fmt" "fmt"
"github.com/tickstep/aliyunpan/internal/log" "github.com/tickstep/aliyunpan/internal/log"
"github.com/tickstep/aliyunpan/internal/plugins" "github.com/tickstep/aliyunpan/internal/plugins"
@ -167,7 +168,7 @@ func (utu *UploadTaskUnit) upload() (result *taskframework.TaskUnitRunResult) {
Parallel: utu.Parallel, Parallel: utu.Parallel,
BlockSize: utu.BlockSize, BlockSize: utu.BlockSize,
MaxRate: config.Config.MaxUploadRate, MaxRate: config.Config.MaxUploadRate,
}, utu.LocalFileChecksum.UploadOpEntity, utu.GlobalSpeedsStat) }, utu.LocalFileChecksum.UploadOpEntity, utu.PanClient, utu.GlobalSpeedsStat)
// 设置断点续传 // 设置断点续传
if utu.state != nil { if utu.state != nil {
@ -240,6 +241,10 @@ func (utu *UploadTaskUnit) upload() (result *taskframework.TaskUnitRunResult) {
if er != nil { if er != nil {
result.ResultMessage = StrUploadFailed result.ResultMessage = StrUploadFailed
result.NeedRetry = true result.NeedRetry = true
if errors.Is(er, uploader.UploadNoSuchUpload) {
// do not need retry
result.NeedRetry = false
}
result.Err = er result.Err = er
} }
return return
@ -555,14 +560,67 @@ stepUploadUpload:
// 正常上传流程 // 正常上传流程
uploadResult := utu.upload() uploadResult := utu.upload()
if uploadResult != nil && uploadResult.Err != nil { if uploadResult != nil && uploadResult.Err != nil {
if uploadResult.Err == uploader.UploadPartNotSeq { // 处理上传错误
fmt.Printf("[%s] %s 文件分片上传顺序错误,开始重新上传文件\n", utu.taskInfo.Id(), time.Now().Format("2006-01-02 15:04:06")) if errors.Is(uploadResult.Err, uploader.UploadPartNotSeq) {
// 分片乱序错误
utu.amendFileUploadPartNum()
goto stepUploadUpload
}
if errors.Is(uploadResult.Err, uploader.UploadNoSuchUpload) {
// 上传任务过期
fmt.Printf("[%s] %s 网盘上传任务不存在,创建新任务重新上传文件\n", utu.taskInfo.Id(), time.Now().Format("2006-01-02 15:04:06"))
// 需要重新从0开始上传 // 需要重新从0开始上传
uploadResult = nil uploadResult = nil
utu.LocalFileChecksum.UploadOpEntity = nil utu.LocalFileChecksum.UploadOpEntity = nil
utu.state = nil utu.state = nil
goto StepUploadPrepareUpload goto StepUploadPrepareUpload
} }
var apier *apierror.ApiError
if errors.As(uploadResult.Err, &apier) {
// 上传任务过期
if apier.Code == apierror.ApiCodeUploadIdNotFound {
fmt.Printf("[%s] %s 网盘上传任务已失效,创建新任务重新上传文件\n", utu.taskInfo.Id(), time.Now().Format("2006-01-02 15:04:06"))
uploadResult = nil
utu.LocalFileChecksum.UploadOpEntity = nil
utu.state = nil
goto StepUploadPrepareUpload
}
}
} }
return uploadResult return uploadResult
} }
// amendFileUploadPartNum 修正文件分片上传顺序错误
func (utu *UploadTaskUnit) amendFileUploadPartNum() {
if utu.LocalFileChecksum.LocalFileMeta.UploadOpEntity == nil || utu.state == nil {
return
}
logger.Verbosef("adjust the uploaded parts num error\n")
// 分片出现乱序
// 获取的已上传分片信息,修正正确的分片顺序
uploadedParts, uper := utu.PanClient.OpenapiPanClient().GetUploadedPartInfoAllItem(&aliyunpan.GetUploadedPartsParam{
DriveId: utu.LocalFileChecksum.LocalFileMeta.UploadOpEntity.DriveId,
FileId: utu.LocalFileChecksum.LocalFileMeta.UploadOpEntity.FileId,
UploadId: utu.LocalFileChecksum.LocalFileMeta.UploadOpEntity.UploadId,
})
if uper != nil {
logger.Verbosef("get uploaded parts info error: %+v\n", uper)
return
}
// 获取最后上传的分片编号
lastUploadedPartNum := -1
if len(uploadedParts.UploadedParts) > 0 {
lastUploadedPartNum = uploadedParts.UploadedParts[len(uploadedParts.UploadedParts)-1].PartNumber
}
// 修正分片上传的标识
if lastUploadedPartNum > 0 {
logger.Verbosef("get the right uploaded parts num: %d\n", lastUploadedPartNum)
for _, w := range utu.state.BlockList {
if (w.ID + 1) <= lastUploadedPartNum { // 分片的编号从1开始BlockList的id是从0开始
w.UploadDone = true
} else {
w.UploadDone = false
}
}
}
}