From e45e5b613b7a263a73ec619b91a33d55e71b2f0f Mon Sep 17 00:00:00 2001 From: jrtxio Date: Mon, 19 Oct 2026 09:15:23 +0800 Subject: [PATCH] fix: settle generated C++ bounded waits --- CHANGELOG.md | 6 ++++++ platform/linux/host/GeneratedBackend.hpp | 10 ++++++---- platform/windows/host/GeneratedBackend.hpp | 10 ++++++---- rivet-cli/codegen/cpp.rkt | 9 +++++---- rivet/tests/codegen.rkt | 12 ++++++++++++ 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 007555f..5e8ae89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +- Fix generated C++ bounded-wait RPCs so each promise yields exactly one + future, reused for both the timeout wait and the result read. Void RPCs now + also fulfill their promise after decoding a successful response; previously + non-Void calls threw `future_already_retrieved` and Void calls always timed + out even when the backend had completed. + - Add executable product acceptance contracts. `raco rivet acceptance --json` validates `rivet-acceptance.rktd`, rejects missing kind/platform coverage, launches each current-platform suite as an executable plus argv diff --git a/platform/linux/host/GeneratedBackend.hpp b/platform/linux/host/GeneratedBackend.hpp index 44adcfe..327f9a6 100644 --- a/platform/linux/host/GeneratedBackend.hpp +++ b/platform/linux/host/GeneratedBackend.hpp @@ -75,6 +75,7 @@ class API { std::optional greet_within( std::string name, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); + auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async( "greet", rivet::linux_runtime::Value::List{rivet::Value(std::move(name))}, [rivet_done](rivet::linux_runtime::CallResult call) mutable { @@ -85,17 +86,18 @@ class API { rivet_done->set_value(*result.value); } }); - if (rivet_done->get_future().wait_for(rivet_timeout) != + if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } - return rivet_done->get_future().get(); + return rivet_future.get(); } std::optional increment_within( std::int64_t value, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); + auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async( "increment", rivet::linux_runtime::Value::List{rivet::Value(value)}, [rivet_done](rivet::linux_runtime::CallResult call) mutable { @@ -106,12 +108,12 @@ class API { rivet_done->set_value(*result.value); } }); - if (rivet_done->get_future().wait_for(rivet_timeout) != + if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } - return rivet_done->get_future().get(); + return rivet_future.get(); } // State accessors: completion callbacks run on the backend reader thread diff --git a/platform/windows/host/GeneratedBackend.hpp b/platform/windows/host/GeneratedBackend.hpp index 1378d47..72646e0 100644 --- a/platform/windows/host/GeneratedBackend.hpp +++ b/platform/windows/host/GeneratedBackend.hpp @@ -75,6 +75,7 @@ class API { std::optional greet_within( std::string name, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); + auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async( "greet", rivet::windows::Value::List{rivet::Value(std::move(name))}, [rivet_done](rivet::windows::CallResult call) mutable { @@ -85,17 +86,18 @@ class API { rivet_done->set_value(*result.value); } }); - if (rivet_done->get_future().wait_for(rivet_timeout) != + if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } - return rivet_done->get_future().get(); + return rivet_future.get(); } std::optional increment_within( std::int64_t value, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); + auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async( "increment", rivet::windows::Value::List{rivet::Value(value)}, [rivet_done](rivet::windows::CallResult call) mutable { @@ -106,12 +108,12 @@ class API { rivet_done->set_value(*result.value); } }); - if (rivet_done->get_future().wait_for(rivet_timeout) != + if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } - return rivet_done->get_future().get(); + return rivet_future.get(); } // State accessors: completion callbacks run on the backend reader thread diff --git a/rivet-cli/codegen/cpp.rkt b/rivet-cli/codegen/cpp.rkt index c4aaabc..c63ccca 100644 --- a/rivet-cli/codegen/cpp.rkt +++ b/rivet-cli/codegen/cpp.rkt @@ -158,7 +158,8 @@ (define (cpp-promise-handler result-type backend-namespace) (define settle (if (eq? result-type 'Void) - (format "detail::decode_~a(*rivet_result.value);" (type-key result-type)) + (format "detail::decode_~a(*rivet_result.value); rivet_done->set_value();" + (type-key result-type)) (format "rivet_done->set_value(detail::decode_~a(*rivet_result.value));" (type-key result-type)))) (format @@ -186,8 +187,8 @@ (define timed-out-return (if void? "false" "std::nullopt")) (define final-get (if void? - "rivet_done->get_future().get(); return true;" - "return rivet_done->get_future().get();")) + "rivet_future.get(); return true;" + "return rivet_future.get();")) ;; A zero-argument RPC has no parameter list; the timeout must not follow ;; a stray comma. (define within-params @@ -195,7 +196,7 @@ "std::chrono::milliseconds rivet_timeout" (format "~a, std::chrono::milliseconds rivet_timeout" params))) (format - " ~a ~a_within(~a) { auto rivet_done = std::make_shared>(); auto rivet_id = backend_.request_async(~a, rivet::Value::List{~a}, ~a); if (rivet_done->get_future().wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return ~a; } ~a }\n" + " ~a ~a_within(~a) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async(~a, rivet::Value::List{~a}, ~a); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return ~a; } ~a }\n" return-type (cpp-id (schema-rpc-name info)) within-params diff --git a/rivet/tests/codegen.rkt b/rivet/tests/codegen.rkt index 52a2ab9..da376c5 100644 --- a/rivet/tests/codegen.rkt +++ b/rivet/tests/codegen.rkt @@ -194,6 +194,18 @@ RKT (check-true (string-contains? cpp "bool log_line_within(std::string message, std::chrono::milliseconds rivet_timeout)")) (check-true (string-contains? cpp "backend_.cancel(rivet_id); return false;")) (check-true (string-contains? cpp "rivet_done->set_value(detail::decode_Int64(*rivet_result.value));")) + (check-true + (string-contains? cpp + "auto rivet_future = rivet_done->get_future();")) + (check-true + (string-contains? cpp + "rivet_future.wait_for(rivet_timeout)")) + (check-true (string-contains? cpp "return rivet_future.get();")) + (check-false + (string-contains? cpp "rivet_done->get_future().wait_for")) + (check-true + (string-contains? cpp + "detail::decode_Void(*rivet_result.value); rivet_done->set_value();")) ;; A Void async handler must not assign into Result, which has no ;; value member — the decode only validates arrival. (check-false (string-contains? cpp "result.value = detail::decode_void"))