This is just a summary about how the cancellation is working right now so I won’t forget:
Cancellation process
- All operations have a
resultwhich can control the operation (pause, resume, stop (now / after file), abort) - The backup handler is passed a cancellation token
Controller.Stop()cancels that token and callsBasicResult.Stop()Controller.Abort()callsBasicResults.Abort()andThread.Abort()on the task thread (does not cancel the token)
Part A:
BasicResult.Stop()setsm_pauseEventto unpaused andm_controlStateto stopped (aborted forAbort()) on the result. These are used byresult.TaskControlRendevouz():- The method throws an exception if aborted
- If paused it blocks, otherwise it returns the current state
BackendManageruses this to pause and abort transfers between operations and in the progress handlerBackupHandler: pause and abort before post backup verificationCompactHandler,DeleteHandler,RecreateDatabaseHandler,RepairHandler,RestoreControlFilesHandler,RestoreHandler,TestHandler:
pause/abort before each file, stop: complete backend transfer and finish transaction before stoppingListChangesHandler: pause/stop/abort at a few predetermined spotsListFilesHandler: pause/stop/abort before each fileset
Part B:
-
In addition,
BasicResult.Stop()calls stop on am_taskController, but only for stop now. There is supposed to be a distinction between stop now and stop after current file in the task controller, but it is not used. -
For
BackupHandlerthis is passed in asresult.TaskReadertoBackendUploaderDataBlockProcessorFileBlockProcessorStreamBlockSplitterFileEnumerationProcessSpillCollectorProcess
and is checked at some other places, but not to:
FilePreFilterProcessMetadataPreProcess(gets the cancellation token though, uses it to ignore cancellation exceptions)ProgressHandler
-
In each of those with task reader:
await taskreader.ProgressAsyncis called repeatedly- Running: this returns false without blocking
- Paused: this await blocks until resumed
- Stopped: this returns true without blocking, allowing cleanup after a file/block is complete
- Terminated (only called in
Dispose()): this cancels the underlying task, throwing aTaskCancelledExceptiononce awaited. Some processors rely on this to finish after all other tasks.
-
Otherwise the task reader is only used for
FileEnumerationProcessinTestFilterHandler
Part C
- The cancellation token is used in
FileBlockProcessorFileEnumerationProcessBackupHandler.RunMainOperation()to determine if the backup is partial
- It seems to be checked at least as often as
ITaskReader.ProgressAsync, or more often. I think this is to cancel faster in sections where a pause would not make sense.
Conclusion
- There seem to be two parallel ways to communicate the end of the operation (three if you count the cancellation token)
BasicResult.TaskControlRendevouz():- is a blocking operation to pause the progress
- throws when aborted
- can return
RunorStop(Pausewill always block until resumed andAbortwill always throw)
ITaskReader.ProgressAsync:- is async
awaitcompatible to pause the progress (Pause()is not called anywhere, should be inBasicResults.Pause()) - throws when terminated (in
Dispose, which does not seem to be called ever, it is also not set inBasicResults.Abort()) - returns
trueto continue,falseto stop
- is async
I think it was intended to move from the blocking TaskControlRendevouz() to the async ProgressAsync, but this transition is very incomplete at the moment. In the commit history it was added in a3f2b39d9 in 2016, seems to be after the cancellation token was already used:
Implemented handling of pause/stop/abort in the concurrent code.
Implemented the dry-run feature for backups.
TaskControlRendevouz() was added in bd53090a in 2014:
Implemented the pause/resume/stop/start methods throughout the calls to allow for interactive control over the tasks
Right now this whole logic is very confusing, maybe @kenkendk knows more about how this was intended to be.