rikyoz / bit7z

A C++ static library offering a clean and simple interface to the 7-zip shared libraries.
https://rikyoz.github.io/bit7z
Mozilla Public License 2.0
623 stars 113 forks source link

How can i cancel a long operation? #37

Closed fabri9532 closed 11 months ago

fabri9532 commented 4 years ago

I would like to write a backup program but I need to allow the user to cancel a compression / decompression / verification operation. How can I do?

Also, I'd like to ask if the library is Thread-Safe.

Thanks for the help, Fabrizio.

rikyoz commented 4 years ago

Hi! Sorry for the late reply!

I would like to write a backup program but I need to allow the user to cancel a compression / decompression / verification operation. How can I do?

Unfortunately, bit7z at the moment does not support directly this feature, but I hope to implement it in the next major version. A temporary (and raw) solution may be to put all the compression/decompression/verification code in a separate thread and controlling this latter (e.g., killing it when user cancel the operation). However, I've never had a chance to test a solution like this before.

Also, I'd like to ask if the library is Thread-Safe.

This is a though question! I did not specifically designed the library for being thread safe. However, if you use bit7z in different threads without sharing anything you should be ok, as long as the operations do not work on the same output files.

e.g.:

// Thread 1
Bit7zLibrary lib{ L"7z.dll" }
BitCompressor compressor{ lib, BitFormat::SevenZip };
compressor.compressFile( L"thread1.txt", L"thread1.zip" );

// Thread 2
Bit7zLibrary lib{ L"7z.dll" }
BitCompressor compressor{ lib, BitFormat::SevenZip };
compressor.compressFile( L"thread2.txt", L"thread2.zip" );

If you share, say, a Bit7zLibrary object between threads, also there should not be no issues, in the same conditions; nevertheless, in this case the thread safety may also depend more closely on the behavior of 7-zip rather than of bit7z (and I cannot find clear information about the thread safety of the first).

In any other case, sharing object instances of classes from bit7z may need thread synchronization if they mutate their state (e.g., both call setPassword).

I hope to have helped. Riccardo

fabri9532 commented 4 years ago

I have already put the compression/decompression/verification code in a separate thread but I think the solution of killing the thread is not for me. Also, I used the "std::packaged_task" which, as far as I know, cannot be killed. I waited for the next major release: do you have any idea for a date? The explanations on Thread-Safety are clear.

Thanks for everything, Fabrizio.

fabri9532 commented 4 years ago

A method to pause compression/decompression/verification may also be useful, because, if a message confirming the cancellation of the operation is displayed to the user, the current operation can be paused until the chosen choice.

Thanks, Fabrizio.

vladimir-kraus commented 2 years ago

Isn't it possible to implement cancelling/pausing/resuming/progress via callbacks somehow?

There could be some new "operation status" callback or maybe it can be incorporated into existing callback (progress callback?). When returning true from the callback the operation would continue. When returning false from the callback, the operation would abort (and bit7z code would take care of cleanup of the temporary items it created, if there are any such items to be trashed).

The same way pausing the operation would be implemented. The archiving operation would pause until the code returns from callback. (maybe this is already possible, I have not tested it yet)

PS: I have just discovered this amazing library so I do not have much experience with it. So these are just a bunch of ideas...

rikyoz commented 2 years ago

Isn't it possible to implement cancelling/pausing/resuming/progress via callbacks somehow?

With the current stable version, no, sorry. It will be possible with the next major version, though.

There could be some new "operation status" callback or maybe it can be incorporated into existing callback (progress callback?). When returning true from the callback the operation would continue. When returning false from the callback, the operation would abort (and bit7z code would take care of cleanup of the temporary items it created, if there are any such items to be trashed).

This is more or less how it will be implemented in the next major version. I changed the progress callback signature, which now returns a bool.

The same way pausing the operation would be implemented. The archiving operation would pause until the code returns from callback. (maybe this is already possible, I have not tested it yet)

Yes, the same progress callback can also be used for pausing the operation. However, the current implementation needs the callback code to provide the actual pause mechanism (e.g., via a std::condition_var or a std::mutex). Maybe in the future, bit7z could provide a friendlier interface for this functionality.

PS: I have just discovered this amazing library so I do not have much experience with it. So these are just a bunch of ideas...

Thank you for appreciating this project!

chaseYLC commented 2 years ago

I would like to write a backup program but I need to allow the user to cancel a compression / decompression / verification operation. How can I do?

I used the provided callback function for the 'cancel' operation

try {
   ...
   BitExtractor extractor{ lib, BitFormat::SevenZip };
   ...
   extractor.setProgressCallback([&](uint64_t progress_size) {
        if (progress_size) {
            std::cout << "progress : " << progress_size << std::endl;

            //
            // When the user requests cancellation I turn on 'myCancelFlag'
            //
            if( myCancelFlag ){
                throw std::runtime_error("terminated in force by user-request");
            }
        }});
}
catch (const BitException& ex) {
     std::cout << "exception : " << ex.what();
}
fabri9532 commented 2 years ago

I have tested the code provided by chaseYLC in a Windows Forms application and the program break with an "Unhandled exception".

rikyoz commented 2 years ago

Sorry for the late reply!

I have tested the code provided by chaseYLC in a Windows Forms application and the program break with an "Unhandled exception".

@fabri9532 I will need to test this way of implementing the cancellation of long operations. However, your issue may be because, in the code snippet, a std::runtime_error is thrown, while the catch clause captures a BitException.

Since also BitException derives from std::runtime_error, you may try something like

//...
catch (const std::runtime_error& ex) {
     std::cout << "exception : " << ex.what();
}

or, if you want to distinguish between the two, you may try with this order of catches:

//...
catch (const BitException& ex) {
     std::cout << "exception : " << ex.what();
}
catch (const std::runtime_error& ex) {
     // Interrupted by the user
}

Anyway, thanks @chaseYLC for sharing your workaround!

fabri9532 commented 2 years ago

Tested with: catch (const std::runtime_error& ex) Same result.

vladimir-kraus commented 2 years ago

@fabri9532 Without knowing your code I may only guess: do you enclose in try/catch the block where you set the progress callback or the block where you actually run the extraction with BitExtractor::extract()? I believe it should be the latter, not the former.

The code snippet provided by @chaseYLC may be confusing since it encloses setting the callback but not the extraction.

I have not tested it, so I am just guessing.

fabri9532 commented 2 years ago

Hi vladimir-kraus,

I tested the code not with bit7z::BitExtractor but with bit7z::BitCompressor. My application is a Windows Forms application running on VC++ 2017. My compress code run in a separated task. I have enclosed with the try/catch block the bit7z::BitCompressor::compress(). But I get "Unhandled exception" in the progress callback at: throw std::runtime_error("terminated in force by user-request")

rikyoz commented 11 months ago

Implemented in v4.0.0.