|
25 | 25 | #include <kj/test.h> |
26 | 26 | #include <map> |
27 | 27 | #include <memory> |
| 28 | +#include <mutex> |
28 | 29 | #include <mp/config.h> |
29 | 30 | #include <mp/proxy.h> |
30 | 31 | #include <mp/proxy.capnp.h> |
@@ -666,5 +667,99 @@ KJ_TEST("Call async IPC method without thread or pool errors correctly") |
666 | 667 | KJ_EXPECT(error_thrown); |
667 | 668 | } |
668 | 669 |
|
| 670 | +KJ_TEST("Cancel an in-flight IPC call") |
| 671 | +{ |
| 672 | + TestSetup setup; |
| 673 | + ProxyClient<messages::FooInterface>* foo = setup.client.get(); |
| 674 | + foo->initThreadMap(); |
| 675 | + std::promise<void> waiting; |
| 676 | + std::promise<void> done; |
| 677 | + |
| 678 | + // Install a function that blocks until its `CancelArg` fires, so |
| 679 | + // cancellation is the only way out. |
| 680 | + setup.server->m_impl->m_cancel_fn = [&](CancelArg cancel) { |
| 681 | + std::mutex mutex; |
| 682 | + std::condition_variable cv; |
| 683 | + bool canceled = false; |
| 684 | + cancel([&] { |
| 685 | + const std::lock_guard<std::mutex> lock{mutex}; |
| 686 | + canceled = true; |
| 687 | + cv.notify_all(); |
| 688 | + }); |
| 689 | + std::unique_lock<std::mutex> lock{mutex}; |
| 690 | + waiting.set_value(); |
| 691 | + cv.wait(lock, [&] { return canceled; }); |
| 692 | + done.set_value(); |
| 693 | + }; |
| 694 | + |
| 695 | + std::promise<CancelFn> cancel_fn; |
| 696 | + std::thread canceler([&] { |
| 697 | + CancelFn fire{cancel_fn.get_future().get()}; |
| 698 | + waiting.get_future().wait(); |
| 699 | + fire(); |
| 700 | + }); |
| 701 | + bool interrupted = false; |
| 702 | + try { |
| 703 | + foo->callCancelFnAsync([&](CancelFn fn) { cancel_fn.set_value(std::move(fn)); }); |
| 704 | + } catch (const InterruptException&) { |
| 705 | + interrupted = true; |
| 706 | + } |
| 707 | + canceler.join(); |
| 708 | + KJ_EXPECT(interrupted); |
| 709 | + KJ_EXPECT(done.get_future().wait_for(std::chrono::minutes{5}) == std::future_status::ready); |
| 710 | + |
| 711 | + // Connection should be unaffected. |
| 712 | + KJ_EXPECT(foo->add(1, 2) == 3); |
| 713 | +} |
| 714 | + |
| 715 | +KJ_TEST("Dropping the client promise cancels an executing method") |
| 716 | +{ |
| 717 | + TestSetup setup; |
| 718 | + constexpr std::chrono::seconds timeout{30}; |
| 719 | + std::promise<void> waiting; |
| 720 | + std::promise<void> done; |
| 721 | + |
| 722 | + // Install a function that blocks until its `CancelArg` fires, so |
| 723 | + // cancellation is the only way out. |
| 724 | + setup.server->m_impl->m_cancel_fn = [&](CancelArg cancel) { |
| 725 | + std::mutex mutex; |
| 726 | + std::condition_variable cv; |
| 727 | + bool canceled = false; |
| 728 | + cancel([&] { |
| 729 | + const std::lock_guard<std::mutex> lock{mutex}; |
| 730 | + canceled = true; |
| 731 | + cv.notify_all(); |
| 732 | + }); |
| 733 | + std::unique_lock<std::mutex> lock{mutex}; |
| 734 | + waiting.set_value(); |
| 735 | + cv.wait(lock, [&] { return canceled; }); |
| 736 | + done.set_value(); |
| 737 | + }; |
| 738 | + ProxyClient<messages::FooInterface>* foo{setup.client.get()}; |
| 739 | + foo->initThreadMap(); |
| 740 | + |
| 741 | + // Build the request by hand, the way a non-C++ client would. A normal |
| 742 | + // proxy call cannot be abandoned because `clientInvoke` blocks on it. |
| 743 | + std::optional<capnp::RemotePromise<messages::FooInterface::CallCancelFnAsyncResults>> remote; |
| 744 | + foo->m_context.loop->sync([&] { |
| 745 | + auto request{foo->m_client.callCancelFnAsyncRequest()}; |
| 746 | + request.initContext().setThread( |
| 747 | + foo->m_context.connection->m_thread_map.makeThreadRequest().send().getResult()); |
| 748 | + remote.emplace(request.send()); |
| 749 | + }); |
| 750 | + KJ_REQUIRE(waiting.get_future().wait_for(timeout) == std::future_status::ready); |
| 751 | + |
| 752 | + auto done_future{done.get_future()}; |
| 753 | + KJ_EXPECT(done_future.wait_for(std::chrono::seconds{0}) == std::future_status::timeout); |
| 754 | + |
| 755 | + // Abandon the call without disconnecting. |
| 756 | + foo->m_context.loop->sync([&] { remote.reset(); }); |
| 757 | + |
| 758 | + KJ_EXPECT(done_future.wait_for(timeout) == std::future_status::ready); |
| 759 | + |
| 760 | + // Connection should be unaffected. |
| 761 | + KJ_EXPECT(foo->add(1, 2) == 3); |
| 762 | +} |
| 763 | + |
669 | 764 | } // namespace test |
670 | 765 | } // namespace mp |
0 commit comments