Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
Actor Framework
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cpp-libs
Actor Framework
Commits
6129cc74
Unverified
Commit
6129cc74
authored
Jan 15, 2021
by
Noir
Committed by
GitHub
Jan 15, 2021
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1200
Detect and report broken response promises
parents
84c707d8
ff0c105e
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
434 additions
and
241 deletions
+434
-241
CHANGELOG.md
CHANGELOG.md
+4
-0
examples/message_passing/fixed_stack.cpp
examples/message_passing/fixed_stack.cpp
+1
-1
examples/message_passing/promises.cpp
examples/message_passing/promises.cpp
+2
-1
libcaf_core/caf/detail/default_invoke_result_visitor.hpp
libcaf_core/caf/detail/default_invoke_result_visitor.hpp
+2
-25
libcaf_core/caf/detail/typed_actor_util.hpp
libcaf_core/caf/detail/typed_actor_util.hpp
+6
-2
libcaf_core/caf/local_actor.hpp
libcaf_core/caf/local_actor.hpp
+22
-14
libcaf_core/caf/response_promise.hpp
libcaf_core/caf/response_promise.hpp
+155
-86
libcaf_core/caf/sec.hpp
libcaf_core/caf/sec.hpp
+2
-0
libcaf_core/caf/typed_response_promise.hpp
libcaf_core/caf/typed_response_promise.hpp
+65
-44
libcaf_core/src/response_promise.cpp
libcaf_core/src/response_promise.cpp
+138
-67
libcaf_core/src/sec_strings.cpp
libcaf_core/src/sec_strings.cpp
+6
-0
libcaf_core/src/stream_manager.cpp
libcaf_core/src/stream_manager.cpp
+1
-1
libcaf_core/test/response_promise.cpp
libcaf_core/test/response_promise.cpp
+15
-0
libcaf_core/test/typed_response_promise.cpp
libcaf_core/test/typed_response_promise.cpp
+15
-0
No files found.
CHANGELOG.md
View file @
6129cc74
...
@@ -49,6 +49,10 @@ is based on [Keep a Changelog](https://keepachangelog.com).
...
@@ -49,6 +49,10 @@ is based on [Keep a Changelog](https://keepachangelog.com).
string input on the command-line that starts with quotes in the same way it
string input on the command-line that starts with quotes in the same way it
would parse strings from a config file, leading to very unintuitive results in
would parse strings from a config file, leading to very unintuitive results in
some cases (#1113).
some cases (#1113).
-
Response promises now implicitly share their state when copied. Once the
reference count for the state reaches zero, CAF now produces a
`broken_promise`
error if the actor failed to fulfill the promise by calling
either
`dispatch`
or
`delegate`
.
### Fixed
### Fixed
...
...
examples/message_passing/fixed_stack.cpp
View file @
6129cc74
...
@@ -43,7 +43,7 @@ bool from_string(caf::string_view in, fixed_stack_errc& out) {
...
@@ -43,7 +43,7 @@ bool from_string(caf::string_view in, fixed_stack_errc& out) {
}
}
bool
from_integer
(
uint8_t
in
,
fixed_stack_errc
&
out
)
{
bool
from_integer
(
uint8_t
in
,
fixed_stack_errc
&
out
)
{
if
(
in
>
0
&&
in
<
1
)
{
if
(
in
>
0
&&
in
<
3
)
{
out
=
static_cast
<
fixed_stack_errc
>
(
in
);
out
=
static_cast
<
fixed_stack_errc
>
(
in
);
return
true
;
return
true
;
}
else
{
}
else
{
...
...
examples/message_passing/promises.cpp
View file @
6129cc74
...
@@ -16,7 +16,8 @@ adder_actor::behavior_type server_impl(adder_actor::pointer self,
...
@@ -16,7 +16,8 @@ adder_actor::behavior_type server_impl(adder_actor::pointer self,
[
=
](
add_atom
,
int32_t
y
,
int32_t
z
)
{
[
=
](
add_atom
,
int32_t
y
,
int32_t
z
)
{
auto
rp
=
self
->
make_response_promise
<
int32_t
>
();
auto
rp
=
self
->
make_response_promise
<
int32_t
>
();
self
->
request
(
worker
,
infinite
,
add_atom_v
,
y
,
z
)
self
->
request
(
worker
,
infinite
,
add_atom_v
,
y
,
z
)
.
then
([
rp
](
int32_t
result
)
mutable
{
rp
.
deliver
(
result
);
});
.
then
([
rp
](
int32_t
result
)
mutable
{
rp
.
deliver
(
result
);
},
[
rp
](
error
&
err
)
mutable
{
rp
.
deliver
(
std
::
move
(
err
));
});
return
rp
;
return
rp
;
},
},
};
};
...
...
libcaf_core/caf/detail/default_invoke_result_visitor.hpp
View file @
6129cc74
...
@@ -27,38 +27,15 @@ public:
...
@@ -27,38 +27,15 @@ public:
void
operator
()(
error
&
x
)
override
{
void
operator
()(
error
&
x
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
x
));
CAF_LOG_TRACE
(
CAF_ARG
(
x
));
delegate
(
x
);
self_
->
respond
(
x
);
}
}
void
operator
()(
message
&
x
)
override
{
void
operator
()(
message
&
x
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
x
));
CAF_LOG_TRACE
(
CAF_ARG
(
x
));
delegate
(
x
);
self_
->
respond
(
x
);
}
}
private:
private:
void
deliver
(
response_promise
&
rp
,
error
&
x
)
{
CAF_LOG_DEBUG
(
"report error back to requesting actor"
);
rp
.
deliver
(
std
::
move
(
x
));
}
void
deliver
(
response_promise
&
rp
,
message
&
x
)
{
CAF_LOG_DEBUG
(
"respond via response_promise"
);
// suppress empty messages for asynchronous messages
if
(
x
.
empty
()
&&
rp
.
async
())
return
;
rp
.
deliver
(
std
::
move
(
x
));
}
template
<
class
T
>
void
delegate
(
T
&
x
)
{
auto
rp
=
self_
->
make_response_promise
();
if
(
!
rp
.
pending
())
{
CAF_LOG_DEBUG
(
"suppress response message: invalid response promise"
);
return
;
}
deliver
(
rp
,
x
);
}
Self
*
self_
;
Self
*
self_
;
};
};
...
...
libcaf_core/caf/detail/typed_actor_util.hpp
View file @
6129cc74
...
@@ -23,14 +23,18 @@ struct make_response_promise_helper {
...
@@ -23,14 +23,18 @@ struct make_response_promise_helper {
};
};
template
<
class
...
Ts
>
template
<
class
...
Ts
>
struct
make_response_promise_helper
<
typed_response_promise
<
Ts
...
>>
struct
make_response_promise_helper
<
typed_response_promise
<
Ts
...
>>
{
:
make_response_promise_helper
<
Ts
...
>
{};
using
type
=
typed_response_promise
<
Ts
...
>
;
};
template
<
>
template
<
>
struct
make_response_promise_helper
<
response_promise
>
{
struct
make_response_promise_helper
<
response_promise
>
{
using
type
=
response_promise
;
using
type
=
response_promise
;
};
};
template
<
class
...
Ts
>
using
response_promise_t
=
typename
make_response_promise_helper
<
Ts
...
>::
type
;
template
<
class
Output
,
class
F
>
template
<
class
Output
,
class
F
>
struct
type_checker
{
struct
type_checker
{
static
void
check
()
{
static
void
check
()
{
...
...
libcaf_core/caf/local_actor.hpp
View file @
6129cc74
...
@@ -341,11 +341,15 @@ public:
...
@@ -341,11 +341,15 @@ public:
/// `make_response_promise<typed_response_promise<int, int>>()`
/// `make_response_promise<typed_response_promise<int, int>>()`
/// is equivalent to `make_response_promise<int, int>()`.
/// is equivalent to `make_response_promise<int, int>()`.
template
<
class
...
Ts
>
template
<
class
...
Ts
>
typename
detail
::
make_response_promise_helper
<
Ts
...
>::
type
detail
::
response_promise_t
<
Ts
...
>
make_response_promise
()
{
make_response_promise
()
{
using
result_t
=
typename
detail
::
make_response_promise_helper
<
Ts
...
>::
type
;
if
(
current_element_
==
nullptr
||
current_element_
->
mid
.
is_answered
())
if
(
current_element_
!=
nullptr
&&
!
current_element_
->
mid
.
is_answered
())
{
auto
result
=
result_t
{
this
,
*
current_element_
};
current_element_
->
mid
.
mark_as_answered
();
return
result
;
}
else
{
return
{};
return
{};
return
{
this
->
ctrl
(),
*
current_element_
};
}
}
}
/// Creates a `response_promise` to respond to a request later on.
/// Creates a `response_promise` to respond to a request later on.
...
@@ -353,16 +357,15 @@ public:
...
@@ -353,16 +357,15 @@ public:
return
make_response_promise
<
response_promise
>
();
return
make_response_promise
<
response_promise
>
();
}
}
/// Creates a `typed_response_promise` and responds immediately.
template
<
class
...
Ts
>
/// Return type is deduced from arguments.
[[
deprecated
(
"simply return the result from the message handler"
)]]
//
/// Return value is implicitly convertible to untyped response promise.
detail
::
response_promise_t
<
std
::
decay_t
<
Ts
>
...
>
template
<
class
...
Ts
,
response
(
Ts
&&
...
xs
)
{
class
R
=
typename
detail
::
make_response_promise_helper
<
if
(
current_element_
)
{
typename
std
::
decay
<
Ts
>
::
type
...
>::
type
>
response_promise
::
respond_to
(
this
,
current_element_
,
R
response
(
Ts
&&
...
xs
)
{
make_message
(
std
::
forward
<
Ts
>
(
xs
)...));
auto
promise
=
make_response_promise
<
R
>
();
}
promise
.
deliver
(
std
::
forward
<
Ts
>
(
xs
)...);
return
{};
return
promise
;
}
}
const
char
*
name
()
const
override
;
const
char
*
name
()
const
override
;
...
@@ -429,6 +432,11 @@ public:
...
@@ -429,6 +432,11 @@ public:
message_id
new_request_id
(
message_priority
mp
);
message_id
new_request_id
(
message_priority
mp
);
template
<
class
T
>
void
respond
(
T
&
x
)
{
response_promise
::
respond_to
(
this
,
current_mailbox_element
(),
x
);
}
/// @endcond
/// @endcond
protected:
protected:
...
...
libcaf_core/caf/response_promise.hpp
View file @
6129cc74
...
@@ -11,68 +11,133 @@
...
@@ -11,68 +11,133 @@
#include "caf/actor_cast.hpp"
#include "caf/actor_cast.hpp"
#include "caf/check_typed_input.hpp"
#include "caf/check_typed_input.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/message.hpp"
#include "caf/message.hpp"
#include "caf/message_id.hpp"
#include "caf/message_id.hpp"
#include "caf/response_type.hpp"
#include "caf/response_type.hpp"
namespace
caf
{
namespace
caf
{
///
A response promise can be used to deliver a uniquely identifiable
///
Enables actors to delay a response message by capturing the context of a
/// re
sponse message from the server (i.e. receiver of the request)
/// re
quest message. This is particularly useful when an actor needs to
///
to the client (i.e. the sender of the request)
.
///
communicate to other actors in order to fulfill a request for a client
.
class
CAF_CORE_EXPORT
response_promise
{
class
CAF_CORE_EXPORT
response_promise
{
public:
public:
using
forwarding_stack
=
std
::
vector
<
strong_actor_ptr
>
;
// -- friends ----------------------------------------------------------------
/// Constructs an invalid response promise.
friend
class
local_actor
;
response_promise
();
response_promise
(
none_t
)
;
friend
class
stream_manager
;
response_promise
(
strong_actor_ptr
self
,
strong_actor_ptr
source
,
template
<
class
...
>
forwarding_stack
stages
,
message_id
id
);
friend
class
typed_response_promise
;
// -- member types -----------------------------------------------------------
using
forwarding_stack
=
std
::
vector
<
strong_actor_ptr
>
;
response_promise
(
strong_actor_ptr
self
,
mailbox_element
&
src
);
// -- constructors, destructors, and assignment operators --------------------
response_promise
()
=
default
;
response_promise
(
response_promise
&&
)
=
default
;
response_promise
(
response_promise
&&
)
=
default
;
response_promise
(
const
response_promise
&
)
=
default
;
response_promise
(
const
response_promise
&
)
=
default
;
response_promise
&
operator
=
(
response_promise
&&
)
=
default
;
response_promise
&
operator
=
(
response_promise
&&
)
=
default
;
response_promise
&
operator
=
(
const
response_promise
&
)
=
default
;
response_promise
&
operator
=
(
const
response_promise
&
)
=
default
;
/// Satisfies the promise by sending a non-error response message.
[[
deprecated
(
"use the default constructor instead"
)]]
response_promise
(
none_t
)
template
<
class
T
,
class
...
Ts
>
:
response_promise
()
{
detail
::
enable_if_t
<
((
sizeof
...(
Ts
)
>
0
)
// nop
||
(
!
std
::
is_convertible
<
T
,
error
>::
value
}
&&
!
std
::
is_same
<
detail
::
decay_t
<
T
>
,
unit_t
>::
value
))
&&
!
detail
::
is_expected
<
detail
::
decay_t
<
T
>>::
value
>
// -- properties -------------------------------------------------------------
deliver
(
T
&&
x
,
Ts
&&
...
xs
)
{
using
ts
=
detail
::
type_list
<
detail
::
decay_t
<
T
>
,
detail
::
decay_t
<
Ts
>
...
>
;
/// Returns whether this response promise replies to an asynchronous message.
static_assert
(
!
detail
::
tl_exists
<
ts
,
detail
::
is_result
>::
value
,
bool
async
()
const
noexcept
;
"it is not possible to deliver objects of type result<T>"
);
static_assert
(
!
detail
::
tl_exists
<
ts
,
detail
::
is_expected
>::
value
,
/// Queries whether this promise is a valid promise that is not satisfied yet.
bool
pending
()
const
noexcept
;
/// Returns the source of the corresponding request.
strong_actor_ptr
source
()
const
noexcept
;
/// Returns the remaining stages for the corresponding request.
forwarding_stack
stages
()
const
;
/// Returns the actor that will receive the response, i.e.,
/// `stages().front()` if `!stages().empty()` or `source()` otherwise.
strong_actor_ptr
next
()
const
noexcept
;
/// Returns the message ID of the corresponding request.
message_id
id
()
const
noexcept
;
// -- delivery ---------------------------------------------------------------
/// Satisfies the promise by sending the given message.
/// @note Drops empty messages silently when responding to an asynchronous
/// request message, i.e., if `async() == true`.
/// @post `pending() == false`
void
deliver
(
message
msg
);
/// Satisfies the promise by sending an error message.
/// @post `pending() == false`
void
deliver
(
error
x
);
/// Satisfies the promise by sending an empty message.
/// @note Sends no message if the request message was asynchronous, i.e., if
/// `async() == true`.
/// @post `pending() == false`
void
deliver
();
/// Satisfies the promise by sending an empty message.
/// @note Sends no message if the request message was asynchronous, i.e., if
/// `async() == true`.
/// @post `pending() == false`
void
deliver
(
unit_t
)
{
deliver
();
}
/// Satisfies the promise by sending `make_message(xs...)`.
/// @post `pending() == false`
template
<
class
...
Ts
>
void
deliver
(
Ts
...
xs
)
{
using
arg_types
=
detail
::
type_list
<
Ts
...
>
;
static_assert
(
!
detail
::
tl_exists
<
arg_types
,
detail
::
is_result
>::
value
,
"delivering a result<T> is not supported"
);
static_assert
(
!
detail
::
tl_exists
<
arg_types
,
detail
::
is_expected
>::
value
,
"mixing expected<T> with regular values is not supported"
);
"mixing expected<T> with regular values is not supported"
);
if
constexpr
(
sizeof
...(
Ts
)
==
0
if
(
pending
())
{
&&
std
::
is_same
<
message
,
std
::
decay_t
<
T
>>::
value
)
state_
->
deliver_impl
(
make_message
(
std
::
move
(
xs
)...));
deliver_impl
(
std
::
forward
<
T
>
(
x
));
state_
.
reset
();
else
}
deliver_impl
(
make_message
(
std
::
forward
<
T
>
(
x
),
std
::
forward
<
Ts
>
(
xs
)...));
}
}
/// Satisfies the promise by sending either an error or a non-error response
/// Satisfies the promise by sending the content of `x`, i.e., either a value
/// message.
/// of type `T` or an @ref error.
/// @post `pending() == false`
template
<
class
T
>
template
<
class
T
>
void
deliver
(
expected
<
T
>
x
)
{
void
deliver
(
expected
<
T
>
x
)
{
if
(
pending
())
{
if
(
x
)
{
if
(
x
)
{
if
constexpr
(
std
::
is_same
<
T
,
void
>::
value
)
if
constexpr
(
std
::
is_same
<
T
,
void
>::
value
deliver
();
||
std
::
is_same
<
T
,
unit_t
>::
value
)
state_
->
deliver_impl
(
make_message
());
else
else
deliver
(
std
::
move
(
*
x
));
state_
->
deliver_impl
(
make_message
(
std
::
move
(
*
x
)
));
}
else
{
}
else
{
deliver
(
std
::
move
(
x
.
error
(
)));
state_
->
deliver_impl
(
make_message
(
std
::
move
(
x
.
error
()
)));
}
}
state_
.
reset
();
}
}
}
// -- delegation -------------------------------------------------------------
/// Satisfies the promise by delegating to another actor.
/// Satisfies the promise by delegating to another actor.
/// @post `pending() == false`
template
<
message_priority
P
=
message_priority
::
normal
,
class
Handle
=
actor
,
template
<
message_priority
P
=
message_priority
::
normal
,
class
Handle
=
actor
,
class
...
Ts
>
class
...
Ts
>
delegated_response_type_t
<
delegated_response_type_t
<
...
@@ -84,73 +149,77 @@ public:
...
@@ -84,73 +149,77 @@ public:
typename
std
::
decay
<
Ts
>::
type
>::
type
...
>
;
typename
std
::
decay
<
Ts
>::
type
>::
type
...
>
;
static_assert
(
response_type_unbox
<
signatures_of_t
<
Handle
>
,
token
>::
valid
,
static_assert
(
response_type_unbox
<
signatures_of_t
<
Handle
>
,
token
>::
valid
,
"receiver does not accept given message"
);
"receiver does not accept given message"
);
if
(
pending
())
{
if
constexpr
(
P
==
message_priority
::
high
)
if
constexpr
(
P
==
message_priority
::
high
)
id_
=
id_
.
with_high_priority
();
state_
->
id
=
state_
->
id
.
with_high_priority
();
if
constexpr
(
std
::
is_same
<
detail
::
type_list
<
message
>
,
if
constexpr
(
std
::
is_same
<
detail
::
type_list
<
message
>
,
detail
::
type_list
<
std
::
decay_t
<
Ts
>
...
>>::
value
)
detail
::
type_list
<
std
::
decay_t
<
Ts
>
...
>>::
value
)
delegate_impl
(
actor_cast
<
abstract_actor
*>
(
dest
),
std
::
forward
<
Ts
>
(
xs
)...);
state_
->
delegate_impl
(
actor_cast
<
abstract_actor
*>
(
dest
),
std
::
forward
<
Ts
>
(
xs
)...);
else
else
delegate_impl
(
actor_cast
<
abstract_actor
*>
(
dest
),
state_
->
delegate_impl
(
actor_cast
<
abstract_actor
*>
(
dest
),
make_message
(
std
::
forward
<
Ts
>
(
xs
)...));
make_message
(
std
::
forward
<
Ts
>
(
xs
)...));
state_
.
reset
();
}
return
{};
return
{};
}
}
/// Satisfies the promise by sending an error response message.
private:
void
deliver
(
error
x
);
// -- constructors that are visible only to friends --------------------------
/// Satisfies the promise by sending an empty message if this promise has a
/// valid message ID, i.e., `async() == false`.
void
deliver
();
/// Satisfies the promise by sending an empty message if this promise has a
/// valid message ID, i.e., `async() == false`.
void
deliver
(
unit_t
)
{
deliver
();
}
/// Returns whether this response promise replies to an asynchronous message.
response_promise
(
local_actor
*
self
,
strong_actor_ptr
source
,
bool
async
()
const
;
forwarding_stack
stages
,
message_id
id
)
;
/// Queries whether this promise is a valid promise that is not satisfied yet.
response_promise
(
local_actor
*
self
,
mailbox_element
&
src
);
bool
pending
()
const
{
return
source_
!=
nullptr
||
!
stages_
.
empty
();
}
/// Returns the source of the corresponding request.
// -- utility functions that are visible only to friends ---------------------
const
strong_actor_ptr
&
source
()
const
{
return
source_
;
}
/// Returns the remaining stages for the corresponding request.
/// Sends @p response as if creating a response promise from @p self and
const
forwarding_stack
&
stages
()
const
{
/// @p request and then calling `deliver` on it but avoids extra overhead such
return
stages_
;
/// as heap allocations for the promise.
}
static
void
respond_to
(
local_actor
*
self
,
mailbox_element
*
request
,
message
&
response
);
/// Returns the actor that will receive the response, i.e.,
/// @copydoc respond_to
/// `stages().front()` if `!stages().empty()` or `source()` otherwise.
static
void
respond_to
(
local_actor
*
self
,
mailbox_element
*
request
,
strong_actor_ptr
next
()
const
{
error
&
response
);
return
stages_
.
empty
()
?
source_
:
stages_
.
front
();
}
/// Returns the message ID of the corresponding request.
// -- nested types -----------------------------------------------------------
message_id
id
()
const
{
return
id_
;
}
private:
// Note: response promises must remain local to their owner. Hence, we don't
/// Returns a downcasted version of `self_`.
// need a thread-safe reference count for the state.
local_actor
*
self_dptr
()
const
;
class
CAF_CORE_EXPORT
state
{
public:
state
()
=
default
;
state
(
const
state
&
)
=
delete
;
state
&
operator
=
(
const
state
&
)
=
delete
;
~
state
();
execution_unit
*
context
();
void
cancel
();
void
deliver_impl
(
message
msg
);
void
deliver_impl
(
message
msg
);
void
delegate_impl
(
abstract_actor
*
receiver
,
message
msg
);
void
delegate_impl
(
abstract_actor
*
receiver
,
message
msg
);
strong_actor_ptr
self_
;
mutable
size_t
ref_count
=
1
;
strong_actor_ptr
source_
;
local_actor
*
self
;
forwarding_stack
stages_
;
strong_actor_ptr
source
;
message_id
id_
;
forwarding_stack
stages
;
message_id
id
;
friend
void
intrusive_ptr_add_ref
(
const
state
*
ptr
)
{
++
ptr
->
ref_count
;
}
friend
void
intrusive_ptr_release
(
const
state
*
ptr
)
{
if
(
--
ptr
->
ref_count
==
0
)
delete
ptr
;
}
};
// -- member variables -------------------------------------------------------
intrusive_ptr
<
state
>
state_
;
};
};
}
// namespace caf
}
// namespace caf
libcaf_core/caf/sec.hpp
View file @
6129cc74
...
@@ -159,6 +159,8 @@ enum class sec : uint8_t {
...
@@ -159,6 +159,8 @@ enum class sec : uint8_t {
unsupported_operation
,
unsupported_operation
,
/// A key lookup failed.
/// A key lookup failed.
no_such_key
=
65
,
no_such_key
=
65
,
/// An destroyed a response promise without calling deliver or delegate on it.
broken_promise
,
};
};
// --(rst-sec-end)--
// --(rst-sec-end)--
...
...
libcaf_core/caf/typed_response_promise.hpp
View file @
6129cc74
...
@@ -12,43 +12,78 @@
...
@@ -12,43 +12,78 @@
namespace
caf
{
namespace
caf
{
/// A response promise can be used to deliver a uniquely identifiable
/// Enables statically typed actors to delay a response message by capturing the
/// response message from the server (i.e. receiver of the request)
/// context of a request message. This is particularly useful when an actor
/// to the client (i.e. the sender of the request).
/// needs to communicate to other actors in order to fulfill a request for a
/// client.
template
<
class
...
Ts
>
template
<
class
...
Ts
>
class
typed_response_promise
{
class
typed_response_promise
{
public:
public:
// -- friends ----------------------------------------------------------------
friend
class
local_actor
;
// -- member types -----------------------------------------------------------
using
forwarding_stack
=
response_promise
::
forwarding_stack
;
using
forwarding_stack
=
response_promise
::
forwarding_stack
;
/// Constructs an invalid response promise.
// -- constructors, destructors, and assignment operators --------------------
typed_response_promise
()
=
default
;
typed_response_promise
()
=
default
;
typed_response_promise
(
none_t
x
)
:
promise_
(
x
)
{
typed_response_promise
(
typed_response_promise
&&
)
=
default
;
typed_response_promise
(
const
typed_response_promise
&
)
=
default
;
typed_response_promise
&
operator
=
(
typed_response_promise
&&
)
=
default
;
typed_response_promise
&
operator
=
(
const
typed_response_promise
&
)
=
default
;
[[
deprecated
(
"use the default constructor instead"
)]]
//
typed_response_promise
(
none_t
x
)
:
promise_
(
x
)
{
// nop
// nop
}
}
typed_response_promise
(
strong_actor_ptr
self
,
strong_actor_ptr
source
,
// -- properties -------------------------------------------------------------
forwarding_stack
stages
,
message_id
id
)
:
promise_
(
std
::
move
(
self
),
std
::
move
(
source
),
std
::
move
(
stages
),
id
)
{
/// @copydoc response_promise::async
// nop
bool
async
()
const
{
return
promise_
.
async
();
}
}
typed_response_promise
(
strong_actor_ptr
self
,
mailbox_element
&
src
)
/// @copydoc response_promise::pending
:
promise_
(
std
::
move
(
self
),
src
)
{
bool
pending
()
const
{
// nop
return
promise_
.
pending
();
}
}
typed_response_promise
(
typed_response_promise
&&
)
=
default
;
/// @copydoc response_promise::source
typed_response_promise
(
const
typed_response_promise
&
)
=
default
;
strong_actor_ptr
source
()
const
{
typed_response_promise
&
operator
=
(
typed_response_promise
&&
)
=
default
;
return
promise_
.
source
();
typed_response_promise
&
operator
=
(
const
typed_response_promise
&
)
=
default
;
}
/// @copydoc response_promise::stages
forwarding_stack
stages
()
const
{
return
promise_
.
stages
();
}
/// @copydoc response_promise::next
strong_actor_ptr
next
()
const
{
return
promise_
.
next
();
}
/// @copydoc response_promise::id
message_id
id
()
const
{
return
promise_
.
id
();
}
/// Implicitly convertible to untyped response promise.
[[
deprecated
(
"use the typed_response_promise directly"
)]]
[[
deprecated
(
"Use the typed_response_promise directly."
)]]
operator
response_promise
&
()
{
operator
response_promise
&
()
{
return
promise_
;
return
promise_
;
}
}
// -- delivery ---------------------------------------------------------------
/// Satisfies the promise by sending a non-error response message.
/// Satisfies the promise by sending a non-error response message.
template
<
class
...
Us
>
template
<
class
...
Us
>
std
::
enable_if_t
<
(
std
::
is_constructible
<
Ts
,
Us
>::
value
&&
...)
>
std
::
enable_if_t
<
(
std
::
is_constructible
<
Ts
,
Us
>::
value
&&
...)
>
...
@@ -77,6 +112,8 @@ public:
...
@@ -77,6 +112,8 @@ public:
promise_
.
deliver
(
std
::
move
(
x
));
promise_
.
deliver
(
std
::
move
(
x
));
}
}
// -- delegation -------------------------------------------------------------
/// Satisfies the promise by delegating to another actor.
/// Satisfies the promise by delegating to another actor.
template
<
message_priority
P
=
message_priority
::
normal
,
class
Handle
=
actor
,
template
<
message_priority
P
=
message_priority
::
normal
,
class
Handle
=
actor
,
class
...
Us
>
class
...
Us
>
...
@@ -84,38 +121,22 @@ public:
...
@@ -84,38 +121,22 @@ public:
return
promise_
.
template
delegate
<
P
>(
dest
,
std
::
forward
<
Us
>
(
xs
)...);
return
promise_
.
template
delegate
<
P
>(
dest
,
std
::
forward
<
Us
>
(
xs
)...);
}
}
/// Returns whether this response promise replies to an asynchronous message.
private:
bool
async
()
const
{
// -- constructors that are visible only to friends --------------------------
return
promise_
.
async
();
}
/// Queries whether this promise is a valid promise that is not satisfied yet.
bool
pending
()
const
{
return
promise_
.
pending
();
}
/// Returns the source of the corresponding request.
const
strong_actor_ptr
&
source
()
const
{
return
promise_
.
source
();
}
/// Returns the remaining stages for the corresponding request.
typed_response_promise
(
local_actor
*
self
,
strong_actor_ptr
source
,
const
forwarding_stack
&
stages
()
const
{
forwarding_stack
stages
,
message_id
id
)
return
promise_
.
stages
();
:
promise_
(
self
,
std
::
move
(
source
),
std
::
move
(
stages
),
id
)
{
// nop
}
}
/// Returns the actor that will receive the response, i.e.,
typed_response_promise
(
local_actor
*
self
,
mailbox_element
&
src
)
/// `stages().front()` if `!stages().empty()` or `source()` otherwise.
:
promise_
(
self
,
src
)
{
strong_actor_ptr
next
()
const
{
// nop
return
promise_
.
next
();
}
}
/// Returns the message ID of the corresponding request.
// -- member variables -------------------------------------------------------
message_id
id
()
const
{
return
promise_
.
id
();
}
private:
response_promise
promise_
;
response_promise
promise_
;
};
};
...
...
libcaf_core/src/response_promise.cpp
View file @
6129cc74
...
@@ -14,105 +14,176 @@
...
@@ -14,105 +14,176 @@
namespace
caf
{
namespace
caf
{
response_promise
::
response_promise
()
:
self_
(
nullptr
)
{
namespace
{
// nop
bool
requires_response
(
const
strong_actor_ptr
&
source
,
const
mailbox_element
::
forwarding_stack
&
stages
,
message_id
mid
)
{
return
!
mid
.
is_response
()
&&
!
mid
.
is_answered
()
&&
(
source
||
!
stages
.
empty
());
}
}
response_promise
::
response_promise
(
none_t
)
:
response_promise
(
)
{
bool
requires_response
(
const
mailbox_element
&
src
)
{
// nop
return
requires_response
(
src
.
sender
,
src
.
stages
,
src
.
mid
);
}
}
response_promise
::
response_promise
(
strong_actor_ptr
self
,
}
// namespace
strong_actor_ptr
source
,
forwarding_stack
stages
,
message_id
mid
)
// -- constructors, destructors, and assignment operators ----------------------
:
id_
(
mid
)
{
response_promise
::
response_promise
(
local_actor
*
self
,
strong_actor_ptr
source
,
forwarding_stack
stages
,
message_id
mid
)
{
CAF_ASSERT
(
self
!=
nullptr
);
CAF_ASSERT
(
self
!=
nullptr
);
// Form an invalid request promise when initialized from a response ID, since
// Form an invalid request promise when initialized from a response ID, since
// we always drop messages in this case.
// we always drop messages in this case. Also don't create promises for
if
(
!
mid
.
is_response
())
{
// anonymous messages since there's nowhere to send the message to anyway.
self_
.
swap
(
self
);
if
(
requires_response
(
source
,
stages
,
mid
))
{
source_
.
swap
(
source
);
state_
=
make_counted
<
state
>
();
stages_
.
swap
(
stages
);
state_
->
self
=
self
;
state_
->
source
.
swap
(
source
);
state_
->
stages
.
swap
(
stages
);
state_
->
id
=
mid
;
}
}
}
}
response_promise
::
response_promise
(
strong_actor_ptr
self
,
mailbox_element
&
src
)
response_promise
::
response_promise
(
local_actor
*
self
,
mailbox_element
&
src
)
:
response_promise
(
s
td
::
move
(
self
),
std
::
move
(
src
.
sender
),
:
response_promise
(
s
elf
,
std
::
move
(
src
.
sender
),
std
::
move
(
src
.
stages
),
s
td
::
move
(
src
.
stages
),
s
rc
.
mid
)
{
src
.
mid
)
{
// nop
// nop
}
}
// -- properties ---------------------------------------------------------------
bool
response_promise
::
async
()
const
noexcept
{
return
id
().
is_async
();
}
bool
response_promise
::
pending
()
const
noexcept
{
return
state_
!=
nullptr
&&
state_
->
self
!=
nullptr
;
}
strong_actor_ptr
response_promise
::
source
()
const
noexcept
{
if
(
state_
)
return
state_
->
source
;
else
return
nullptr
;
}
response_promise
::
forwarding_stack
response_promise
::
stages
()
const
{
if
(
state_
)
return
state_
->
stages
;
else
return
{};
}
strong_actor_ptr
response_promise
::
next
()
const
noexcept
{
if
(
state_
)
return
state_
->
stages
.
empty
()
?
state_
->
source
:
state_
->
stages
[
0
];
else
return
nullptr
;
}
message_id
response_promise
::
id
()
const
noexcept
{
if
(
state_
)
return
state_
->
id
;
else
return
make_message_id
();
}
// -- delivery -----------------------------------------------------------------
void
response_promise
::
deliver
(
message
msg
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
msg
));
if
(
pending
())
{
state_
->
deliver_impl
(
std
::
move
(
msg
));
state_
.
reset
();
}
}
void
response_promise
::
deliver
(
error
x
)
{
void
response_promise
::
deliver
(
error
x
)
{
deliver_impl
(
make_message
(
std
::
move
(
x
)));
CAF_LOG_TRACE
(
CAF_ARG
(
x
));
if
(
pending
())
{
state_
->
deliver_impl
(
make_message
(
std
::
move
(
x
)));
state_
.
reset
();
}
}
}
void
response_promise
::
deliver
()
{
void
response_promise
::
deliver
()
{
deliver_impl
(
make_message
());
CAF_LOG_TRACE
(
CAF_ARG
(
""
));
if
(
pending
())
{
state_
->
deliver_impl
(
make_message
());
state_
.
reset
();
}
}
}
bool
response_promise
::
async
()
const
{
void
response_promise
::
respond_to
(
local_actor
*
self
,
mailbox_element
*
request
,
return
id_
.
is_async
();
message
&
response
)
{
if
(
request
&&
requires_response
(
*
request
))
{
state
tmp
;
tmp
.
self
=
self
;
tmp
.
source
.
swap
(
request
->
sender
);
tmp
.
stages
.
swap
(
request
->
stages
);
tmp
.
id
=
request
->
mid
;
tmp
.
deliver_impl
(
std
::
move
(
response
));
request
->
mid
.
mark_as_answered
();
}
}
}
local_actor
*
response_promise
::
self_dptr
()
const
{
void
response_promise
::
respond_to
(
local_actor
*
self
,
mailbox_element
*
request
,
// TODO: We require that self_ was constructed by using a local_actor*. The
error
&
response
)
{
// type erasure performed by strong_actor_ptr hides that fact. We
if
(
request
&&
requires_response
(
*
request
))
{
// probably should use a different pointer type such as
state
tmp
;
// intrusive_ptr<local_actor>. However, that would mean we would have to
tmp
.
self
=
self
;
// include local_actor.hpp in response_promise.hpp or provide overloads
tmp
.
source
.
swap
(
request
->
sender
);
// for intrusive_ptr_add_ref and intrusive_ptr_release.
tmp
.
stages
.
swap
(
request
->
stages
);
auto
self_baseptr
=
actor_cast
<
abstract_actor
*>
(
self_
);
tmp
.
id
=
request
->
mid
;
return
static_cast
<
local_actor
*>
(
self_baseptr
);
tmp
.
deliver_impl
(
make_message
(
std
::
move
(
response
)));
request
->
mid
.
mark_as_answered
();
}
}
// -- state --------------------------------------------------------------------
response_promise
::
state
::~
state
()
{
if
(
self
)
{
CAF_LOG_DEBUG
(
"broken promise!"
);
deliver_impl
(
make_message
(
make_error
(
sec
::
broken_promise
)));
}
}
}
execution_unit
*
response_promise
::
context
()
{
void
response_promise
::
state
::
cancel
()
{
return
self_
==
nullptr
?
nullptr
:
self_dptr
()
->
context
()
;
self
=
nullptr
;
}
}
void
response_promise
::
deliver_impl
(
message
msg
)
{
void
response_promise
::
state
::
deliver_impl
(
message
msg
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
msg
));
CAF_LOG_TRACE
(
CAF_ARG
(
msg
));
if
(
self_
==
nullptr
)
{
if
(
msg
.
empty
()
&&
id
.
is_async
())
{
CAF_LOG_DEBUG
(
"drop response: invalid promise"
);
return
;
}
if
(
msg
.
empty
()
&&
id_
.
is_async
())
{
CAF_LOG_DEBUG
(
"drop response: empty response to asynchronous input"
);
CAF_LOG_DEBUG
(
"drop response: empty response to asynchronous input"
);
self_
.
reset
();
}
else
{
return
;
if
(
stages
.
empty
())
{
detail
::
profiled_send
(
self
,
self
->
ctrl
(),
source
,
id
.
response_id
(),
forwarding_stack
{},
self
->
context
(),
std
::
move
(
msg
));
}
else
{
auto
next
=
std
::
move
(
stages
.
back
());
stages
.
pop_back
();
detail
::
profiled_send
(
self
,
std
::
move
(
source
),
next
,
id
,
std
::
move
(
stages
),
self
->
context
(),
std
::
move
(
msg
));
}
}
auto
dptr
=
self_dptr
();
if
(
!
stages_
.
empty
())
{
auto
next
=
std
::
move
(
stages_
.
back
());
stages_
.
pop_back
();
detail
::
profiled_send
(
dptr
,
std
::
move
(
source_
),
next
,
id_
,
std
::
move
(
stages_
),
dptr
->
context
(),
std
::
move
(
msg
));
self_
.
reset
();
return
;
}
}
if
(
source_
)
{
cancel
();
detail
::
profiled_send
(
dptr
,
self_
,
source_
,
id_
.
response_id
(),
no_stages
,
dptr
->
context
(),
std
::
move
(
msg
));
self_
.
reset
();
source_
.
reset
();
return
;
}
CAF_LOG_WARNING
(
"malformed response promise: self != nullptr && !pending()"
);
}
}
void
response_promise
::
delegate_impl
(
abstract_actor
*
receiver
,
message
msg
)
{
void
response_promise
::
state
::
delegate_impl
(
abstract_actor
*
receiver
,
message
msg
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
msg
));
CAF_LOG_TRACE
(
CAF_ARG
(
msg
));
if
(
receiver
==
nullptr
)
{
if
(
receiver
!=
nullptr
)
{
detail
::
profiled_send
(
self
,
std
::
move
(
source
),
receiver
,
id
,
std
::
move
(
stages
),
self
->
context
(),
std
::
move
(
msg
));
}
else
{
CAF_LOG_DEBUG
(
"drop response: invalid delegation target"
);
CAF_LOG_DEBUG
(
"drop response: invalid delegation target"
);
return
;
}
if
(
self_
==
nullptr
)
{
CAF_LOG_DEBUG
(
"drop response: invalid promise"
);
return
;
}
}
auto
dptr
=
self_dptr
();
cancel
();
detail
::
profiled_send
(
dptr
,
std
::
move
(
source_
),
receiver
,
id_
,
std
::
move
(
stages_
),
dptr
->
context
(),
std
::
move
(
msg
));
self_
.
reset
();
}
}
}
// namespace caf
}
// namespace caf
libcaf_core/src/sec_strings.cpp
View file @
6129cc74
...
@@ -148,6 +148,8 @@ std::string to_string(sec x) {
...
@@ -148,6 +148,8 @@ std::string to_string(sec x) {
return
"unsupported_operation"
;
return
"unsupported_operation"
;
case
sec
:
:
no_such_key
:
case
sec
:
:
no_such_key
:
return
"no_such_key"
;
return
"no_such_key"
;
case
sec
:
:
broken_promise
:
return
"broken_promise"
;
};
};
}
}
...
@@ -350,6 +352,9 @@ bool from_string(string_view in, sec& out) {
...
@@ -350,6 +352,9 @@ bool from_string(string_view in, sec& out) {
}
else
if
(
in
==
"no_such_key"
)
{
}
else
if
(
in
==
"no_such_key"
)
{
out
=
sec
::
no_such_key
;
out
=
sec
::
no_such_key
;
return
true
;
return
true
;
}
else
if
(
in
==
"broken_promise"
)
{
out
=
sec
::
broken_promise
;
return
true
;
}
else
{
}
else
{
return
false
;
return
false
;
}
}
...
@@ -427,6 +432,7 @@ bool from_integer(std::underlying_type_t<sec> in,
...
@@ -427,6 +432,7 @@ bool from_integer(std::underlying_type_t<sec> in,
case
sec
:
:
type_clash
:
case
sec
:
:
type_clash
:
case
sec
:
:
unsupported_operation
:
case
sec
:
:
unsupported_operation
:
case
sec
:
:
no_such_key
:
case
sec
:
:
no_such_key
:
case
sec
:
:
broken_promise
:
out
=
result
;
out
=
result
;
return
true
;
return
true
;
};
};
...
...
libcaf_core/src/stream_manager.cpp
View file @
6129cc74
...
@@ -240,7 +240,7 @@ stream_slot
...
@@ -240,7 +240,7 @@ stream_slot
stream_manager
::
add_unchecked_outbound_path_impl
(
strong_actor_ptr
next
,
stream_manager
::
add_unchecked_outbound_path_impl
(
strong_actor_ptr
next
,
message
handshake
)
{
message
handshake
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
next
)
<<
CAF_ARG
(
handshake
));
CAF_LOG_TRACE
(
CAF_ARG
(
next
)
<<
CAF_ARG
(
handshake
));
response_promise
rp
{
self_
->
ctrl
()
,
self_
->
ctrl
(),
{
next
},
make_message_id
()};
response_promise
rp
{
self_
,
self_
->
ctrl
(),
{
next
},
make_message_id
()};
return
add_unchecked_outbound_path_impl
(
rp
,
std
::
move
(
handshake
));
return
add_unchecked_outbound_path_impl
(
rp
,
std
::
move
(
handshake
));
}
}
...
...
libcaf_core/test/response_promise.cpp
View file @
6129cc74
...
@@ -140,6 +140,21 @@ SCENARIO("response promises allow delaying of response messages") {
...
@@ -140,6 +140,21 @@ SCENARIO("response promises allow delaying of response messages") {
}
}
}
}
SCENARIO
(
"response promises send errors when broken"
)
{
auto
adder_hdl
=
sys
.
spawn
(
adder
);
auto
hdl
=
sys
.
spawn
(
requester_v1
,
adder_hdl
);
GIVEN
(
"a dispatcher, and adder and a client"
)
{
WHEN
(
"the dispatcher terminates before calling deliver on its promise"
)
{
inject
((
int
,
int
),
from
(
self
).
to
(
hdl
).
with
(
3
,
4
));
inject
((
exit_msg
),
to
(
hdl
).
with
(
exit_msg
{
hdl
.
address
(),
exit_reason
::
kill
}));
THEN
(
"clients receive a broken_promise error"
)
{
expect
((
error
),
from
(
hdl
).
to
(
self
).
with
(
sec
::
broken_promise
));
}
}
}
}
SCENARIO
(
"response promises allow delegation"
)
{
SCENARIO
(
"response promises allow delegation"
)
{
GIVEN
(
"a dispatcher that calls delegate on its promise"
)
{
GIVEN
(
"a dispatcher that calls delegate on its promise"
)
{
auto
adder_hdl
=
sys
.
spawn
(
adder
);
auto
adder_hdl
=
sys
.
spawn
(
adder
);
...
...
libcaf_core/test/typed_response_promise.cpp
View file @
6129cc74
...
@@ -145,6 +145,21 @@ SCENARIO("response promises allow delaying of response messages") {
...
@@ -145,6 +145,21 @@ SCENARIO("response promises allow delaying of response messages") {
}
}
}
}
SCENARIO
(
"response promises send errors when broken"
)
{
auto
adder_hdl
=
sys
.
spawn
(
adder
);
auto
hdl
=
sys
.
spawn
(
requester_v1
,
adder_hdl
);
GIVEN
(
"a dispatcher, and adder and a client"
)
{
WHEN
(
"the dispatcher terminates before calling deliver on its promise"
)
{
inject
((
int
,
int
),
from
(
self
).
to
(
hdl
).
with
(
3
,
4
));
inject
((
exit_msg
),
to
(
hdl
).
with
(
exit_msg
{
hdl
.
address
(),
exit_reason
::
kill
}));
THEN
(
"clients receive a broken_promise error"
)
{
expect
((
error
),
from
(
hdl
).
to
(
self
).
with
(
sec
::
broken_promise
));
}
}
}
}
SCENARIO
(
"response promises allow delegation"
)
{
SCENARIO
(
"response promises allow delegation"
)
{
GIVEN
(
"a dispatcher that calls delegate on its promise"
)
{
GIVEN
(
"a dispatcher that calls delegate on its promise"
)
{
auto
adder_hdl
=
sys
.
spawn
(
adder
);
auto
adder_hdl
=
sys
.
spawn
(
adder
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment