Skip to content
This repository has been archived by the owner on Dec 8, 2021. It is now read-only.

feat: support cancellation for long running operations #160

Merged
merged 3 commits into from
Feb 6, 2020

Conversation

tmatsuo
Copy link
Contributor

@tmatsuo tmatsuo commented Feb 5, 2020

This change is Reviewable

@tmatsuo tmatsuo requested a review from coryan February 5, 2020 20:59
@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Feb 5, 2020
@codecov
Copy link

codecov bot commented Feb 5, 2020

Codecov Report

Merging #160 into master will decrease coverage by 0.07%.
The diff coverage is 90.19%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #160      +/-   ##
==========================================
- Coverage   95.66%   95.59%   -0.08%     
==========================================
  Files          83       83              
  Lines        3900     3947      +47     
==========================================
+ Hits         3731     3773      +42     
- Misses        169      174       +5
Impacted Files Coverage Δ
google/cloud/internal/future_base.h 100% <100%> (ø) ⬆️
google/cloud/future_generic_test.cc 98.91% <100%> (+0.05%) ⬆️
google/cloud/future_void_test.cc 98.94% <100%> (+0.05%) ⬆️
google/cloud/future_void.h 96.87% <50%> (-3.13%) ⬇️
google/cloud/future_generic.h 96.96% <50%> (-3.04%) ⬇️
google/cloud/internal/future_impl.h 90.32% <82.35%> (-0.86%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e932202...42f593b. Read the comment docs.

Copy link
Contributor

@coryan coryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really good, a few suggestions to reduce the code duplication and some questions around how to report and handle errors.

Reviewed 6 of 6 files at r1.
Reviewable status: all files reviewed, 9 unresolved discussions (waiting on @tmatsuo)


google/cloud/future_generic.h, line 82 at r1 (raw file):

   * Cancel the future by invoking cancel() on the shared state.
   */
  void cancel() { this->shared_state_->cancel(); }

It seems like this function can be refactored to future_base<T>?

What is the desired behavior if somebody tries to cancel a satisfied future? I would find it surprising for the cancel request to be successful in that case. Maybe it should fail? More on how to do this later.


google/cloud/future_generic.h, line 182 at r1 (raw file):

   * Returns if it is "cancelled".
   */
  bool cancelled() { return this->shared_state_->cancelled_; }

I think a more interesting accessor is cancellable() which should return false for any promise that is satisfied or that has been cancelled already. I think we should refactor that behavior to shared_state_.


google/cloud/future_generic_test.cc, line 638 at r1 (raw file):

// this is just giving implementors freedom.

/// @test Verify the behavior around cancellation.

I think we should also have tests for canceling a future that is already satisfied, and satisfying a future that has a cancel request on it. The first should fail (with an error TBD) the second should succeed.


google/cloud/future_void.h, line 79 at r1 (raw file):

   * Cancel the future by invoking cancel() on the shared state.
   */
  void cancel() { shared_state_->cancel(); }

Same comments as above re: refactoring to future_base


google/cloud/future_void.h, line 178 at r1 (raw file):

   * Returns if it is "cancelled".
   */
  bool cancelled() { return shared_state_->cancelled_; }

Same comments as above re: cancellable()


google/cloud/future_void_test.cc, line 647 at r1 (raw file):

// this is just giving implementors freedom.

/// @test Verify the behavior around cancellation.

Same comments as above re: additional tests.


google/cloud/internal/future_base.h, line 147 at r1 (raw file):

class promise_base {
 public:
  promise_base(std::function<void()> cancellation_callback = [] {})

We have the default callback specified in two places, consider using an explicit default constructor for this class.


google/cloud/internal/future_impl.h, line 71 at r1 (raw file):

class future_shared_state_base {
 public:
  future_shared_state_base(std::function<void()> cancellation_callback = [] {})

You can also do:

future_shared_state_base() : future_shared_state_base([]{}) {}

Which might be more readable?


google/cloud/internal/future_impl.h, line 208 at r1 (raw file):

    }
    cancellation_callback_();
    cancelled_ = true;

This is not set if an exception is thrown by cancellation_callback_(). That might or might not be the desired behavior. I think it is: if the cancel callback throws then it could not "do its job" which should be "request a cancellation". Maybe a comment explaining this decision?


google/cloud/internal/future_impl.h, line 262 at r1 (raw file):

  }

  // My (@coryan) reading of the spec is that calling get_future() on a promise

BTW, here is another reason to keep the cancelled_ flag in the shared state, I had forgotten about it.

Copy link
Contributor Author

@tmatsuo tmatsuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 6 files reviewed, 9 unresolved discussions (waiting on @coryan)


google/cloud/future_generic.h, line 82 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

It seems like this function can be refactored to future_base<T>?

What is the desired behavior if somebody tries to cancel a satisfied future? I would find it surprising for the cancel request to be successful in that case. Maybe it should fail? More on how to do this later.

How about to return bool?


google/cloud/future_generic.h, line 182 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

I think a more interesting accessor is cancellable() which should return false for any promise that is satisfied or that has been cancelled already. I think we should refactor that behavior to shared_state_.

Done


google/cloud/future_generic_test.cc, line 638 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

I think we should also have tests for canceling a future that is already satisfied, and satisfying a future that has a cancel request on it. The first should fail (with an error TBD) the second should succeed.

Done


google/cloud/future_void.h, line 79 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

Same comments as above re: refactoring to future_base

Done


google/cloud/future_void.h, line 178 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

Same comments as above re: cancellable()

Done


google/cloud/future_void_test.cc, line 647 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

Same comments as above re: additional tests.

Done


google/cloud/internal/future_base.h, line 147 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

We have the default callback specified in two places, consider using an explicit default constructor for this class.

Done


google/cloud/internal/future_impl.h, line 71 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

You can also do:

future_shared_state_base() : future_shared_state_base([]{}) {}

Which might be more readable?

Done


google/cloud/internal/future_impl.h, line 208 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

This is not set if an exception is thrown by cancellation_callback_(). That might or might not be the desired behavior. I think it is: if the cancel callback throws then it could not "do its job" which should be "request a cancellation". Maybe a comment explaining this decision?

I introduced try and catch, also added some comments

@codecov-io
Copy link

codecov-io commented Feb 6, 2020

Codecov Report

Merging #160 into master will decrease coverage by 0.02%.
The diff coverage is 93.75%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #160      +/-   ##
==========================================
- Coverage   95.66%   95.63%   -0.03%     
==========================================
  Files          83       83              
  Lines        3900     3944      +44     
==========================================
+ Hits         3731     3772      +41     
- Misses        169      172       +3
Impacted Files Coverage Δ
google/cloud/internal/future_base.h 100% <100%> (ø) ⬆️
google/cloud/future_generic_test.cc 98.91% <100%> (+0.05%) ⬆️
google/cloud/future_void_test.cc 98.94% <100%> (+0.05%) ⬆️
google/cloud/future_void.h 96.87% <50%> (-3.13%) ⬇️
google/cloud/future_generic.h 96.96% <50%> (-3.04%) ⬇️
google/cloud/internal/future_impl.h 91.3% <93.33%> (+0.12%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e932202...d86a3a3. Read the comment docs.

Copy link
Contributor

@coryan coryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I liked cancel() better before. Thoughts?

Reviewed 6 of 6 files at r2.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @tmatsuo)


google/cloud/future_generic_test.cc, line 648 at r2 (raw file):

/// @test Verify the behavior around cancellation.
TEST(FutureTestInt, cancellation_and_sdatisfaction) {

nit: typo is sdatis -> satis


google/cloud/internal/future_impl.h, line 208 at r1 (raw file):

Previously, tmatsuo (Takashi Matsuo) wrote…

I introduced try and catch, also added some comments

I liked it better before: less code and more information returned to the application. Returning a bool seems like a good idea, so what do you think of this code:

bool cancel() {
  if (!cancellable()) return false;
  cancellation_callback_();
  // If the callback fails with an exception we assume it had no effect.
  // Incidentally this means we provide the strong exception guarantee for this function.
  cancelled_ = true;
  return true;
}

Copy link
Contributor Author

@tmatsuo tmatsuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, PTAL

Reviewable status: 3 of 6 files reviewed, 1 unresolved discussion (waiting on @coryan)


google/cloud/internal/future_impl.h, line 208 at r1 (raw file):

Previously, coryan (Carlos O'Ryan) wrote…

I liked it better before: less code and more information returned to the application. Returning a bool seems like a good idea, so what do you think of this code:

bool cancel() {
  if (!cancellable()) return false;
  cancellation_callback_();
  // If the callback fails with an exception we assume it had no effect.
  // Incidentally this means we provide the strong exception guarantee for this function.
  cancelled_ = true;
  return true;
}

I somehow thought differently, but now I agree.

Copy link
Contributor

@coryan coryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 3 of 3 files at r3.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved

@tmatsuo tmatsuo merged commit bfe16d3 into googleapis:master Feb 6, 2020
@tmatsuo tmatsuo deleted the change-the-future branch February 6, 2020 18:33
@@ -279,6 +299,10 @@ class future_shared_state_base {
* member variable and does not satisfy the shared state.
*/
std::unique_ptr<continuation_base> continuation_;

// Allow users "cancel" the future with the given callback.
std::atomic<bool> cancelled_ = ATOMIC_VAR_INIT(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why need this be atomic? Or, more generally, what are the concurrency requirements of cancel()? Must callers serialize cancel() calls? If so, atomicity doesn't seem to be required. If not, atomicity doesn't seem sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devbww Good point. Maybe I can change it to bool. It is sufficient for my use case.

coryan pushed a commit to coryan/google-cloud-cpp that referenced this pull request Apr 24, 2020
…ogle-cloud-cpp-common#160)

* feat: support cancellation for long running operations

* address review comments

* address code review comments
coryan pushed a commit to coryan/google-cloud-cpp that referenced this pull request Apr 24, 2020
…ogle-cloud-cpp-common#160)

* feat: support cancellation for long running operations

* address review comments

* address code review comments
coryan pushed a commit to coryan/google-cloud-cpp that referenced this pull request Apr 24, 2020
…ogle-cloud-cpp-common#160)

* feat: support cancellation for long running operations

* address review comments

* address code review comments
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants