From 7470e5356e04424cb891bd6cdea9071a005f30dd Mon Sep 17 00:00:00 2001 From: DRON-666 <64691982+DRON-666@users.noreply.github.com> Date: Fri, 6 Nov 2020 03:41:02 +0300 Subject: [PATCH] vss: Add "timeout" option Changing multiple "callAsyncFunctionAndWait" with fixed timeout to calculated timeout based on deadline. --- internal/fs/fs_local_vss.go | 10 ++++++++-- internal/fs/vss.go | 4 +++- internal/fs/vss_windows.go | 37 +++++++++++++++++++++++++------------ 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/internal/fs/fs_local_vss.go b/internal/fs/fs_local_vss.go index f68e2ff28..1f6001782 100644 --- a/internal/fs/fs_local_vss.go +++ b/internal/fs/fs_local_vss.go @@ -6,6 +6,7 @@ import ( "runtime" "strings" "sync" + "time" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/options" @@ -13,6 +14,7 @@ import ( // VSSConfig holds extended options of windows volume shadow copy service. type VSSConfig struct { + Timeout time.Duration `option:"timeout" help:"time that the VSS can spend creating snapshots before timing out"` } func init() { @@ -23,7 +25,9 @@ func init() { // NewVSSConfig returns a new VSSConfig with the default values filled in. func NewVSSConfig() VSSConfig { - return VSSConfig{} + return VSSConfig{ + Timeout: time.Second * 120, + } } // ParseVSSConfig parses a VSS extended options to VSSConfig struct. @@ -52,6 +56,7 @@ type LocalVss struct { mutex sync.RWMutex msgError ErrorHandler msgMessage MessageHandler + timeout time.Duration } // statically ensure that LocalVss implements FS. @@ -66,6 +71,7 @@ func NewLocalVss(msgError ErrorHandler, msgMessage MessageHandler, cfg VSSConfig failedSnapshots: make(map[string]struct{}), msgError: msgError, msgMessage: msgMessage, + timeout: cfg.Timeout, } } @@ -144,7 +150,7 @@ func (fs *LocalVss) snapshotPath(path string) string { vssVolume := volumeNameLower + string(filepath.Separator) fs.msgMessage("creating VSS snapshot for [%s]\n", vssVolume) - if snapshot, err := NewVssSnapshot(vssVolume, 120, fs.msgError); err != nil { + if snapshot, err := NewVssSnapshot(vssVolume, fs.timeout, fs.msgError); err != nil { _ = fs.msgError(vssVolume, errors.Errorf("failed to create snapshot for [%s]: %s", vssVolume, err)) fs.failedSnapshots[volumeNameLower] = struct{}{} diff --git a/internal/fs/vss.go b/internal/fs/vss.go index 5f0ea36d9..92143883d 100644 --- a/internal/fs/vss.go +++ b/internal/fs/vss.go @@ -4,6 +4,8 @@ package fs import ( + "time" + "github.com/restic/restic/internal/errors" ) @@ -34,7 +36,7 @@ func HasSufficientPrivilegesForVSS() error { // NewVssSnapshot creates a new vss snapshot. If creating the snapshots doesn't // finish within the timeout an error is returned. func NewVssSnapshot( - _ string, _ uint, _ ErrorHandler) (VssSnapshot, error) { + _ string, _ time.Duration, _ ErrorHandler) (VssSnapshot, error) { return VssSnapshot{}, errors.New("VSS snapshots are only supported on windows") } diff --git a/internal/fs/vss_windows.go b/internal/fs/vss_windows.go index d75567d25..4e7f10385 100644 --- a/internal/fs/vss_windows.go +++ b/internal/fs/vss_windows.go @@ -9,6 +9,7 @@ import ( "runtime" "strings" "syscall" + "time" "unsafe" ole "github.com/go-ole/go-ole" @@ -617,8 +618,13 @@ func (vssAsync *IVSSAsync) QueryStatus() (HRESULT, uint32) { // WaitUntilAsyncFinished waits until either the async call is finished or // the given timeout is reached. -func (vssAsync *IVSSAsync) WaitUntilAsyncFinished(millis uint32) error { - hresult := vssAsync.Wait(millis) +func (vssAsync *IVSSAsync) WaitUntilAsyncFinished(timeout time.Duration) error { + const maxTimeout = 2147483647 * time.Millisecond + if timeout > maxTimeout { + timeout = maxTimeout + } + + hresult := vssAsync.Wait(uint32(timeout.Milliseconds())) err := newVssErrorIfResultNotOK("Wait() failed", hresult) if err != nil { vssAsync.Cancel() @@ -677,7 +683,7 @@ type VssSnapshot struct { snapshotProperties VssSnapshotProperties snapshotDeviceObject string mountPointInfo map[string]MountPoint - timeoutInMillis uint32 + timeout time.Duration } // GetSnapshotDeviceObject returns root path to access the snapshot files @@ -730,7 +736,7 @@ func HasSufficientPrivilegesForVSS() error { // NewVssSnapshot creates a new vss snapshot. If creating the snapshots doesn't // finish within the timeout an error is returned. func NewVssSnapshot( - volume string, timeoutInSeconds uint, msgError ErrorHandler) (VssSnapshot, error) { + volume string, timeout time.Duration, msgError ErrorHandler) (VssSnapshot, error) { is64Bit, err := isRunningOn64BitWindows() if err != nil { @@ -744,7 +750,7 @@ func NewVssSnapshot( runtime.GOARCH)) } - timeoutInMillis := uint32(timeoutInSeconds * 1000) + deadline := time.Now().Add(timeout) oleIUnknown, err := initializeVssCOMInterface() if oleIUnknown != nil { @@ -796,7 +802,7 @@ func NewVssSnapshot( } err = callAsyncFunctionAndWait(iVssBackupComponents.GatherWriterMetadata, - "GatherWriterMetadata", timeoutInMillis) + "GatherWriterMetadata", deadline) if err != nil { iVssBackupComponents.Release() return VssSnapshot{}, err @@ -854,7 +860,7 @@ func NewVssSnapshot( } err = callAsyncFunctionAndWait(iVssBackupComponents.PrepareForBackup, "PrepareForBackup", - timeoutInMillis) + deadline) if err != nil { // After calling PrepareForBackup one needs to call AbortBackup() before releasing the VSS // instance for proper cleanup. @@ -865,7 +871,7 @@ func NewVssSnapshot( } err = callAsyncFunctionAndWait(iVssBackupComponents.DoSnapshotSet, "DoSnapshotSet", - timeoutInMillis) + deadline) if err != nil { iVssBackupComponents.AbortBackup() iVssBackupComponents.Release() @@ -901,7 +907,7 @@ func NewVssSnapshot( } return VssSnapshot{iVssBackupComponents, snapshotSetID, snapshotProperties, - snapshotProperties.GetSnapshotDeviceObject(), mountPointInfo, timeoutInMillis}, nil + snapshotProperties.GetSnapshotDeviceObject(), mountPointInfo, time.Until(deadline)}, nil } // Delete deletes the created snapshot. @@ -922,8 +928,10 @@ func (p *VssSnapshot) Delete() error { if p.iVssBackupComponents != nil { defer p.iVssBackupComponents.Release() + deadline := time.Now().Add(p.timeout) + err = callAsyncFunctionAndWait(p.iVssBackupComponents.BackupComplete, "BackupComplete", - p.timeoutInMillis) + deadline) if err != nil { return err } @@ -945,7 +953,7 @@ type asyncCallFunc func() (*IVSSAsync, error) // callAsyncFunctionAndWait calls an async functions and waits for it to either // finish or timeout. -func callAsyncFunctionAndWait(function asyncCallFunc, name string, timeoutInMillis uint32) error { +func callAsyncFunctionAndWait(function asyncCallFunc, name string, deadline time.Time) error { iVssAsync, err := function() if err != nil { return err @@ -955,7 +963,12 @@ func callAsyncFunctionAndWait(function asyncCallFunc, name string, timeoutInMill return newVssTextError(fmt.Sprintf("%s() returned nil", name)) } - err = iVssAsync.WaitUntilAsyncFinished(timeoutInMillis) + timeout := time.Until(deadline) + if timeout <= 0 { + return newVssTextError(fmt.Sprintf("%s() deadline exceeded", name)) + } + + err = iVssAsync.WaitUntilAsyncFinished(timeout) iVssAsync.Release() return err }