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
95aeba25
Commit
95aeba25
authored
Dec 16, 2022
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'topic/neverlord/coverage'
parents
bdc663a9
a50d4e81
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
296 additions
and
41 deletions
+296
-41
CHANGELOG.md
CHANGELOG.md
+8
-0
libcaf_core/caf/async/spsc_buffer.hpp
libcaf_core/caf/async/spsc_buffer.hpp
+25
-8
libcaf_core/caf/flow/gen/from_callable.hpp
libcaf_core/caf/flow/gen/from_callable.hpp
+2
-1
libcaf_core/caf/flow/op/from_resource.hpp
libcaf_core/caf/flow/op/from_resource.hpp
+8
-6
libcaf_core/caf/flow/op/from_steps.hpp
libcaf_core/caf/flow/op/from_steps.hpp
+20
-15
libcaf_core/test/flow/generation.cpp
libcaf_core/test/flow/generation.cpp
+233
-11
No files found.
CHANGELOG.md
View file @
95aeba25
...
@@ -9,6 +9,14 @@ is based on [Keep a Changelog](https://keepachangelog.com).
...
@@ -9,6 +9,14 @@ is based on [Keep a Changelog](https://keepachangelog.com).
-
The new classes
`json_value`
,
`json_array`
and
`json_object`
allow working
-
The new classes
`json_value`
,
`json_array`
and
`json_object`
allow working
with JSON inputs directly. Actors can also pass around JSON values safely.
with JSON inputs directly. Actors can also pass around JSON values safely.
-
Fused stages now properly forward errors during the initial subscription to
their observer.
### Fixed
-
The SPSC buffer now makes sure that subscribers get informed of a producer has
already left before the subscriber appeared and vice versa. This fixes a race
on the buffer that could cause indefinite hanging of an application.
## [0.19.0-rc.1] - 2022-10-31
## [0.19.0-rc.1] - 2022-10-31
...
...
libcaf_core/caf/async/spsc_buffer.hpp
View file @
95aeba25
...
@@ -41,8 +41,19 @@ public:
...
@@ -41,8 +41,19 @@ public:
using
lock_type
=
std
::
unique_lock
<
std
::
mutex
>
;
using
lock_type
=
std
::
unique_lock
<
std
::
mutex
>
;
/// Packs various status flags for the buffer into a single struct.
struct
flags
{
/// Stores whether `close` has been called.
bool
closed
:
1
;
/// Stores whether the buffer had a consumer at some point.
bool
had_consumer
:
1
;
/// Stores whether the buffer had a producer at some point.
bool
had_producer
:
1
;
};
spsc_buffer
(
uint32_t
capacity
,
uint32_t
min_pull_size
)
spsc_buffer
(
uint32_t
capacity
,
uint32_t
min_pull_size
)
:
capacity_
(
capacity
),
min_pull_size_
(
min_pull_size
)
{
:
capacity_
(
capacity
),
min_pull_size_
(
min_pull_size
)
{
memset
(
&
flags_
,
0
,
sizeof
(
flags
));
// Allocate some extra space in the buffer in case the producer goes beyond
// Allocate some extra space in the buffer in case the producer goes beyond
// the announced capacity.
// the announced capacity.
buf_
.
reserve
(
capacity
+
(
capacity
/
2
));
buf_
.
reserve
(
capacity
+
(
capacity
/
2
));
...
@@ -58,7 +69,7 @@ public:
...
@@ -58,7 +69,7 @@ public:
size_t
push
(
span
<
const
T
>
items
)
{
size_t
push
(
span
<
const
T
>
items
)
{
lock_type
guard
{
mtx_
};
lock_type
guard
{
mtx_
};
CAF_ASSERT
(
producer_
!=
nullptr
);
CAF_ASSERT
(
producer_
!=
nullptr
);
CAF_ASSERT
(
!
closed_
);
CAF_ASSERT
(
!
flags_
.
closed
);
buf_
.
insert
(
buf_
.
end
(),
items
.
begin
(),
items
.
end
());
buf_
.
insert
(
buf_
.
end
(),
items
.
begin
(),
items
.
end
());
if
(
buf_
.
size
()
==
items
.
size
()
&&
consumer_
)
if
(
buf_
.
size
()
==
items
.
size
()
&&
consumer_
)
consumer_
->
on_producer_wakeup
();
consumer_
->
on_producer_wakeup
();
...
@@ -95,7 +106,7 @@ public:
...
@@ -95,7 +106,7 @@ public:
/// closed or aborted the flow.
/// closed or aborted the flow.
bool
has_consumer_event
()
const
noexcept
{
bool
has_consumer_event
()
const
noexcept
{
lock_type
guard
{
mtx_
};
lock_type
guard
{
mtx_
};
return
!
buf_
.
empty
()
||
closed_
;
return
!
buf_
.
empty
()
||
flags_
.
closed
;
}
}
/// Returns how many items are currently available. This may be greater than
/// Returns how many items are currently available. This may be greater than
...
@@ -116,7 +127,7 @@ public:
...
@@ -116,7 +127,7 @@ public:
void
close
()
{
void
close
()
{
lock_type
guard
{
mtx_
};
lock_type
guard
{
mtx_
};
if
(
producer_
)
{
if
(
producer_
)
{
closed_
=
true
;
flags_
.
closed
=
true
;
producer_
=
nullptr
;
producer_
=
nullptr
;
if
(
buf_
.
empty
()
&&
consumer_
)
if
(
buf_
.
empty
()
&&
consumer_
)
consumer_
->
on_producer_wakeup
();
consumer_
->
on_producer_wakeup
();
...
@@ -128,7 +139,7 @@ public:
...
@@ -128,7 +139,7 @@ public:
void
abort
(
error
reason
)
{
void
abort
(
error
reason
)
{
lock_type
guard
{
mtx_
};
lock_type
guard
{
mtx_
};
if
(
producer_
)
{
if
(
producer_
)
{
closed_
=
true
;
flags_
.
closed
=
true
;
err_
=
std
::
move
(
reason
);
err_
=
std
::
move
(
reason
);
producer_
=
nullptr
;
producer_
=
nullptr
;
if
(
buf_
.
empty
()
&&
consumer_
)
if
(
buf_
.
empty
()
&&
consumer_
)
...
@@ -153,8 +164,11 @@ public:
...
@@ -153,8 +164,11 @@ public:
if
(
consumer_
)
if
(
consumer_
)
CAF_RAISE_ERROR
(
"SPSC buffer already has a consumer"
);
CAF_RAISE_ERROR
(
"SPSC buffer already has a consumer"
);
consumer_
=
std
::
move
(
consumer
);
consumer_
=
std
::
move
(
consumer
);
flags_
.
had_consumer
=
true
;
if
(
producer_
)
if
(
producer_
)
ready
();
ready
();
else
if
(
flags_
.
had_producer
)
consumer_
->
on_producer_wakeup
();
}
}
/// Producer callback for the initial handshake between producer and consumer.
/// Producer callback for the initial handshake between producer and consumer.
...
@@ -164,8 +178,11 @@ public:
...
@@ -164,8 +178,11 @@ public:
if
(
producer_
)
if
(
producer_
)
CAF_RAISE_ERROR
(
"SPSC buffer already has a producer"
);
CAF_RAISE_ERROR
(
"SPSC buffer already has a producer"
);
producer_
=
std
::
move
(
producer
);
producer_
=
std
::
move
(
producer
);
flags_
.
had_producer
=
true
;
if
(
consumer_
)
if
(
consumer_
)
ready
();
ready
();
else
if
(
flags_
.
had_consumer
)
producer_
->
on_consumer_cancel
();
}
}
/// Returns the capacity as passed to the constructor of the buffer.
/// Returns the capacity as passed to the constructor of the buffer.
...
@@ -195,7 +212,7 @@ public:
...
@@ -195,7 +212,7 @@ public:
/// Blocks until there is at least one item available or the producer stopped.
/// Blocks until there is at least one item available or the producer stopped.
/// @pre the consumer calls `cv.notify_all()` in its `on_producer_wakeup`
/// @pre the consumer calls `cv.notify_all()` in its `on_producer_wakeup`
void
await_consumer_ready
(
lock_type
&
guard
,
std
::
condition_variable
&
cv
)
{
void
await_consumer_ready
(
lock_type
&
guard
,
std
::
condition_variable
&
cv
)
{
while
(
!
closed_
&&
buf_
.
empty
())
{
while
(
!
flags_
.
closed
&&
buf_
.
empty
())
{
cv
.
wait
(
guard
);
cv
.
wait
(
guard
);
}
}
}
}
...
@@ -206,7 +223,7 @@ public:
...
@@ -206,7 +223,7 @@ public:
template
<
class
TimePoint
>
template
<
class
TimePoint
>
bool
await_consumer_ready
(
lock_type
&
guard
,
std
::
condition_variable
&
cv
,
bool
await_consumer_ready
(
lock_type
&
guard
,
std
::
condition_variable
&
cv
,
TimePoint
timeout
)
{
TimePoint
timeout
)
{
while
(
!
closed_
&&
buf_
.
empty
())
while
(
!
flags_
.
closed
&&
buf_
.
empty
())
if
(
cv
.
wait_until
(
guard
,
timeout
)
==
std
::
cv_status
::
timeout
)
if
(
cv
.
wait_until
(
guard
,
timeout
)
==
std
::
cv_status
::
timeout
)
return
false
;
return
false
;
return
true
;
return
true
;
...
@@ -248,7 +265,7 @@ public:
...
@@ -248,7 +265,7 @@ public:
guard
.
lock
();
guard
.
lock
();
overflow
=
buf_
.
size
()
<=
capacity_
?
0u
:
buf_
.
size
()
-
capacity_
;
overflow
=
buf_
.
size
()
<=
capacity_
?
0u
:
buf_
.
size
()
-
capacity_
;
}
}
if
(
!
buf_
.
empty
()
||
!
closed_
)
{
if
(
!
buf_
.
empty
()
||
!
flags_
.
closed
)
{
return
{
true
,
consumed
};
return
{
true
,
consumed
};
}
else
{
}
else
{
consumer_
=
nullptr
;
consumer_
=
nullptr
;
...
@@ -298,7 +315,7 @@ private:
...
@@ -298,7 +315,7 @@ private:
uint32_t
demand_
=
0
;
uint32_t
demand_
=
0
;
/// Stores whether `close` has been called.
/// Stores whether `close` has been called.
bool
closed_
=
false
;
flags
flags_
;
/// Stores the abort reason.
/// Stores the abort reason.
error
err_
;
error
err_
;
...
...
libcaf_core/caf/flow/gen/from_callable.hpp
View file @
95aeba25
...
@@ -37,7 +37,8 @@ public:
...
@@ -37,7 +37,8 @@ public:
if
(
!
val
)
{
if
(
!
val
)
{
step
.
on_complete
(
steps
...);
step
.
on_complete
(
steps
...);
return
;
return
;
}
else
if
(
!
step
.
on_next
(
*
val
,
steps
...))
}
if
(
!
step
.
on_next
(
*
val
,
steps
...))
return
;
return
;
}
else
{
}
else
{
if
(
!
step
.
on_next
(
fn_
(),
steps
...))
if
(
!
step
.
on_next
(
fn_
(),
steps
...))
...
...
libcaf_core/caf/flow/op/from_resource.hpp
View file @
95aeba25
...
@@ -35,8 +35,10 @@ public:
...
@@ -35,8 +35,10 @@ public:
}
}
~
from_resource_sub
()
{
~
from_resource_sub
()
{
if
(
buf_
)
// The buffer points back to this object as consumer, so this cannot be
buf_
->
cancel
();
// destroyed unless we have called buf_->cancel(). All code paths that do
// call cancel() on the buffer also must set the variable to `nullptr`.
CAF_ASSERT
(
buf_
==
nullptr
);
ctx_
->
deref_execution_context
();
ctx_
->
deref_execution_context
();
}
}
...
@@ -51,7 +53,7 @@ public:
...
@@ -51,7 +53,7 @@ public:
if
(
!
disposed_
)
{
if
(
!
disposed_
)
{
disposed_
=
true
;
disposed_
=
true
;
if
(
!
running_
)
if
(
!
running_
)
do_
cancel
();
do_
dispose
();
}
}
}
}
...
@@ -114,7 +116,7 @@ private:
...
@@ -114,7 +116,7 @@ private:
}
}
}
}
void
do_
cancel
()
{
void
do_
dispose
()
{
if
(
buf_
)
{
if
(
buf_
)
{
buf_
->
cancel
();
buf_
->
cancel
();
buf_
=
nullptr
;
buf_
=
nullptr
;
...
@@ -129,7 +131,7 @@ private:
...
@@ -129,7 +131,7 @@ private:
CAF_LOG_TRACE
(
""
);
CAF_LOG_TRACE
(
""
);
auto
guard
=
detail
::
make_scope_guard
([
this
]
{
running_
=
false
;
});
auto
guard
=
detail
::
make_scope_guard
([
this
]
{
running_
=
false
;
});
if
(
disposed_
)
{
if
(
disposed_
)
{
do_
cancel
();
do_
dispose
();
return
;
return
;
}
}
CAF_ASSERT
(
out_
);
CAF_ASSERT
(
out_
);
...
@@ -142,7 +144,7 @@ private:
...
@@ -142,7 +144,7 @@ private:
disposed_
=
true
;
disposed_
=
true
;
return
;
return
;
}
else
if
(
disposed_
)
{
}
else
if
(
disposed_
)
{
do_
cancel
();
do_
dispose
();
return
;
return
;
}
else
if
(
pulled
==
0
)
{
}
else
if
(
pulled
==
0
)
{
return
;
return
;
...
...
libcaf_core/caf/flow/op/from_steps.hpp
View file @
95aeba25
...
@@ -47,9 +47,10 @@ public:
...
@@ -47,9 +47,10 @@ public:
}
}
void
on_error
(
const
error
&
what
)
{
void
on_error
(
const
error
&
what
)
{
CAF_ASSERT
(
sub
->
in_
.
valid
());
if
(
sub
->
in_
)
{
sub
->
in_
.
dispose
();
sub
->
in_
.
dispose
();
sub
->
in_
=
nullptr
;
sub
->
in_
=
nullptr
;
}
sub
->
err_
=
what
;
sub
->
err_
=
what
;
}
}
};
};
...
@@ -137,6 +138,19 @@ public:
...
@@ -137,6 +138,19 @@ public:
void
on_error
(
const
error
&
what
)
override
{
void
on_error
(
const
error
&
what
)
override
{
if
(
in_
)
{
if
(
in_
)
{
if
(
!
err_
)
{
auto
fn
=
[
this
,
&
what
](
auto
&
step
,
auto
&
...
steps
)
{
term_step
term
{
this
};
step
.
on_error
(
what
,
steps
...,
term
);
};
std
::
apply
(
fn
,
steps_
);
if
(
!
running_
)
{
running_
=
true
;
do_run
();
}
}
}
else
if
(
out_
)
{
// This may only happen if subscribing to the input fails.
auto
fn
=
[
this
,
&
what
](
auto
&
step
,
auto
&
...
steps
)
{
auto
fn
=
[
this
,
&
what
](
auto
&
step
,
auto
&
...
steps
)
{
term_step
term
{
this
};
term_step
term
{
this
};
step
.
on_error
(
what
,
steps
...,
term
);
step
.
on_error
(
what
,
steps
...,
term
);
...
@@ -273,19 +287,10 @@ public:
...
@@ -273,19 +287,10 @@ public:
auto
ptr
=
make_counted
<
sub_t
>
(
super
::
ctx_
,
out
,
steps_
);
auto
ptr
=
make_counted
<
sub_t
>
(
super
::
ctx_
,
out
,
steps_
);
input_
->
subscribe
(
observer
<
input_type
>
{
ptr
});
input_
->
subscribe
(
observer
<
input_type
>
{
ptr
});
if
(
ptr
->
subscribed
())
{
if
(
ptr
->
subscribed
())
{
auto
sub
=
subscription
{
std
::
move
(
ptr
)};
out
.
on_subscribe
(
subscription
{
ptr
});
out
.
on_subscribe
(
sub
);
return
ptr
->
as_disposable
();
return
std
::
move
(
sub
).
as_disposable
();
}
else
if
(
auto
&
fail_reason
=
ptr
->
fail_reason
())
{
out
.
on_error
(
fail_reason
);
return
disposable
{};
}
else
{
auto
err
=
make_error
(
sec
::
invalid_observable
,
"flow operator from_steps failed "
"to subscribe to its input"
);
out
.
on_error
(
err
);
return
disposable
{};
}
}
return
disposable
{};
}
}
private:
private:
...
...
libcaf_core/test/flow/generation.cpp
View file @
95aeba25
...
@@ -8,6 +8,7 @@
...
@@ -8,6 +8,7 @@
#include "core-test.hpp"
#include "core-test.hpp"
#include "caf/async/blocking_producer.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/coordinator.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/merge.hpp"
#include "caf/flow/observable_builder.hpp"
#include "caf/flow/observable_builder.hpp"
...
@@ -18,10 +19,19 @@ using namespace caf;
...
@@ -18,10 +19,19 @@ using namespace caf;
namespace
{
namespace
{
using
ivec
=
std
::
vector
<
int
>
;
struct
fixture
:
test_coordinator_fixture
<>
{
struct
fixture
:
test_coordinator_fixture
<>
{
flow
::
scoped_coordinator_ptr
ctx
=
flow
::
make_scoped_coordinator
();
flow
::
scoped_coordinator_ptr
ctx
=
flow
::
make_scoped_coordinator
();
};
};
auto
iota_vec
(
size_t
n
,
int
init
=
1
)
{
auto
result
=
ivec
{};
result
.
resize
(
n
);
std
::
iota
(
result
.
begin
(),
result
.
end
(),
init
);
return
result
;
}
}
// namespace
}
// namespace
BEGIN_FIXTURE_SCOPE
(
fixture
)
BEGIN_FIXTURE_SCOPE
(
fixture
)
...
@@ -30,7 +40,6 @@ SCENARIO("repeater sources repeat one value indefinitely") {
...
@@ -30,7 +40,6 @@ SCENARIO("repeater sources repeat one value indefinitely") {
GIVEN
(
"a repeater source"
)
{
GIVEN
(
"a repeater source"
)
{
WHEN
(
"subscribing to its output"
)
{
WHEN
(
"subscribing to its output"
)
{
THEN
(
"the observer receives the same value over and over again"
)
{
THEN
(
"the observer receives the same value over and over again"
)
{
using
ivec
=
std
::
vector
<
int
>
;
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
ctx
->
make_observable
().
repeat
(
42
).
subscribe
(
snk
->
as_observer
());
ctx
->
make_observable
().
repeat
(
42
).
subscribe
(
snk
->
as_observer
());
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
...
@@ -55,10 +64,11 @@ SCENARIO("container sources stream their input values") {
...
@@ -55,10 +64,11 @@ SCENARIO("container sources stream their input values") {
GIVEN
(
"a container source"
)
{
GIVEN
(
"a container source"
)
{
WHEN
(
"subscribing to its output"
)
{
WHEN
(
"subscribing to its output"
)
{
THEN
(
"the observer receives the values from the container in order"
)
{
THEN
(
"the observer receives the values from the container in order"
)
{
using
ivec
=
std
::
vector
<
int
>
;
auto
xs
=
ivec
{
1
,
2
,
3
,
4
,
5
,
6
,
7
};
auto
xs
=
ivec
{
1
,
2
,
3
,
4
,
5
,
6
,
7
};
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
ctx
->
make_observable
().
from_container
(
xs
).
subscribe
(
snk
->
as_observer
());
ctx
->
make_observable
()
.
from_container
(
std
::
move
(
xs
))
.
subscribe
(
snk
->
as_observer
());
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK
(
snk
->
buf
.
empty
());
CHECK
(
snk
->
buf
.
empty
());
if
(
CHECK
(
snk
->
sub
))
{
if
(
CHECK
(
snk
->
sub
))
{
...
@@ -72,6 +82,19 @@ SCENARIO("container sources stream their input values") {
...
@@ -72,6 +82,19 @@ SCENARIO("container sources stream their input values") {
}
}
}
}
}
}
WHEN
(
"combining it with with a step that limits the amount of items"
)
{
THEN
(
"the observer receives the defined subset of values"
)
{
auto
xs
=
iota_vec
(
713
);
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
res
=
ivec
{};
ctx
->
make_observable
()
.
from_container
(
std
::
move
(
xs
))
.
take
(
678
)
.
for_each
([
&
res
](
int
val
)
{
res
.
push_back
(
val
);
});
ctx
->
run
();
CHECK_EQ
(
res
,
iota_vec
(
678
));
}
}
}
}
}
}
...
@@ -79,7 +102,6 @@ SCENARIO("value sources produce exactly one input") {
...
@@ -79,7 +102,6 @@ SCENARIO("value sources produce exactly one input") {
GIVEN
(
"a value source"
)
{
GIVEN
(
"a value source"
)
{
WHEN
(
"subscribing to its output"
)
{
WHEN
(
"subscribing to its output"
)
{
THEN
(
"the observer receives one value"
)
{
THEN
(
"the observer receives one value"
)
{
using
ivec
=
std
::
vector
<
int
>
;
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
ctx
->
make_observable
().
just
(
42
).
subscribe
(
snk
->
as_observer
());
ctx
->
make_observable
().
just
(
42
).
subscribe
(
snk
->
as_observer
());
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
...
@@ -100,7 +122,6 @@ SCENARIO("callable sources stream values generated from a function object") {
...
@@ -100,7 +122,6 @@ SCENARIO("callable sources stream values generated from a function object") {
WHEN
(
"subscribing to its output"
)
{
WHEN
(
"subscribing to its output"
)
{
THEN
(
"the observer receives an indefinite amount of values"
)
{
THEN
(
"the observer receives an indefinite amount of values"
)
{
auto
f
=
[
n
=
1
]()
mutable
{
return
n
++
;
};
auto
f
=
[
n
=
1
]()
mutable
{
return
n
++
;
};
using
ivec
=
std
::
vector
<
int
>
;
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
ctx
->
make_observable
().
from_callable
(
f
).
subscribe
(
snk
->
as_observer
());
ctx
->
make_observable
().
from_callable
(
f
).
subscribe
(
snk
->
as_observer
());
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
...
@@ -118,6 +139,19 @@ SCENARIO("callable sources stream values generated from a function object") {
...
@@ -118,6 +139,19 @@ SCENARIO("callable sources stream values generated from a function object") {
}
}
}
}
}
}
WHEN
(
"combining it with with a step that accepts a finite amount"
)
{
THEN
(
"the observer receives a fixed amount of values"
)
{
auto
res
=
ivec
{};
auto
f
=
[
n
=
1
]()
mutable
{
return
n
++
;
};
ctx
->
make_observable
()
//
.
from_callable
(
f
)
.
take
(
713
)
.
for_each
([
&
res
](
int
val
)
{
res
.
push_back
(
val
);
});
ctx
->
run
();
CHECK_EQ
(
res
,
iota_vec
(
713
));
}
}
}
}
GIVEN
(
"a callable source returning optional values"
)
{
GIVEN
(
"a callable source returning optional values"
)
{
WHEN
(
"subscribing to its output"
)
{
WHEN
(
"subscribing to its output"
)
{
...
@@ -128,7 +162,6 @@ SCENARIO("callable sources stream values generated from a function object") {
...
@@ -128,7 +162,6 @@ SCENARIO("callable sources stream values generated from a function object") {
else
else
return
std
::
nullopt
;
return
std
::
nullopt
;
};
};
using
ivec
=
std
::
vector
<
int
>
;
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
ctx
->
make_observable
().
from_callable
(
f
).
subscribe
(
snk
->
as_observer
());
ctx
->
make_observable
().
from_callable
(
f
).
subscribe
(
snk
->
as_observer
());
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
...
@@ -144,12 +177,162 @@ SCENARIO("callable sources stream values generated from a function object") {
...
@@ -144,12 +177,162 @@ SCENARIO("callable sources stream values generated from a function object") {
}
}
}
}
}
}
WHEN
(
"combining it with with a step that accepts a finite amount"
)
{
THEN
(
"the observer receives a fixed amount of values"
)
{
auto
res
=
ivec
{};
auto
f
=
[
n
=
1
]()
mutable
->
std
::
optional
<
int
>
{
return
n
++
;
};
ctx
->
make_observable
()
//
.
from_callable
(
f
)
.
take
(
713
)
.
for_each
([
&
res
](
int
val
)
{
res
.
push_back
(
val
);
});
ctx
->
run
();
CHECK_EQ
(
res
,
iota_vec
(
713
));
}
}
}
}
SCENARIO
(
"asynchronous buffers can generate flow items"
)
{
GIVEN
(
"a background thread writing into an async buffer"
)
{
auto
producer_impl
=
[](
async
::
producer_resource
<
int
>
res
,
bool
*
cancelled
)
{
auto
producer
=
async
::
make_blocking_producer
(
std
::
move
(
res
));
if
(
!
producer
)
CAF_FAIL
(
"make_blocking_producer failed"
);
for
(
int
i
=
1
;
i
<=
713
;
++
i
)
{
if
(
!
producer
->
push
(
i
))
{
*
cancelled
=
true
;
return
;
}
}
*
cancelled
=
false
;
};
WHEN
(
"reading all values from the buffer"
)
{
THEN
(
"the observer receives all produced values"
)
{
auto
cancelled
=
false
;
auto
[
pull
,
push
]
=
async
::
make_spsc_buffer_resource
<
int
>
();
auto
bg_thread
=
std
::
thread
{
producer_impl
,
push
,
&
cancelled
};
auto
res
=
ivec
{};
ctx
->
make_observable
()
//
.
from_resource
(
pull
)
.
take
(
777
)
.
for_each
([
&
res
](
int
val
)
{
res
.
push_back
(
val
);
});
ctx
->
run
();
CHECK_EQ
(
res
,
iota_vec
(
713
));
bg_thread
.
join
();
CHECK
(
!
cancelled
);
}
}
WHEN
(
"reading only a subset of values from the buffer"
)
{
THEN
(
"producer receives a cancel event after the selected items"
)
{
auto
cancelled
=
false
;
auto
[
pull
,
push
]
=
async
::
make_spsc_buffer_resource
<
int
>
();
auto
bg_thread
=
std
::
thread
{
producer_impl
,
push
,
&
cancelled
};
auto
res
=
ivec
{};
ctx
->
make_observable
()
//
.
from_resource
(
pull
)
.
take
(
20
)
.
for_each
([
&
res
](
int
val
)
{
res
.
push_back
(
val
);
});
ctx
->
run
();
CHECK_EQ
(
res
,
iota_vec
(
20
));
bg_thread
.
join
();
CHECK
(
cancelled
);
}
}
WHEN
(
"canceling the subscription to the buffer"
)
{
THEN
(
"the producer receives a cancel event"
)
{
auto
cancelled
=
false
;
auto
[
pull
,
push
]
=
async
::
make_spsc_buffer_resource
<
int
>
();
auto
res
=
ivec
{};
auto
sub
=
ctx
->
make_observable
()
//
.
from_resource
(
pull
)
.
take
(
777
)
.
for_each
([
&
res
](
int
val
)
{
res
.
push_back
(
val
);
});
// Run initial actions to handle events from the initial request()
// calls. Without this step, from_resource is in `running_` state and we
// won't hit the code paths for disposing a "cold" object. This is also
// why we spin up the thread later: making sure we're hitting the code
// paths we want to test here.
ctx
->
run_some
();
auto
bg_thread
=
std
::
thread
{
producer_impl
,
push
,
&
cancelled
};
sub
.
dispose
();
ctx
->
run
();
CHECK
(
res
.
empty
());
bg_thread
.
join
();
CHECK
(
cancelled
);
}
}
}
GIVEN
(
"a null-resource"
)
{
WHEN
(
"trying to read from it"
)
{
THEN
(
"the observer receives an error"
)
{
auto
res
=
ivec
{};
auto
err
=
error
{};
auto
pull
=
async
::
consumer_resource
<
int
>
{};
ctx
->
make_observable
()
//
.
from_resource
(
pull
)
.
take
(
713
)
.
do_on_error
([
&
err
](
const
error
&
what
)
{
err
=
what
;
})
.
for_each
([
&
res
](
int
val
)
{
res
.
push_back
(
val
);
});
ctx
->
run
();
CHECK
(
res
.
empty
());
CHECK
(
err
);
}
}
}
GIVEN
(
"a resource that has already been accessed"
)
{
WHEN
(
"trying to read from it"
)
{
THEN
(
"the observer receives an error"
)
{
auto
[
pull
,
push
]
=
async
::
make_spsc_buffer_resource
<
int
>
();
auto
pull_cpy
=
pull
;
auto
buf
=
pull_cpy
.
try_open
();
CHECK
(
buf
!=
nullptr
);
auto
res
=
ivec
{};
auto
err
=
error
{};
ctx
->
make_observable
()
//
.
from_resource
(
pull
)
.
take
(
713
)
.
do_on_error
([
&
err
](
const
error
&
what
)
{
err
=
what
;
})
.
for_each
([
&
res
](
int
val
)
{
res
.
push_back
(
val
);
});
ctx
->
run
();
CHECK
(
res
.
empty
());
CHECK
(
err
);
}
}
}
GIVEN
(
"a from_resource_sub object"
)
{
WHEN
(
"manipulating its ref count as consumer or disposable"
)
{
THEN
(
"the different pointer types manipulate the same ref count"
)
{
using
buf_t
=
async
::
spsc_buffer
<
int
>
;
using
impl_t
=
flow
::
op
::
from_resource_sub
<
buf_t
>
;
auto
ptr
=
make_counted
<
impl_t
>
(
ctx
.
get
(),
nullptr
,
flow
::
observer
<
int
>::
ignore
());
CHECK_EQ
(
ptr
->
get_reference_count
(),
1u
);
{
auto
sub
=
flow
::
subscription
{
ptr
.
get
()};
CHECK_EQ
(
ptr
->
get_reference_count
(),
2u
);
}
CHECK_EQ
(
ptr
->
get_reference_count
(),
1u
);
{
auto
cptr
=
async
::
consumer_ptr
{
ptr
.
get
()};
CHECK_EQ
(
ptr
->
get_reference_count
(),
2u
);
}
CHECK_EQ
(
ptr
->
get_reference_count
(),
1u
);
}
}
}
}
}
}
namespace
{
namespace
{
class
custom_generator
{
// Generates 7 integers and then calls on_complete.
class
i7_generator
{
public:
public:
using
output_type
=
int
;
using
output_type
=
int
;
...
@@ -169,15 +352,36 @@ private:
...
@@ -169,15 +352,36 @@ private:
int
value_
=
1
;
int
value_
=
1
;
};
};
// Generates 3 integers and then calls on_error.
class
broken_generator
{
public:
using
output_type
=
int
;
template
<
class
Step
,
class
...
Steps
>
void
pull
(
size_t
n
,
Step
&
step
,
Steps
&
...
steps
)
{
for
(
size_t
i
=
0
;
i
<
n
;
++
i
)
{
if
(
value_
>
3
)
{
auto
err
=
make_error
(
sec
::
runtime_error
,
"something went wrong"
);
step
.
on_error
(
err
,
steps
...);
return
;
}
else
if
(
!
step
.
on_next
(
value_
++
,
steps
...))
{
return
;
}
}
}
private:
int
value_
=
1
;
};
}
// namespace
}
// namespace
SCENARIO
(
"
lifting converts a generator into an observable
"
)
{
SCENARIO
(
"
users can provide custom generators
"
)
{
GIVEN
(
"a
lifted
implementation of the generator concept"
)
{
GIVEN
(
"a
n
implementation of the generator concept"
)
{
WHEN
(
"subscribing to its output"
)
{
WHEN
(
"subscribing to its output"
)
{
THEN
(
"the observer receives the generated values"
)
{
THEN
(
"the observer receives the generated values"
)
{
using
ivec
=
std
::
vector
<
int
>
;
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
f
=
custom
_generator
{};
auto
f
=
i7
_generator
{};
ctx
->
make_observable
().
from_generator
(
f
).
subscribe
(
snk
->
as_observer
());
ctx
->
make_observable
().
from_generator
(
f
).
subscribe
(
snk
->
as_observer
());
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK
(
snk
->
buf
.
empty
());
CHECK
(
snk
->
buf
.
empty
());
...
@@ -192,6 +396,24 @@ SCENARIO("lifting converts a generator into an observable") {
...
@@ -192,6 +396,24 @@ SCENARIO("lifting converts a generator into an observable") {
}
}
}
}
}
}
GIVEN
(
"an implementation of the generator concept that calls on_error"
)
{
WHEN
(
"subscribing to its output"
)
{
THEN
(
"the observer receives the generated values followed by an error"
)
{
auto
snk
=
flow
::
make_passive_observer
<
int
>
();
auto
f
=
broken_generator
{};
ctx
->
make_observable
().
from_generator
(
f
).
subscribe
(
snk
->
as_observer
());
CHECK_EQ
(
snk
->
state
,
flow
::
observer_state
::
subscribed
);
CHECK
(
snk
->
buf
.
empty
());
CHECK
(
snk
->
subscribed
());
snk
->
request
(
27
);
ctx
->
run
();
CHECK_EQ
(
snk
->
buf
,
ivec
({
1
,
2
,
3
}));
if
(
CHECK
(
snk
->
aborted
()))
{
CHECK_EQ
(
snk
->
err
,
caf
::
sec
::
runtime_error
);
}
}
}
}
}
}
END_FIXTURE_SCOPE
()
END_FIXTURE_SCOPE
()
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