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
d9639b60
Commit
d9639b60
authored
Mar 11, 2023
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1382
parents
e6f6dab9
e7a0c077
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
445 additions
and
36 deletions
+445
-36
CHANGELOG.md
CHANGELOG.md
+3
-0
libcaf_core/caf/async/spsc_buffer.hpp
libcaf_core/caf/async/spsc_buffer.hpp
+47
-23
libcaf_core/caf/flow/observable.hpp
libcaf_core/caf/flow/observable.hpp
+4
-4
libcaf_core/caf/flow/observer.hpp
libcaf_core/caf/flow/observer.hpp
+10
-3
libcaf_core/caf/flow/op/from_resource.hpp
libcaf_core/caf/flow/op/from_resource.hpp
+0
-4
libcaf_core/test/async/spsc_buffer.cpp
libcaf_core/test/async/spsc_buffer.cpp
+381
-2
No files found.
CHANGELOG.md
View file @
d9639b60
...
...
@@ -47,6 +47,9 @@ is based on [Keep a Changelog](https://keepachangelog.com).
could end up in a long-running read loop. To avoid potentially starving other
actors or activities, scheduled actors now limit the amount of actions that
may run in one iteration (#1364).
-
Destroying a consumer or producer resource before opening it lead to a stall
of the consumer / producer. The buffer now keeps track of whether
`close`
or
`abort`
were called prior to consumers or producers attaching.
### Deprecated
...
...
libcaf_core/caf/async/spsc_buffer.hpp
View file @
d9639b60
...
...
@@ -45,10 +45,8 @@ public:
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
;
/// Stores whether `cancel` has been called.
bool
canceled
:
1
;
};
spsc_buffer
(
uint32_t
capacity
,
uint32_t
min_pull_size
)
...
...
@@ -125,20 +123,14 @@ public:
/// Closes the buffer by request of the producer.
void
close
()
{
lock_type
guard
{
mtx_
};
if
(
producer_
)
{
flags_
.
closed
=
true
;
producer_
=
nullptr
;
if
(
buf_
.
empty
()
&&
consumer_
)
consumer_
->
on_producer_wakeup
();
}
abort
(
error
{});
}
/// Closes the buffer by request of the producer and signals an error to the
/// consumer.
void
abort
(
error
reason
)
{
lock_type
guard
{
mtx_
};
if
(
producer_
)
{
if
(
!
flags_
.
closed
)
{
flags_
.
closed
=
true
;
err_
=
std
::
move
(
reason
);
producer_
=
nullptr
;
...
...
@@ -150,7 +142,8 @@ public:
/// Closes the buffer by request of the consumer.
void
cancel
()
{
lock_type
guard
{
mtx_
};
if
(
consumer_
)
{
if
(
!
flags_
.
canceled
)
{
flags_
.
canceled
=
true
;
consumer_
=
nullptr
;
if
(
producer_
)
producer_
->
on_consumer_cancel
();
...
...
@@ -162,12 +155,11 @@ public:
CAF_ASSERT
(
consumer
!=
nullptr
);
lock_type
guard
{
mtx_
};
if
(
consumer_
)
CAF_RAISE_ERROR
(
"SPSC buffer already has a consumer"
);
CAF_RAISE_ERROR
(
std
::
logic_error
,
"SPSC buffer already has a consumer"
);
consumer_
=
std
::
move
(
consumer
);
flags_
.
had_consumer
=
true
;
if
(
producer_
)
ready
();
else
if
(
flags_
.
had_producer
)
else
if
(
flags_
.
closed
)
consumer_
->
on_producer_wakeup
();
}
...
...
@@ -176,12 +168,11 @@ public:
CAF_ASSERT
(
producer
!=
nullptr
);
lock_type
guard
{
mtx_
};
if
(
producer_
)
CAF_RAISE_ERROR
(
"SPSC buffer already has a producer"
);
CAF_RAISE_ERROR
(
std
::
logic_error
,
"SPSC buffer already has a producer"
);
producer_
=
std
::
move
(
producer
);
flags_
.
had_producer
=
true
;
if
(
consumer_
)
ready
();
else
if
(
flags_
.
had_consumer
)
else
if
(
flags_
.
canceled
)
producer_
->
on_consumer_cancel
();
}
...
...
@@ -347,7 +338,7 @@ struct resource_ctrl : ref_counted {
if
(
buf
)
{
if
constexpr
(
IsProducer
)
{
auto
err
=
make_error
(
sec
::
disposed
,
"
producer_resource destroyed
without opening it"
);
"
destroyed producer_resource
without opening it"
);
buf
->
abort
(
err
);
}
else
{
buf
->
cancel
();
...
...
@@ -356,13 +347,12 @@ struct resource_ctrl : ref_counted {
}
buffer_ptr
try_open
()
{
auto
res
=
buffer_ptr
{};
std
::
unique_lock
guard
{
mtx
};
if
(
buf
)
{
auto
res
=
buffer_ptr
{};
res
.
swap
(
buf
);
return
res
;
}
return
nullptr
;
return
res
;
}
mutable
std
::
mutex
mtx
;
...
...
@@ -431,6 +421,20 @@ public:
return
ctrl_
!=
nullptr
;
}
bool
operator
!
()
const
noexcept
{
return
ctrl_
==
nullptr
;
}
friend
bool
operator
==
(
const
consumer_resource
&
lhs
,
const
consumer_resource
&
rhs
)
{
return
lhs
.
ctrl_
==
rhs
.
ctrl_
;
}
friend
bool
operator
!=
(
const
consumer_resource
&
lhs
,
const
consumer_resource
&
rhs
)
{
return
lhs
.
ctrl_
!=
rhs
.
ctrl_
;
}
private:
intrusive_ptr
<
resource_ctrl
<
T
,
false
>>
ctrl_
;
};
...
...
@@ -485,10 +489,30 @@ public:
buf
->
close
();
}
/// Calls `try_open` and on success immediately calls `abort` on the buffer.
void
abort
(
error
reason
)
{
if
(
auto
buf
=
try_open
())
buf
->
abort
(
std
::
move
(
reason
));
}
explicit
operator
bool
()
const
noexcept
{
return
ctrl_
!=
nullptr
;
}
bool
operator
!
()
const
noexcept
{
return
ctrl_
==
nullptr
;
}
friend
bool
operator
==
(
const
producer_resource
&
lhs
,
const
producer_resource
&
rhs
)
{
return
lhs
.
ctrl_
==
rhs
.
ctrl_
;
}
friend
bool
operator
!=
(
const
producer_resource
&
lhs
,
const
producer_resource
&
rhs
)
{
return
lhs
.
ctrl_
!=
rhs
.
ctrl_
;
}
private:
intrusive_ptr
<
resource_ctrl
<
T
,
true
>>
ctrl_
;
};
...
...
libcaf_core/caf/flow/observable.hpp
View file @
d9639b60
...
...
@@ -477,8 +477,8 @@ disposable observable<T>::subscribe(async::producer_resource<T> resource) {
using
adapter_type
=
buffer_writer_impl
<
buffer_type
>
;
if
(
auto
buf
=
resource
.
try_open
())
{
CAF_LOG_DEBUG
(
"subscribe producer resource to flow"
);
auto
adapter
=
make_counted
<
adapter_type
>
(
pimpl_
->
ctx
()
,
buf
);
buf
->
set_producer
(
adapter
);
auto
adapter
=
make_counted
<
adapter_type
>
(
pimpl_
->
ctx
());
adapter
->
init
(
buf
);
auto
obs
=
adapter
->
as_observer
();
auto
sub
=
subscribe
(
std
::
move
(
obs
));
pimpl_
->
ctx
()
->
watch
(
sub
);
...
...
@@ -754,8 +754,8 @@ async::consumer_resource<T>
observable
<
T
>::
to_resource
(
size_t
buffer_size
,
size_t
min_request_size
)
{
using
buffer_type
=
async
::
spsc_buffer
<
T
>
;
auto
buf
=
make_counted
<
buffer_type
>
(
buffer_size
,
min_request_size
);
auto
up
=
make_counted
<
buffer_writer_impl
<
buffer_type
>>
(
pimpl_
->
ctx
()
,
buf
);
buf
->
set_producer
(
up
);
auto
up
=
make_counted
<
buffer_writer_impl
<
buffer_type
>>
(
pimpl_
->
ctx
());
up
->
init
(
buf
);
subscribe
(
up
->
as_observer
());
return
async
::
consumer_resource
<
T
>
{
std
::
move
(
buf
)};
}
...
...
libcaf_core/caf/flow/observer.hpp
View file @
d9639b60
...
...
@@ -287,10 +287,8 @@ public:
// -- constructors, destructors, and assignment operators --------------------
buffer_writer_impl
(
coordinator
*
ctx
,
buffer_ptr
buf
)
:
ctx_
(
ctx
),
buf_
(
std
::
move
(
buf
))
{
buffer_writer_impl
(
coordinator
*
ctx
)
:
ctx_
(
ctx
)
{
CAF_ASSERT
(
ctx_
!=
nullptr
);
CAF_ASSERT
(
buf_
!=
nullptr
);
}
~
buffer_writer_impl
()
{
...
...
@@ -298,6 +296,15 @@ public:
buf_
->
close
();
}
void
init
(
buffer_ptr
buf
)
{
// This step is a bit subtle. Basically, buf->set_producer might throw, in
// which case we must not set buf_ to avoid closing a buffer that we don't
// actually own.
CAF_ASSERT
(
buf
!=
nullptr
);
buf
->
set_producer
(
this
);
buf_
=
std
::
move
(
buf
);
}
// -- intrusive_ptr interface ------------------------------------------------
friend
void
intrusive_ptr_add_ref
(
const
buffer_writer_impl
*
ptr
)
noexcept
{
...
...
libcaf_core/caf/flow/op/from_resource.hpp
View file @
d9639b60
...
...
@@ -35,10 +35,6 @@ public:
}
~
from_resource_sub
()
{
// The buffer points back to this object as consumer, so this cannot be
// 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
();
}
...
...
libcaf_core/test/async/spsc_buffer.cpp
View file @
d9639b60
...
...
@@ -108,6 +108,44 @@ struct dummy_observer {
BEGIN_FIXTURE_SCOPE
(
test_coordinator_fixture
<>
)
TEST_CASE
(
"resources may be copied"
)
{
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
// Test copy constructor.
async
::
consumer_resource
<
int
>
rd2
{
rd
};
CHECK_EQ
(
rd
,
rd2
);
async
::
producer_resource
<
int
>
wr2
{
wr
};
CHECK_EQ
(
wr
,
wr2
);
// Test copy-assignment.
async
::
consumer_resource
<
int
>
rd3
;
CHECK_NE
(
rd2
,
rd3
);
rd3
=
rd2
;
CHECK_EQ
(
rd2
,
rd3
);
async
::
producer_resource
<
int
>
wr3
;
CHECK_NE
(
wr2
,
wr3
);
wr3
=
wr2
;
CHECK_EQ
(
wr2
,
wr3
);
}
TEST_CASE
(
"resources may be moved"
)
{
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
CHECK
(
rd
);
CHECK
(
wr
);
// Test move constructor.
async
::
consumer_resource
<
int
>
rd2
{
std
::
move
(
rd
)};
CHECK
(
!
rd
);
CHECK
(
rd2
);
async
::
producer_resource
<
int
>
wr2
{
std
::
move
(
wr
)};
CHECK
(
!
wr
);
CHECK
(
wr2
);
// Test move-assignment.
async
::
consumer_resource
<
int
>
rd3
{
std
::
move
(
rd2
)};
CHECK
(
!
rd2
);
CHECK
(
rd3
);
async
::
producer_resource
<
int
>
wr3
{
std
::
move
(
wr2
)};
CHECK
(
!
wr2
);
CHECK
(
wr3
);
}
SCENARIO
(
"SPSC buffers may go past their capacity"
)
{
GIVEN
(
"an SPSC buffer with consumer and produer"
)
{
auto
prod
=
make_counted
<
dummy_producer
>
();
...
...
@@ -126,7 +164,7 @@ SCENARIO("SPSC buffers may go past their capacity") {
buf
->
push
(
2
);
CHECK_EQ
(
cons
->
producer_wakeups
,
1u
);
THEN
(
"excess items are stored but do not trigger demand when consumed"
)
{
std
::
vector
<
int
>
tmp
{
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
};
auto
tmp
=
std
::
vector
<
int
>
{
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
};
buf
->
push
(
make_span
(
tmp
));
prod
->
demand
=
0
;
CHECK_EQ
(
cons
->
producer_wakeups
,
1u
);
...
...
@@ -153,8 +191,42 @@ SCENARIO("SPSC buffers may go past their capacity") {
}
}
SCENARIO
(
"the prioritize_errors policy skips processing of pending items"
)
{
GIVEN
(
"an SPSC buffer with consumer and produer"
)
{
WHEN
(
"pushing into the buffer and then aborting"
)
{
THEN
(
"pulling items with prioritize_errors skips remaining items"
)
{
auto
prod
=
make_counted
<
dummy_producer
>
();
auto
cons
=
make_counted
<
dummy_consumer
>
();
auto
buf
=
make_counted
<
async
::
spsc_buffer
<
int
>>
(
10
,
2
);
auto
tmp
=
std
::
vector
<
int
>
{
1
,
2
,
3
,
4
,
5
};
buf
->
set_producer
(
prod
);
buf
->
push
(
make_span
(
tmp
));
buf
->
set_consumer
(
cons
);
CHECK_EQ
(
cons
->
producer_wakeups
,
1u
);
MESSAGE
(
"consume one element"
);
{
dummy_observer
obs
;
auto
[
ok
,
consumed
]
=
buf
->
pull
(
async
::
prioritize_errors
,
1
,
obs
);
CHECK_EQ
(
ok
,
true
);
CHECK_EQ
(
consumed
,
1u
);
CHECK_EQ
(
obs
.
consumed
,
1u
);
}
MESSAGE
(
"set an error and try consuming remaining elements"
);
{
buf
->
abort
(
sec
::
runtime_error
);
dummy_observer
obs
;
auto
[
ok
,
consumed
]
=
buf
->
pull
(
async
::
prioritize_errors
,
1
,
obs
);
CHECK_EQ
(
ok
,
false
);
CHECK_EQ
(
consumed
,
0u
);
CHECK_EQ
(
obs
.
err
,
sec
::
runtime_error
);
}
}
}
}
}
SCENARIO
(
"SPSC buffers moves data between actors"
)
{
GIVEN
(
"a SPSC buffer resource"
)
{
GIVEN
(
"a
n
SPSC buffer resource"
)
{
WHEN
(
"opening the resource from two actors"
)
{
THEN
(
"data travels through the SPSC buffer"
)
{
using
actor_t
=
event_based_actor
;
...
...
@@ -180,4 +252,311 @@ SCENARIO("SPSC buffers moves data between actors") {
}
}
SCENARIO
(
"SPSC buffers appear empty when only one actor is connected"
)
{
GIVEN
(
"an SPSC buffer resource"
)
{
WHEN
(
"destroying the write end before adding a subscriber"
)
{
THEN
(
"no data arrives through the SPSC buffer"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
auto
finalized
=
false
;
{
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
sys
.
spawn
([
rd
{
rd
},
&
outputs
,
&
finalized
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd
)
.
do_finally
([
&
finalized
]
{
finalized
=
true
;
})
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
}
// At scope exit, `wr` gets destroyed, closing the buffer.
run
();
CHECK
(
finalized
);
CHECK
(
outputs
.
empty
());
}
}
WHEN
(
"destroying the write end after adding a subscriber"
)
{
THEN
(
"no data arrives through the SPSC buffer"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
auto
finalized
=
false
;
{
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
sys
.
spawn
([
rd
{
rd
},
&
outputs
,
&
finalized
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd
)
.
do_finally
([
&
finalized
]
{
finalized
=
true
;
})
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
// Only difference to before: have the actor create the observable
// from `rd` handle before destroying `wr`.
run
();
}
// At scope exit, `wr` gets destroyed, closing the buffer.
run
();
CHECK
(
finalized
);
CHECK
(
outputs
.
empty
());
}
}
WHEN
(
"aborting the write end"
)
{
THEN
(
"the observer receives on_error"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
auto
on_error_called
=
false
;
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
sys
.
spawn
([
rd
{
rd
},
&
outputs
,
&
on_error_called
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd
)
.
do_on_error
([
&
on_error_called
](
const
error
&
err
)
{
on_error_called
=
true
;
CHECK_EQ
(
err
,
sec
::
runtime_error
);
})
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
wr
.
abort
(
sec
::
runtime_error
);
wr
.
abort
(
sec
::
runtime_error
);
// Calling twice must have no side effect.
run
();
CHECK
(
on_error_called
);
CHECK
(
outputs
.
empty
());
}
}
WHEN
(
"closing the write end"
)
{
THEN
(
"the observer receives on_complete"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
auto
on_complete_called
=
false
;
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
sys
.
spawn
([
rd
{
rd
},
&
outputs
,
&
on_complete_called
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd
)
.
do_on_complete
([
&
on_complete_called
]
{
//
on_complete_called
=
true
;
})
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
wr
.
close
();
wr
.
close
();
// Calling twice must have no side effect.
run
();
CHECK
(
on_complete_called
);
CHECK
(
outputs
.
empty
());
}
}
}
}
SCENARIO
(
"SPSC buffers drop data when discarding the read end"
)
{
GIVEN
(
"an SPSC buffer resource"
)
{
WHEN
(
"destroying the read end before adding a publisher"
)
{
THEN
(
"the flow of the writing actor gets canceled"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
{
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
sys
.
spawn
([
wr
{
wr
}](
actor_t
*
src
)
{
src
->
make_observable
()
//
.
iota
(
1
)
.
subscribe
(
wr
);
});
}
// At scope exit, `rd` gets destroyed, closing the buffer.
run
();
CHECK
(
outputs
.
empty
());
}
}
WHEN
(
"destroying the read end before adding a publisher"
)
{
THEN
(
"the flow of the writing actor gets canceled"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
{
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
sys
.
spawn
([
wr
{
wr
}](
actor_t
*
src
)
{
src
->
make_observable
()
//
.
iota
(
1
)
.
subscribe
(
wr
);
});
// Only difference to before: have the actor add an observer that
// writes to `wr` before destroying `rd`.
run
();
}
// At scope exit, `rd` gets destroyed, closing the buffer.
run
();
CHECK
(
outputs
.
empty
());
}
}
WHEN
(
"canceling the read end before adding a publisher"
)
{
THEN
(
"the flow of the writing actor gets canceled"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
sys
.
spawn
([
wr
{
wr
}](
actor_t
*
src
)
{
src
->
make_observable
()
//
.
iota
(
1
)
.
subscribe
(
wr
);
});
rd
.
cancel
();
rd
.
cancel
();
// Calling twice must have no side effect.
run
();
CHECK
(
outputs
.
empty
());
}
}
}
}
SCENARIO
(
"resources are invalid after calling try_open"
)
{
GIVEN
(
"a producer resource"
)
{
WHEN
(
"opening it twice"
)
{
THEN
(
"the second try_open fails"
)
{
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
CHECK
(
rd
);
CHECK_NE
(
rd
.
try_open
(),
nullptr
);
CHECK
(
!
rd
);
CHECK_EQ
(
rd
.
try_open
(),
nullptr
);
}
}
}
}
SCENARIO
(
"producer resources may be subscribed to flows only once"
)
{
GIVEN
(
"a producer resource"
)
{
WHEN
(
"subscribing it to a flow twice"
)
{
THEN
(
"the second attempt results in a canceled subscription"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
auto
prod1
=
sys
.
spawn
([
wr
{
wr
}](
actor_t
*
src
)
{
src
->
make_observable
()
//
.
iota
(
1
)
.
subscribe
(
wr
);
});
self
->
monitor
(
prod1
);
run
();
auto
prod2
=
sys
.
spawn
([
wr
{
wr
}](
actor_t
*
src
)
{
src
->
make_observable
()
//
.
iota
(
1
)
.
subscribe
(
wr
);
});
self
->
monitor
(
prod2
);
run
();
expect
((
down_msg
),
to
(
self
).
with
(
down_msg
{
prod2
.
address
(),
error
{}}));
CHECK
(
self
->
mailbox
().
empty
());
}
}
}
}
SCENARIO
(
"consumer resources may be converted to flows only once"
)
{
GIVEN
(
"a consumer resource"
)
{
WHEN
(
"making an observable from the resource"
)
{
THEN
(
"the second attempt results in an empty observable"
)
{
using
actor_t
=
event_based_actor
;
auto
outputs
=
std
::
vector
<
int
>
{};
auto
[
rd
,
wr
]
=
async
::
make_spsc_buffer_resource
<
int
>
(
6
,
2
);
auto
snk1
=
sys
.
spawn
([
rd
{
rd
},
&
outputs
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd
)
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
self
->
monitor
(
snk1
);
run
();
auto
snk2
=
sys
.
spawn
([
rd
{
rd
},
&
outputs
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd
)
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
self
->
monitor
(
snk2
);
run
();
expect
((
down_msg
),
to
(
self
).
with
(
down_msg
{
snk2
.
address
(),
error
{}}));
CHECK
(
self
->
mailbox
().
empty
());
CHECK
(
outputs
.
empty
());
}
}
}
}
#ifdef CAF_ENABLE_EXCEPTIONS
// Note: this basically checks that buffer protects against misuse and is not
// how users should do things.
SCENARIO
(
"SPSC buffers reject multiple producers"
)
{
GIVEN
(
"an SPSC buffer resource"
)
{
WHEN
(
"attaching a second producer"
)
{
THEN
(
"the buffer immediately calls on_consumer_cancel on it"
)
{
// Note: two resources should never point to the same buffer.
using
actor_t
=
event_based_actor
;
using
buffer_type
=
async
::
spsc_buffer
<
int
>
;
auto
buf
=
make_counted
<
buffer_type
>
(
20
,
5
);
auto
rd
=
async
::
consumer_resource
<
int
>
{
buf
};
auto
wr1
=
async
::
producer_resource
<
int
>
{
buf
};
auto
wr2
=
async
::
producer_resource
<
int
>
{
buf
};
auto
prod1
=
sys
.
spawn
([
wr1
](
actor_t
*
src
)
{
src
->
make_observable
().
iota
(
1
).
subscribe
(
wr1
);
});
self
->
monitor
(
prod1
);
run
();
auto
prod2
=
sys
.
spawn
([
wr2
](
actor_t
*
src
)
{
src
->
make_observable
().
iota
(
1
).
subscribe
(
wr2
);
});
self
->
monitor
(
prod2
);
run
();
// prod2 dies immediately to the exception.
expect
((
down_msg
),
to
(
self
).
with
(
down_msg
{
prod2
.
address
(),
sec
::
runtime_error
}));
CHECK
(
self
->
mailbox
().
empty
());
}
}
}
}
// Note: this basically checks that buffer protects against misuse and is not
// how users should do things.
SCENARIO
(
"SPSC buffers reject multiple consumers"
)
{
GIVEN
(
"an SPSC buffer resource"
)
{
WHEN
(
"attaching a second consumer"
)
{
THEN
(
"the buffer throws an exception"
)
{
// Note: two resources should never point to the same buffer.
using
actor_t
=
event_based_actor
;
using
buffer_type
=
async
::
spsc_buffer
<
int
>
;
auto
buf
=
make_counted
<
buffer_type
>
(
20
,
5
);
auto
rd1
=
async
::
consumer_resource
<
int
>
{
buf
};
auto
rd2
=
async
::
consumer_resource
<
int
>
{
buf
};
auto
wr
=
async
::
producer_resource
<
int
>
{
buf
};
auto
outputs
=
std
::
vector
<
int
>
{};
auto
snk1
=
sys
.
spawn
([
rd1
,
&
outputs
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd1
)
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
self
->
monitor
(
snk1
);
run
();
auto
snk2
=
sys
.
spawn
([
rd2
,
&
outputs
](
actor_t
*
snk
)
{
snk
->
make_observable
()
//
.
from_resource
(
rd2
)
.
for_each
([
&
outputs
](
int
x
)
{
outputs
.
emplace_back
(
x
);
});
});
self
->
monitor
(
snk2
);
run
();
// snk2 dies immediately to the exception.
expect
((
down_msg
),
to
(
self
).
with
(
down_msg
{
snk2
.
address
(),
sec
::
runtime_error
}));
CHECK
(
self
->
mailbox
().
empty
());
CHECK
(
outputs
.
empty
());
}
}
}
}
#endif // CAF_ENABLE_EXCEPTIONS
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