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
f1a3b267
Commit
f1a3b267
authored
Feb 19, 2022
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow socket managers to transfer socket ownership
parent
085a1045
Changes
18
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
916 additions
and
477 deletions
+916
-477
libcaf_net/caf/net/connection_acceptor.hpp
libcaf_net/caf/net/connection_acceptor.hpp
+12
-8
libcaf_net/caf/net/endpoint_manager_impl.hpp
libcaf_net/caf/net/endpoint_manager_impl.hpp
+12
-5
libcaf_net/caf/net/hypertext_oriented_layer_ptr.hpp
libcaf_net/caf/net/hypertext_oriented_layer_ptr.hpp
+1
-1
libcaf_net/caf/net/message_flow_bridge.hpp
libcaf_net/caf/net/message_flow_bridge.hpp
+1
-1
libcaf_net/caf/net/multiplexer.hpp
libcaf_net/caf/net/multiplexer.hpp
+52
-7
libcaf_net/caf/net/pollset_updater.hpp
libcaf_net/caf/net/pollset_updater.hpp
+2
-2
libcaf_net/caf/net/socket_manager.hpp
libcaf_net/caf/net/socket_manager.hpp
+83
-55
libcaf_net/caf/net/stream_transport.hpp
libcaf_net/caf/net/stream_transport.hpp
+243
-107
libcaf_net/src/multiplexer.cpp
libcaf_net/src/multiplexer.cpp
+225
-115
libcaf_net/src/pollset_updater.cpp
libcaf_net/src/pollset_updater.cpp
+15
-13
libcaf_net/src/socket_manager.cpp
libcaf_net/src/socket_manager.cpp
+33
-43
libcaf_net/test/multiplexer.cpp
libcaf_net/test/multiplexer.cpp
+166
-70
libcaf_net/test/net-test.hpp
libcaf_net/test/net-test.hpp
+28
-0
libcaf_net/test/net/actor_shell.cpp
libcaf_net/test/net/actor_shell.cpp
+1
-0
libcaf_net/test/net/length_prefix_framing.cpp
libcaf_net/test/net/length_prefix_framing.cpp
+7
-4
libcaf_net/test/net/producer_adapter.cpp
libcaf_net/test/net/producer_adapter.cpp
+1
-2
libcaf_net/test/net/typed_actor_shell.cpp
libcaf_net/test/net/typed_actor_shell.cpp
+1
-0
libcaf_net/test/stream_transport.cpp
libcaf_net/test/stream_transport.cpp
+33
-44
No files found.
libcaf_net/caf/net/connection_acceptor.hpp
View file @
f1a3b267
...
@@ -28,6 +28,10 @@ public:
...
@@ -28,6 +28,10 @@ public:
using
factory_type
=
Factory
;
using
factory_type
=
Factory
;
using
read_result
=
typename
socket_manager
::
read_result
;
using
write_result
=
typename
socket_manager
::
write_result
;
// -- constructors, destructors, and assignment operators --------------------
// -- constructors, destructors, and assignment operators --------------------
template
<
class
...
Ts
>
template
<
class
...
Ts
>
...
@@ -51,28 +55,28 @@ public:
...
@@ -51,28 +55,28 @@ public:
}
}
template
<
class
LowerLayerPtr
>
template
<
class
LowerLayerPtr
>
bool
handle_read_event
(
LowerLayerPtr
parent
)
{
read_result
handle_read_event
(
LowerLayerPtr
parent
)
{
CAF_LOG_TRACE
(
""
);
CAF_LOG_TRACE
(
""
);
if
(
auto
x
=
accept
(
parent
->
handle
()))
{
if
(
auto
x
=
accept
(
parent
->
handle
()))
{
socket_manager_ptr
child
=
factory_
.
make
(
*
x
,
owner_
->
mpx_ptr
());
socket_manager_ptr
child
=
factory_
.
make
(
*
x
,
owner_
->
mpx_ptr
());
if
(
!
child
)
{
if
(
!
child
)
{
CAF_LOG_ERROR
(
"factory failed to create a new child"
);
CAF_LOG_ERROR
(
"factory failed to create a new child"
);
parent
->
abort_reason
(
sec
::
runtime_error
);
parent
->
abort_reason
(
sec
::
runtime_error
);
return
false
;
return
read_result
::
stop
;
}
}
if
(
auto
err
=
child
->
init
(
cfg_
))
{
if
(
auto
err
=
child
->
init
(
cfg_
))
{
CAF_LOG_ERROR
(
"failed to initialize new child:"
<<
err
);
CAF_LOG_ERROR
(
"failed to initialize new child:"
<<
err
);
parent
->
abort_reason
(
std
::
move
(
err
));
parent
->
abort_reason
(
std
::
move
(
err
));
return
false
;
return
read_result
::
stop
;
}
}
if
(
limit_
==
0
)
{
if
(
limit_
==
0
)
{
return
true
;
return
read_result
::
again
;
}
else
{
}
else
{
return
++
accepted_
<
limit_
;
return
++
accepted_
<
limit_
?
read_result
::
again
:
read_result
::
stop
;
}
}
}
else
{
}
else
{
CAF_LOG_ERROR
(
"accept failed:"
<<
x
.
error
());
CAF_LOG_ERROR
(
"accept failed:"
<<
x
.
error
());
return
false
;
return
read_result
::
stop
;
}
}
}
}
...
@@ -82,9 +86,9 @@ public:
...
@@ -82,9 +86,9 @@ public:
}
}
template
<
class
LowerLayerPtr
>
template
<
class
LowerLayerPtr
>
bool
handle_write_event
(
LowerLayerPtr
)
{
write_result
handle_write_event
(
LowerLayerPtr
)
{
CAF_LOG_ERROR
(
"connection_acceptor received write event"
);
CAF_LOG_ERROR
(
"connection_acceptor received write event"
);
return
false
;
return
write_result
::
stop
;
}
}
template
<
class
LowerLayerPtr
>
template
<
class
LowerLayerPtr
>
...
...
libcaf_net/caf/net/endpoint_manager_impl.hpp
View file @
f1a3b267
...
@@ -23,6 +23,10 @@ public:
...
@@ -23,6 +23,10 @@ public:
using
application_type
=
typename
transport_type
::
application_type
;
using
application_type
=
typename
transport_type
::
application_type
;
using
read_result
=
typename
super
::
read_result
;
using
write_result
=
typename
super
::
write_result
;
// -- constructors, destructors, and assignment operators --------------------
// -- constructors, destructors, and assignment operators --------------------
endpoint_manager_impl
(
const
multiplexer_ptr
&
parent
,
actor_system
&
sys
,
endpoint_manager_impl
(
const
multiplexer_ptr
&
parent
,
actor_system
&
sys
,
...
@@ -52,11 +56,11 @@ public:
...
@@ -52,11 +56,11 @@ public:
return
transport_
.
init
(
*
this
);
return
transport_
.
init
(
*
this
);
}
}
bool
handle_read_event
()
override
{
read_result
handle_read_event
()
override
{
return
transport_
.
handle_read_event
(
*
this
);
return
transport_
.
handle_read_event
(
*
this
);
}
}
bool
handle_write_event
()
override
{
write_result
handle_write_event
()
override
{
if
(
!
this
->
queue_
.
blocked
())
{
if
(
!
this
->
queue_
.
blocked
())
{
this
->
queue_
.
fetch_more
();
this
->
queue_
.
fetch_more
();
auto
&
q
=
std
::
get
<
0
>
(
this
->
queue_
.
queue
().
queues
());
auto
&
q
=
std
::
get
<
0
>
(
this
->
queue_
.
queue
().
queues
());
...
@@ -83,10 +87,13 @@ public:
...
@@ -83,10 +87,13 @@ public:
}
}
if
(
!
transport_
.
handle_write_event
(
*
this
))
{
if
(
!
transport_
.
handle_write_event
(
*
this
))
{
if
(
this
->
queue_
.
blocked
())
if
(
this
->
queue_
.
blocked
())
return
false
;
return
write_result
::
stop
;
return
!
(
this
->
queue_
.
empty
()
&&
this
->
queue_
.
try_block
());
else
if
(
!
(
this
->
queue_
.
empty
()
&&
this
->
queue_
.
try_block
()))
return
write_result
::
again
;
else
return
write_result
::
stop
;
}
}
return
true
;
return
write_result
::
again
;
}
}
void
handle_error
(
sec
code
)
override
{
void
handle_error
(
sec
code
)
override
{
...
...
libcaf_net/caf/net/hypertext_oriented_layer_ptr.hpp
View file @
f1a3b267
...
@@ -117,7 +117,7 @@ public:
...
@@ -117,7 +117,7 @@ public:
std
::
string
len
;
std
::
string
len
;
header_fields_type
fields
;
header_fields_type
fields
;
if
(
!
content
.
empty
())
{
if
(
!
content
.
empty
())
{
auto
len
=
std
::
to_string
(
content
.
size
());
len
=
std
::
to_string
(
content
.
size
());
fields
.
emplace
(
"Content-Type"
,
content_type
);
fields
.
emplace
(
"Content-Type"
,
content_type
);
fields
.
emplace
(
"Content-Length"
,
len
);
fields
.
emplace
(
"Content-Length"
,
len
);
}
}
...
...
libcaf_net/caf/net/message_flow_bridge.hpp
View file @
f1a3b267
...
@@ -160,7 +160,7 @@ public:
...
@@ -160,7 +160,7 @@ public:
void
abort
(
LowerLayerPtr
,
const
error
&
reason
)
{
void
abort
(
LowerLayerPtr
,
const
error
&
reason
)
{
CAF_LOG_TRACE
(
CAF_ARG
(
reason
));
CAF_LOG_TRACE
(
CAF_ARG
(
reason
));
if
(
out_
)
{
if
(
out_
)
{
if
(
reason
==
sec
::
socket_disconnected
||
reason
==
sec
::
dis
card
ed
)
if
(
reason
==
sec
::
socket_disconnected
||
reason
==
sec
::
dis
pos
ed
)
out_
->
close
();
out_
->
close
();
else
else
out_
->
abort
(
reason
);
out_
->
abort
(
reason
);
...
...
libcaf_net/caf/net/multiplexer.hpp
View file @
f1a3b267
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
#include "caf/action.hpp"
#include "caf/action.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/pipe_socket.hpp"
#include "caf/net/pipe_socket.hpp"
...
@@ -24,15 +25,28 @@ struct pollfd;
...
@@ -24,15 +25,28 @@ struct pollfd;
namespace
caf
::
net
{
namespace
caf
::
net
{
class
pollset_updater
;
/// Multiplexes any number of ::socket_manager objects with a ::socket.
/// Multiplexes any number of ::socket_manager objects with a ::socket.
class
CAF_NET_EXPORT
multiplexer
{
class
CAF_NET_EXPORT
multiplexer
{
public:
public:
// -- member types -----------------------------------------------------------
// -- member types -----------------------------------------------------------
struct
poll_update
{
short
events
=
0
;
socket_manager_ptr
mgr
;
};
using
poll_update_map
=
detail
::
unordered_flat_map
<
socket
,
poll_update
>
;
using
pollfd_list
=
std
::
vector
<
pollfd
>
;
using
pollfd_list
=
std
::
vector
<
pollfd
>
;
using
manager_list
=
std
::
vector
<
socket_manager_ptr
>
;
using
manager_list
=
std
::
vector
<
socket_manager_ptr
>
;
// -- friends ----------------------------------------------------------------
friend
class
pollset_updater
;
// Needs access to the `do_*` functions.
// -- constructors, destructors, and assignment operators --------------------
// -- constructors, destructors, and assignment operators --------------------
/// @param parent Points to the owning middleman instance. May be `nullptr`
/// @param parent Points to the owning middleman instance. May be `nullptr`
...
@@ -55,12 +69,18 @@ public:
...
@@ -55,12 +69,18 @@ public:
/// Returns the index of `mgr` in the pollset or `-1`.
/// Returns the index of `mgr` in the pollset or `-1`.
ptrdiff_t
index_of
(
const
socket_manager_ptr
&
mgr
);
ptrdiff_t
index_of
(
const
socket_manager_ptr
&
mgr
);
/// Returns the index of `fd` in the pollset or `-1`.
ptrdiff_t
index_of
(
socket
fd
);
/// Returns the owning @ref middleman instance.
/// Returns the owning @ref middleman instance.
middleman
&
owner
();
middleman
&
owner
();
/// Returns the enclosing @ref actor_system.
/// Returns the enclosing @ref actor_system.
actor_system
&
system
();
actor_system
&
system
();
/// Computes the current mask for the manager. Mostly useful for testing.
operation
mask_of
(
const
socket_manager_ptr
&
mgr
);
// -- thread-safe signaling --------------------------------------------------
// -- thread-safe signaling --------------------------------------------------
/// Registers `mgr` for read events.
/// Registers `mgr` for read events.
...
@@ -109,6 +129,9 @@ public:
...
@@ -109,6 +129,9 @@ public:
/// ready as a result.
/// ready as a result.
bool
poll_once
(
bool
blocking
);
bool
poll_once
(
bool
blocking
);
/// Applies all pending updates.
void
apply_updates
();
/// Sets the thread ID to `std::this_thread::id()`.
/// Sets the thread ID to `std::this_thread::id()`.
void
set_thread_id
();
void
set_thread_id
();
...
@@ -123,13 +146,7 @@ protected:
...
@@ -123,13 +146,7 @@ protected:
// -- utility functions ------------------------------------------------------
// -- utility functions ------------------------------------------------------
/// Handles an I/O event on given manager.
/// Handles an I/O event on given manager.
short
handle
(
const
socket_manager_ptr
&
mgr
,
short
events
,
short
revents
);
void
handle
(
const
socket_manager_ptr
&
mgr
,
short
events
,
short
revents
);
/// Adds a new socket manager to the pollset.
void
add
(
socket_manager_ptr
mgr
);
/// Deletes a known socket manager from the pollset.
void
del
(
ptrdiff_t
index
);
// -- member variables -------------------------------------------------------
// -- member variables -------------------------------------------------------
...
@@ -140,6 +157,10 @@ protected:
...
@@ -140,6 +157,10 @@ protected:
/// order as their sockets appear in `pollset_`.
/// order as their sockets appear in `pollset_`.
manager_list
managers_
;
manager_list
managers_
;
/// Caches changes to the events mask of managed sockets until they can safely
/// take place.
poll_update_map
updates_
;
/// Stores the ID of the thread this multiplexer is running in. Set when
/// Stores the ID of the thread this multiplexer is running in. Set when
/// calling `init()`.
/// calling `init()`.
std
::
thread
::
id
tid_
;
std
::
thread
::
id
tid_
;
...
@@ -157,15 +178,39 @@ protected:
...
@@ -157,15 +178,39 @@ protected:
bool
shutting_down_
=
false
;
bool
shutting_down_
=
false
;
private:
private:
/// Returns a change entry for the socket at given index. Lazily creates a new
/// entry before returning if necessary.
poll_update
&
update_for
(
ptrdiff_t
index
);
/// Returns a change entry for the socket of the manager.
poll_update
&
update_for
(
const
socket_manager_ptr
&
mgr
);
/// Writes `opcode` and pointer to `mgr` the the pipe for handling an event
/// Writes `opcode` and pointer to `mgr` the the pipe for handling an event
/// later via the pollset updater.
/// later via the pollset updater.
template
<
class
T
>
template
<
class
T
>
void
write_to_pipe
(
uint8_t
opcode
,
T
*
ptr
);
void
write_to_pipe
(
uint8_t
opcode
,
T
*
ptr
);
/// @copydoc write_to_pipe
template
<
class
Enum
,
class
T
>
template
<
class
Enum
,
class
T
>
std
::
enable_if_t
<
std
::
is_enum_v
<
Enum
>>
write_to_pipe
(
Enum
opcode
,
T
*
ptr
)
{
std
::
enable_if_t
<
std
::
is_enum_v
<
Enum
>>
write_to_pipe
(
Enum
opcode
,
T
*
ptr
)
{
write_to_pipe
(
static_cast
<
uint8_t
>
(
opcode
),
ptr
);
write_to_pipe
(
static_cast
<
uint8_t
>
(
opcode
),
ptr
);
}
}
// -- internal callback the pollset updater ----------------------------------
void
do_shutdown
();
void
do_register_reading
(
const
socket_manager_ptr
&
mgr
);
void
do_register_writing
(
const
socket_manager_ptr
&
mgr
);
void
do_discard
(
const
socket_manager_ptr
&
mgr
);
void
do_shutdown_reading
(
const
socket_manager_ptr
&
mgr
);
void
do_shutdown_writing
(
const
socket_manager_ptr
&
mgr
);
void
do_init
(
const
socket_manager_ptr
&
mgr
);
};
};
}
// namespace caf::net
}
// namespace caf::net
libcaf_net/caf/net/pollset_updater.hpp
View file @
f1a3b267
...
@@ -51,9 +51,9 @@ public:
...
@@ -51,9 +51,9 @@ public:
error
init
(
const
settings
&
config
)
override
;
error
init
(
const
settings
&
config
)
override
;
bool
handle_read_event
()
override
;
read_result
handle_read_event
()
override
;
bool
handle_write_event
()
override
;
write_result
handle_write_event
()
override
;
void
handle_error
(
sec
code
)
override
;
void
handle_error
(
sec
code
)
override
;
...
...
libcaf_net/caf/net/socket_manager.hpp
View file @
f1a3b267
...
@@ -14,7 +14,6 @@
...
@@ -14,7 +14,6 @@
#include "caf/make_counted.hpp"
#include "caf/make_counted.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/typed_actor_shell.hpp"
#include "caf/net/typed_actor_shell.hpp"
#include "caf/ref_counted.hpp"
#include "caf/ref_counted.hpp"
...
@@ -28,13 +27,48 @@ class CAF_NET_EXPORT socket_manager : public ref_counted {
...
@@ -28,13 +27,48 @@ class CAF_NET_EXPORT socket_manager : public ref_counted {
public:
public:
// -- member types -----------------------------------------------------------
// -- member types -----------------------------------------------------------
/// A callback for unprocessed messages.
using
fallback_handler
=
unique_callback_ptr
<
result
<
message
>
(
message
&
)
>
;
using
fallback_handler
=
unique_callback_ptr
<
result
<
message
>
(
message
&
)
>
;
/// Encodes how a manager wishes to proceed after a read operation.
enum
class
read_result
{
/// Indicates that a manager wants to read again later.
again
,
/// Indicates that a manager wants to stop reading until explicitly resumed.
stop
,
/// Indicates that a manager wants to write to the socket instead of reading
/// from the socket.
want_write
,
/// Indicates that a manager is done with the socket and hands ownership to
/// another manager.
handover
,
};
/// Encodes how a manager wishes to proceed after a write operation.
enum
class
write_result
{
/// Indicates that a manager wants to read again later.
again
,
/// Indicates that a manager wants to stop reading until explicitly resumed.
stop
,
/// Indicates that a manager wants to read from the socket instead of
/// writing to the socket.
want_read
,
/// Indicates that a manager is done with the socket and hands ownership to
/// another manager.
handover
,
};
/// Stores manager-related flags in a single block.
struct
flags_t
{
bool
read_closed
:
1
;
bool
write_closed
:
1
;
};
// -- constructors, destructors, and assignment operators --------------------
// -- constructors, destructors, and assignment operators --------------------
/// @pre `handle != invalid_socket`
/// @pre `handle != invalid_socket`
/// @pre `
parent
!= nullptr`
/// @pre `
mpx
!= nullptr`
socket_manager
(
socket
handle
,
multiplexer
*
parent
);
socket_manager
(
socket
handle
,
multiplexer
*
mpx
);
~
socket_manager
()
override
;
~
socket_manager
()
override
;
...
@@ -59,69 +93,45 @@ public:
...
@@ -59,69 +93,45 @@ public:
/// Returns the owning @ref multiplexer instance.
/// Returns the owning @ref multiplexer instance.
multiplexer
&
mpx
()
noexcept
{
multiplexer
&
mpx
()
noexcept
{
return
*
parent
_
;
return
*
mpx
_
;
}
}
/// Returns the owning @ref multiplexer instance.
/// Returns the owning @ref multiplexer instance.
const
multiplexer
&
mpx
()
const
noexcept
{
const
multiplexer
&
mpx
()
const
noexcept
{
return
*
parent
_
;
return
*
mpx
_
;
}
}
/// Returns a pointer to the owning @ref multiplexer instance.
/// Returns a pointer to the owning @ref multiplexer instance.
multiplexer
*
mpx_ptr
()
noexcept
{
multiplexer
*
mpx_ptr
()
noexcept
{
return
parent
_
;
return
mpx
_
;
}
}
/// Returns a pointer to the owning @ref multiplexer instance.
/// Returns a pointer to the owning @ref multiplexer instance.
const
multiplexer
*
mpx_ptr
()
const
noexcept
{
const
multiplexer
*
mpx_ptr
()
const
noexcept
{
return
parent
_
;
return
mpx
_
;
}
}
/// Returns registered operations (read, write, or both).
/// Closes the read channel of the socket.
operation
mask
()
const
noexcept
{
void
close_read
()
noexcept
;
return
mask_
;
}
/// Convenience function for checking whether `mask()` contains the read bit.
/// Closes the write channel of the socket.
bool
is_reading
()
const
noexcept
{
void
close_write
()
noexcept
;
return
net
::
is_reading
(
mask_
);
}
///
Convenience function for checking whether `mask()` contains the write bi
t.
///
Returns whether the manager closed read operations on the socke
t.
bool
is_writing
()
const
noexcept
{
[[
nodiscard
]]
bool
read_closed
()
const
noexcept
{
return
net
::
is_writing
(
mask_
)
;
return
flags_
.
read_closed
;
}
}
/// Tries to add the read flag to the event mask.
/// Returns whether the manager closed write operations on the socket.
/// @returns `true` if the flag was added, `false` if this call had no effect.
[[
nodiscard
]]
bool
write_closed
()
const
noexcept
{
bool
set_read_flag
()
noexcept
;
return
flags_
.
write_closed
;
}
/// Tries to add the write flag to the event mask.
/// @returns `true` if the flag was added, `false` if this call had no effect.
bool
set_write_flag
()
noexcept
;
/// Removes the read flag from the event mask if present.
bool
unset_read_flag
()
noexcept
;
/// Removes the write flag from the event mask if present.
bool
unset_write_flag
()
noexcept
;
/// Adds the `block_read` flag to the event mask.
void
block_reads
()
noexcept
;
/// Adds the `block_write` flag to the event mask.
void
block_writes
()
noexcept
;
/// Blocks reading and writing in the event mask.
void
block_reads_and_writes
()
noexcept
;
const
error
&
abort_reason
()
const
noexcept
{
const
error
&
abort_reason
()
const
noexcept
{
return
abort_reason_
;
return
abort_reason_
;
}
}
void
abort_reason
(
error
reason
)
noexcept
{
void
abort_reason
(
error
reason
)
noexcept
;
abort_reason_
=
std
::
move
(
reason
);
}
template
<
class
...
Ts
>
template
<
class
...
Ts
>
const
error
&
abort_reason_or
(
Ts
&&
...
xs
)
{
const
error
&
abort_reason_or
(
Ts
&&
...
xs
)
{
...
@@ -153,23 +163,26 @@ public:
...
@@ -153,23 +163,26 @@ public:
// -- event loop management --------------------------------------------------
// -- event loop management --------------------------------------------------
/// Registers the manager for read operations on the @ref multiplexer.
void
register_reading
();
void
register_reading
();
/// Registers the manager for write operations on the @ref multiplexer.
void
register_writing
();
void
register_writing
();
void
shutdown_reading
();
/// Performs a handover to another manager after `handle_read_event` or
/// `handle_read_event` returned `handover`.
void
shutdown_writing
();
socket_manager_ptr
do_handover
();
// -- pure virtual member functions ------------------------------------------
// -- pure virtual member functions ------------------------------------------
/// Initializes the manager and its all of its sub-components.
virtual
error
init
(
const
settings
&
config
)
=
0
;
virtual
error
init
(
const
settings
&
config
)
=
0
;
/// Called whenever the socket received new data.
/// Called whenever the socket received new data.
virtual
bool
handle_read_event
()
=
0
;
virtual
read_result
handle_read_event
()
=
0
;
/// Called whenever the socket is allowed to send data.
/// Called whenever the socket is allowed to send data.
virtual
bool
handle_write_event
()
=
0
;
virtual
write_result
handle_write_event
()
=
0
;
/// Called when the remote side becomes unreachable due to an error.
/// Called when the remote side becomes unreachable due to an error.
/// @param code The error code as reported by the operating system.
/// @param code The error code as reported by the operating system.
...
@@ -179,16 +192,25 @@ public:
...
@@ -179,16 +192,25 @@ public:
/// function on active managers is a no-op.
/// function on active managers is a no-op.
virtual
void
continue_reading
()
=
0
;
virtual
void
continue_reading
()
=
0
;
/// Returns the new manager for the socket after `handle_read_event` or
/// `handle_read_event` returned `handover`.
/// @note When returning a non-null pointer, the new manager *must* also be
/// initialized.
virtual
socket_manager_ptr
make_next_manager
(
socket
handle
);
protected:
protected:
// --
member variables ----------
---------------------------------------------
// --
protected member variables
---------------------------------------------
socket
handle_
;
socket
handle_
;
operation
mask
_
;
multiplexer
*
mpx
_
;
multiplexer
*
parent_
;
private:
// -- private member variables -----------------------------------------------
error
abort_reason_
;
error
abort_reason_
;
flags_t
flags_
;
};
};
template
<
class
Protocol
>
template
<
class
Protocol
>
...
@@ -196,10 +218,16 @@ class socket_manager_impl : public socket_manager {
...
@@ -196,10 +218,16 @@ class socket_manager_impl : public socket_manager {
public:
public:
// -- member types -----------------------------------------------------------
// -- member types -----------------------------------------------------------
using
super
=
socket_manager
;
using
output_tag
=
tag
::
io_event_oriented
;
using
output_tag
=
tag
::
io_event_oriented
;
using
socket_type
=
typename
Protocol
::
socket_type
;
using
socket_type
=
typename
Protocol
::
socket_type
;
using
read_result
=
typename
super
::
read_result
;
using
write_result
=
typename
super
::
write_result
;
// -- constructors, destructors, and assignment operators --------------------
// -- constructors, destructors, and assignment operators --------------------
template
<
class
...
Ts
>
template
<
class
...
Ts
>
...
@@ -228,20 +256,20 @@ public:
...
@@ -228,20 +256,20 @@ public:
// -- event callbacks --------------------------------------------------------
// -- event callbacks --------------------------------------------------------
bool
handle_read_event
()
override
{
read_result
handle_read_event
()
override
{
CAF_LOG_TRACE
(
""
);
CAF_LOG_TRACE
(
""
);
return
protocol_
.
handle_read_event
(
this
);
return
protocol_
.
handle_read_event
(
this
);
}
}
bool
handle_write_event
()
override
{
write_result
handle_write_event
()
override
{
CAF_LOG_TRACE
(
""
);
CAF_LOG_TRACE
(
""
);
return
protocol_
.
handle_write_event
(
this
);
return
protocol_
.
handle_write_event
(
this
);
}
}
void
handle_error
(
sec
code
)
override
{
void
handle_error
(
sec
code
)
override
{
CAF_LOG_TRACE
(
CAF_ARG
(
code
));
CAF_LOG_TRACE
(
CAF_ARG
(
code
));
abort_reason_
=
code
;
this
->
abort_reason
(
make_error
(
code
))
;
return
protocol_
.
abort
(
this
,
abort_reason_
);
return
protocol_
.
abort
(
this
,
this
->
abort_reason
()
);
}
}
void
continue_reading
()
override
{
void
continue_reading
()
override
{
...
...
libcaf_net/caf/net/stream_transport.hpp
View file @
f1a3b267
This diff is collapsed.
Click to expand it.
libcaf_net/src/multiplexer.cpp
View file @
f1a3b267
This diff is collapsed.
Click to expand it.
libcaf_net/src/pollset_updater.cpp
View file @
f1a3b267
...
@@ -18,7 +18,7 @@ namespace caf::net {
...
@@ -18,7 +18,7 @@ namespace caf::net {
pollset_updater
::
pollset_updater
(
pipe_socket
read_handle
,
multiplexer
*
parent
)
pollset_updater
::
pollset_updater
(
pipe_socket
read_handle
,
multiplexer
*
parent
)
:
super
(
read_handle
,
parent
)
{
:
super
(
read_handle
,
parent
)
{
mask_
=
operation
::
read
;
// nop
}
}
pollset_updater
::~
pollset_updater
()
{
pollset_updater
::~
pollset_updater
()
{
...
@@ -45,7 +45,7 @@ void run_action(intptr_t ptr) {
...
@@ -45,7 +45,7 @@ void run_action(intptr_t ptr) {
}
// namespace
}
// namespace
bool
pollset_updater
::
handle_read_event
()
{
pollset_updater
::
read_result
pollset_updater
::
handle_read_event
()
{
CAF_LOG_TRACE
(
""
);
CAF_LOG_TRACE
(
""
);
for
(;;)
{
for
(;;)
{
CAF_ASSERT
((
buf_
.
size
()
-
buf_size_
)
>
0
);
CAF_ASSERT
((
buf_
.
size
()
-
buf_size_
)
>
0
);
...
@@ -60,29 +60,29 @@ bool pollset_updater::handle_read_event() {
...
@@ -60,29 +60,29 @@ bool pollset_updater::handle_read_event() {
memcpy
(
&
ptr
,
buf_
.
data
()
+
1
,
sizeof
(
intptr_t
));
memcpy
(
&
ptr
,
buf_
.
data
()
+
1
,
sizeof
(
intptr_t
));
switch
(
static_cast
<
code
>
(
opcode
))
{
switch
(
static_cast
<
code
>
(
opcode
))
{
case
code
:
:
register_reading
:
case
code
:
:
register_reading
:
parent_
->
register_reading
(
as_mgr
(
ptr
));
mpx_
->
do_
register_reading
(
as_mgr
(
ptr
));
break
;
break
;
case
code
:
:
register_writing
:
case
code
:
:
register_writing
:
parent_
->
register_writing
(
as_mgr
(
ptr
));
mpx_
->
do_
register_writing
(
as_mgr
(
ptr
));
break
;
break
;
case
code
:
:
init_manager
:
case
code
:
:
init_manager
:
parent_
->
init
(
as_mgr
(
ptr
));
mpx_
->
do_
init
(
as_mgr
(
ptr
));
break
;
break
;
case
code
:
:
discard_manager
:
case
code
:
:
discard_manager
:
parent_
->
discard
(
as_mgr
(
ptr
));
mpx_
->
do_
discard
(
as_mgr
(
ptr
));
break
;
break
;
case
code
:
:
shutdown_reading
:
case
code
:
:
shutdown_reading
:
parent_
->
shutdown_reading
(
as_mgr
(
ptr
));
mpx_
->
do_
shutdown_reading
(
as_mgr
(
ptr
));
break
;
break
;
case
code
:
:
shutdown_writing
:
case
code
:
:
shutdown_writing
:
parent_
->
shutdown_writing
(
as_mgr
(
ptr
));
mpx_
->
do_
shutdown_writing
(
as_mgr
(
ptr
));
break
;
break
;
case
code
:
:
run_action
:
case
code
:
:
run_action
:
run_action
(
ptr
);
run_action
(
ptr
);
break
;
break
;
case
code
:
:
shutdown
:
case
code
:
:
shutdown
:
CAF_ASSERT
(
ptr
==
0
);
CAF_ASSERT
(
ptr
==
0
);
parent_
->
shutdown
();
mpx_
->
do_
shutdown
();
break
;
break
;
default:
default:
CAF_LOG_ERROR
(
"opcode not recognized: "
<<
CAF_ARG
(
opcode
));
CAF_LOG_ERROR
(
"opcode not recognized: "
<<
CAF_ARG
(
opcode
));
...
@@ -91,15 +91,17 @@ bool pollset_updater::handle_read_event() {
...
@@ -91,15 +91,17 @@ bool pollset_updater::handle_read_event() {
}
}
}
else
if
(
num_bytes
==
0
)
{
}
else
if
(
num_bytes
==
0
)
{
CAF_LOG_DEBUG
(
"pipe closed, assume shutdown"
);
CAF_LOG_DEBUG
(
"pipe closed, assume shutdown"
);
return
false
;
return
read_result
::
stop
;
}
else
if
(
last_socket_error_is_temporary
())
{
return
read_result
::
again
;
}
else
{
}
else
{
return
last_socket_error_is_temporary
()
;
return
read_result
::
stop
;
}
}
}
}
}
}
bool
pollset_updater
::
handle_write_event
()
{
pollset_updater
::
write_result
pollset_updater
::
handle_write_event
()
{
return
false
;
return
write_result
::
stop
;
}
}
void
pollset_updater
::
handle_error
(
sec
)
{
void
pollset_updater
::
handle_error
(
sec
)
{
...
...
libcaf_net/src/socket_manager.cpp
View file @
f1a3b267
...
@@ -10,10 +10,11 @@
...
@@ -10,10 +10,11 @@
namespace
caf
::
net
{
namespace
caf
::
net
{
socket_manager
::
socket_manager
(
socket
handle
,
multiplexer
*
parent
)
socket_manager
::
socket_manager
(
socket
handle
,
multiplexer
*
mpx
)
:
handle_
(
handle
),
m
ask_
(
operation
::
none
),
parent_
(
parent
)
{
:
handle_
(
handle
),
m
px_
(
mpx
)
{
CAF_ASSERT
(
handle_
!=
invalid_socket
);
CAF_ASSERT
(
handle_
!=
invalid_socket
);
CAF_ASSERT
(
parent
!=
nullptr
);
CAF_ASSERT
(
mpx_
!=
nullptr
);
memset
(
&
flags_
,
0
,
sizeof
(
flags_t
));
}
}
socket_manager
::~
socket_manager
()
{
socket_manager
::~
socket_manager
()
{
...
@@ -21,62 +22,51 @@ socket_manager::~socket_manager() {
...
@@ -21,62 +22,51 @@ socket_manager::~socket_manager() {
}
}
actor_system
&
socket_manager
::
system
()
noexcept
{
actor_system
&
socket_manager
::
system
()
noexcept
{
CAF_ASSERT
(
parent
_
!=
nullptr
);
CAF_ASSERT
(
mpx
_
!=
nullptr
);
return
parent
_
->
system
();
return
mpx
_
->
system
();
}
}
bool
socket_manager
::
set_read_flag
()
noexcept
{
void
socket_manager
::
close_read
()
noexcept
{
auto
old
=
mask_
;
// TODO: extend transport API for closing read operations.
mask_
=
add_read_flag
(
mask_
);
flags_
.
read_closed
=
true
;
return
old
!=
mask_
;
}
}
bool
socket_manager
::
set_write_flag
()
noexcept
{
void
socket_manager
::
close_write
()
noexcept
{
auto
old
=
mask_
;
// TODO: extend transport API for closing write operations.
mask_
=
add_write_flag
(
mask_
);
flags_
.
write_closed
=
true
;
return
old
!=
mask_
;
}
}
bool
socket_manager
::
unset_read_flag
()
noexcept
{
void
socket_manager
::
abort_reason
(
error
reason
)
noexcept
{
auto
old
=
mask_
;
abort_reason_
=
std
::
move
(
reason
);
mask_
=
remove_read_flag
(
mask_
);
flags_
.
read_closed
=
true
;
return
old
!=
mask_
;
flags_
.
write_closed
=
true
;
}
bool
socket_manager
::
unset_write_flag
()
noexcept
{
auto
old
=
mask_
;
mask_
=
remove_write_flag
(
mask_
);
return
old
!=
mask_
;
}
void
socket_manager
::
block_reads
()
noexcept
{
mask_
=
net
::
block_reads
(
mask_
);
}
void
socket_manager
::
block_writes
()
noexcept
{
mask_
=
net
::
block_writes
(
mask_
);
}
void
socket_manager
::
block_reads_and_writes
()
noexcept
{
mask_
=
operation
::
shutdown
;
}
}
void
socket_manager
::
register_reading
()
{
void
socket_manager
::
register_reading
()
{
if
(
!
net
::
is_reading
(
mask_
)
&&
!
is_read_blocked
(
mask_
))
if
(
!
read_closed
(
))
parent
_
->
register_reading
(
this
);
mpx
_
->
register_reading
(
this
);
}
}
void
socket_manager
::
register_writing
()
{
void
socket_manager
::
register_writing
()
{
if
(
!
net
::
is_writing
(
mask_
)
&&
!
is_write_blocked
(
mask_
))
if
(
!
write_closed
(
))
parent
_
->
register_writing
(
this
);
mpx
_
->
register_writing
(
this
);
}
}
void
socket_manager
::
shutdown_reading
()
{
socket_manager_ptr
socket_manager
::
do_handover
()
{
parent_
->
shutdown_reading
(
this
);
flags_
.
read_closed
=
true
;
flags_
.
write_closed
=
true
;
auto
hdl
=
handle_
;
handle_
=
invalid_socket
;
if
(
auto
ptr
=
make_next_manager
(
hdl
))
{
return
ptr
;
}
else
{
close
(
hdl
);
return
nullptr
;
}
}
}
void
socket_manager
::
shutdown_writing
(
)
{
socket_manager_ptr
socket_manager
::
make_next_manager
(
socket
)
{
parent_
->
shutdown_writing
(
this
)
;
return
{}
;
}
}
}
// namespace caf::net
}
// namespace caf::net
libcaf_net/test/multiplexer.cpp
View file @
f1a3b267
...
@@ -6,8 +6,7 @@
...
@@ -6,8 +6,7 @@
#include "caf/net/multiplexer.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "net-test.hpp"
#include "caf/test/dsl.hpp"
#include <new>
#include <new>
#include <tuple>
#include <tuple>
...
@@ -23,19 +22,21 @@ using namespace caf::net;
...
@@ -23,19 +22,21 @@ using namespace caf::net;
namespace
{
namespace
{
using
shared_atomic_count
=
std
::
shared_ptr
<
std
::
atomic
<
size_t
>>
;
class
dummy_manager
:
public
socket_manager
{
class
dummy_manager
:
public
socket_manager
{
public:
public:
dummy_manager
(
s
ize_t
&
manager_count
,
stream_socket
handl
e
,
dummy_manager
(
s
tream_socket
handle
,
multiplexer
*
parent
,
std
::
string
nam
e
,
multiplexer
*
pare
nt
)
shared_atomic_count
cou
nt
)
:
socket_manager
(
handle
,
parent
),
count_
(
manager_
count
)
{
:
socket_manager
(
handle
,
parent
),
name
(
std
::
move
(
name
)),
count_
(
count
)
{
CAF_
MESSAGE
(
"created new dummy manager"
);
MESSAGE
(
"created new dummy manager"
);
++
count_
;
++
*
count_
;
rd_buf_
.
resize
(
1024
);
rd_buf_
.
resize
(
1024
);
}
}
~
dummy_manager
()
{
~
dummy_manager
()
{
CAF_
MESSAGE
(
"destroyed dummy manager"
);
MESSAGE
(
"destroyed dummy manager"
);
--
count_
;
--
*
count_
;
}
}
error
init
(
const
settings
&
)
override
{
error
init
(
const
settings
&
)
override
{
...
@@ -46,7 +47,11 @@ public:
...
@@ -46,7 +47,11 @@ public:
return
socket_cast
<
stream_socket
>
(
handle_
);
return
socket_cast
<
stream_socket
>
(
handle_
);
}
}
bool
handle_read_event
()
override
{
read_result
handle_read_event
()
override
{
if
(
trigger_handover
)
{
MESSAGE
(
name
<<
" triggered a handover"
);
return
read_result
::
handover
;
}
if
(
read_capacity
()
<
1024
)
if
(
read_capacity
()
<
1024
)
rd_buf_
.
resize
(
rd_buf_
.
size
()
+
2048
);
rd_buf_
.
resize
(
rd_buf_
.
size
()
+
2048
);
auto
num_bytes
=
read
(
handle
(),
auto
num_bytes
=
read
(
handle
(),
...
@@ -54,28 +59,47 @@ public:
...
@@ -54,28 +59,47 @@ public:
if
(
num_bytes
>
0
)
{
if
(
num_bytes
>
0
)
{
CAF_ASSERT
(
num_bytes
>
0
);
CAF_ASSERT
(
num_bytes
>
0
);
rd_buf_pos_
+=
num_bytes
;
rd_buf_pos_
+=
num_bytes
;
return
true
;
return
read_result
::
again
;
}
else
if
(
num_bytes
<
0
&&
last_socket_error_is_temporary
())
{
return
read_result
::
again
;
}
else
{
return
read_result
::
stop
;
}
}
return
num_bytes
<
0
&&
last_socket_error_is_temporary
();
}
}
bool
handle_write_event
()
override
{
write_result
handle_write_event
()
override
{
if
(
trigger_handover
)
{
MESSAGE
(
name
<<
" triggered a handover"
);
return
write_result
::
handover
;
}
if
(
wr_buf_
.
size
()
==
0
)
if
(
wr_buf_
.
size
()
==
0
)
return
false
;
return
write_result
::
stop
;
auto
num_bytes
=
write
(
handle
(),
wr_buf_
);
auto
num_bytes
=
write
(
handle
(),
wr_buf_
);
if
(
num_bytes
>
0
)
{
if
(
num_bytes
>
0
)
{
wr_buf_
.
erase
(
wr_buf_
.
begin
(),
wr_buf_
.
begin
()
+
num_bytes
);
wr_buf_
.
erase
(
wr_buf_
.
begin
(),
wr_buf_
.
begin
()
+
num_bytes
);
return
wr_buf_
.
size
()
>
0
;
return
wr_buf_
.
size
()
>
0
?
write_result
::
again
:
write_result
::
stop
;
}
}
return
num_bytes
<
0
&&
last_socket_error_is_temporary
();
return
num_bytes
<
0
&&
last_socket_error_is_temporary
()
?
write_result
::
again
:
write_result
::
stop
;
}
}
void
handle_error
(
sec
code
)
override
{
void
handle_error
(
sec
code
)
override
{
CAF_
FAIL
(
"handle_error called with code "
<<
code
);
FAIL
(
"handle_error called with code "
<<
code
);
}
}
void
continue_reading
()
override
{
void
continue_reading
()
override
{
CAF_FAIL
(
"continue_reading called"
);
FAIL
(
"continue_reading called"
);
}
socket_manager_ptr
make_next_manager
(
socket
handle
)
override
{
if
(
next
!=
nullptr
)
FAIL
(
"asked to do handover twice!"
);
next
=
make_counted
<
dummy_manager
>
(
socket_cast
<
stream_socket
>
(
handle
),
mpx_
,
"Carl"
,
count_
);
if
(
auto
err
=
next
->
init
(
settings
{}))
FAIL
(
"next->init failed: "
<<
err
);
return
next
;
}
}
void
send
(
string_view
x
)
{
void
send
(
string_view
x
)
{
...
@@ -89,6 +113,12 @@ public:
...
@@ -89,6 +113,12 @@ public:
return
result
;
return
result
;
}
}
bool
trigger_handover
=
false
;
intrusive_ptr
<
dummy_manager
>
next
;
std
::
string
name
;
private:
private:
byte
*
read_position_begin
()
{
byte
*
read_position_begin
()
{
return
rd_buf_
.
data
()
+
rd_buf_pos_
;
return
rd_buf_
.
data
()
+
rd_buf_pos_
;
...
@@ -102,7 +132,7 @@ private:
...
@@ -102,7 +132,7 @@ private:
return
rd_buf_
.
size
()
-
rd_buf_pos_
;
return
rd_buf_
.
size
()
-
rd_buf_pos_
;
}
}
s
ize_t
&
count_
;
s
hared_atomic_count
count_
;
size_t
rd_buf_pos_
=
0
;
size_t
rd_buf_pos_
=
0
;
...
@@ -115,84 +145,150 @@ using dummy_manager_ptr = intrusive_ptr<dummy_manager>;
...
@@ -115,84 +145,150 @@ using dummy_manager_ptr = intrusive_ptr<dummy_manager>;
struct
fixture
:
host_fixture
{
struct
fixture
:
host_fixture
{
fixture
()
:
mpx
(
nullptr
)
{
fixture
()
:
mpx
(
nullptr
)
{
manager_count
=
std
::
make_shared
<
std
::
atomic
<
size_t
>>
(
0
);
mpx
.
set_thread_id
();
mpx
.
set_thread_id
();
}
}
~
fixture
()
{
~
fixture
()
{
CAF_REQUIRE_EQUAL
(
manager_count
,
0u
);
mpx
.
shutdown
();
exhaust
();
REQUIRE_EQ
(
*
manager_count
,
0u
);
}
}
void
exhaust
()
{
void
exhaust
()
{
mpx
.
apply_updates
();
while
(
mpx
.
poll_once
(
false
))
while
(
mpx
.
poll_once
(
false
))
;
// Repeat.
;
// Repeat.
}
}
size_t
manager_count
=
0
;
void
apply_updates
()
{
mpx
.
apply_updates
();
}
auto
make_manager
(
stream_socket
fd
,
std
::
string
name
)
{
return
make_counted
<
dummy_manager
>
(
fd
,
&
mpx
,
std
::
move
(
name
),
manager_count
);
}
void
init
()
{
if
(
auto
err
=
mpx
.
init
())
FAIL
(
"mpx.init failed: "
<<
err
);
exhaust
();
}
shared_atomic_count
manager_count
;
multiplexer
mpx
;
multiplexer
mpx
;
};
};
}
// namespace
}
// namespace
CAF_TEST_FIXTURE_SCOPE
(
multiplexer_tests
,
fixture
)
BEGIN_FIXTURE_SCOPE
(
fixture
)
CAF_TEST
(
default
construction
)
{
SCENARIO
(
"the multiplexer has no socket managers after default construction"
)
{
CAF_CHECK_EQUAL
(
mpx
.
num_socket_managers
(),
0u
);
GIVEN
(
"a default constructed multiplexer"
)
{
WHEN
(
"querying the number of socket managers"
)
{
THEN
(
"the result is 0"
)
{
CHECK_EQ
(
mpx
.
num_socket_managers
(),
0u
);
}
}
}
}
}
CAF_TEST
(
init
)
{
SCENARIO
(
"the multiplexer constructs the pollset updater while initializing"
)
{
CAF_CHECK_EQUAL
(
mpx
.
num_socket_managers
(),
0u
);
GIVEN
(
"an initialized multiplexer"
)
{
CAF_REQUIRE_EQUAL
(
mpx
.
init
(),
none
);
WHEN
(
"querying the number of socket managers"
)
{
CAF_CHECK_EQUAL
(
mpx
.
num_socket_managers
(),
1u
);
THEN
(
"the result is 1"
)
{
mpx
.
shutdown
();
CHECK_EQ
(
mpx
.
num_socket_managers
(),
0u
);
CHECK_EQ
(
mpx
.
init
(),
none
);
exhaust
();
exhaust
();
// Calling run must have no effect now.
CHECK_EQ
(
mpx
.
num_socket_managers
(),
1u
);
mpx
.
run
();
}
}
}
}
}
CAF_TEST
(
send
and
receive
)
{
SCENARIO
(
"socket managers can register for read and write operations"
)
{
CAF_REQUIRE_EQUAL
(
mpx
.
init
(),
none
);
GIVEN
(
"an initialized multiplexer"
)
{
auto
sockets
=
unbox
(
make_stream_socket_pair
()
);
init
(
);
{
// Lifetime scope of alice and bob.
WHEN
(
"socket managers register for read and write operations"
)
{
auto
alice
=
make_counted
<
dummy_manager
>
(
manager_count
,
sockets
.
first
,
auto
[
alice_fd
,
bob_fd
]
=
unbox
(
make_stream_socket_pair
());
&
mpx
);
auto
alice
=
make_manager
(
alice_fd
,
"Alice"
);
auto
bob
=
make_counted
<
dummy_manager
>
(
manager_count
,
sockets
.
second
,
&
mpx
);
auto
bob
=
make_manager
(
bob_fd
,
"Bob"
);
alice
->
register_reading
();
alice
->
register_reading
();
bob
->
register_reading
();
bob
->
register_reading
();
CAF_CHECK_EQUAL
(
mpx
.
num_socket_managers
(),
3u
);
apply_updates
();
alice
->
send
(
"hello bob"
);
CHECK_EQ
(
mpx
.
num_socket_managers
(),
3u
);
THEN
(
"the multiplexer runs callbacks on socket activity"
)
{
alice
->
send
(
"Hello Bob!"
);
alice
->
register_writing
();
alice
->
register_writing
();
exhaust
();
exhaust
();
CAF_CHECK_EQUAL
(
bob
->
receive
(),
"hello bob"
);
CHECK_EQ
(
bob
->
receive
(),
"Hello Bob!"
);
}
}
}
}
mpx
.
shutdown
();
}
}
CAF_TEST
(
shutdown
)
{
SCENARIO
(
"a multiplexer terminates its thread after shutting down"
)
{
std
::
mutex
m
;
GIVEN
(
"a multiplexer running in its own thread and some socket managers"
)
{
std
::
condition_variable
cv
;
init
();
bool
thread_id_set
=
false
;
auto
go_time
=
std
::
make_shared
<
barrier
>
(
2
);
auto
run_mpx
=
[
&
]
{
auto
mpx_thread
=
std
::
thread
{[
this
,
go_time
]
{
{
std
::
unique_lock
<
std
::
mutex
>
guard
(
m
);
mpx
.
set_thread_id
();
mpx
.
set_thread_id
();
thread_id_set
=
true
;
go_time
->
arrive_and_wait
();
cv
.
notify_one
();
}
mpx
.
run
();
mpx
.
run
();
};
}
};
CAF_REQUIRE_EQUAL
(
mpx
.
init
(),
none
);
go_time
->
arrive_and_wait
(
);
auto
sockets
=
unbox
(
make_stream_socket_pair
());
auto
[
alice_fd
,
bob_fd
]
=
unbox
(
make_stream_socket_pair
());
auto
alice
=
make_counted
<
dummy_manager
>
(
manager_count
,
sockets
.
first
,
&
mpx
);
auto
alice
=
make_manager
(
alice_fd
,
"Alice"
);
auto
bob
=
make_counted
<
dummy_manager
>
(
manager_count
,
sockets
.
second
,
&
mpx
);
auto
bob
=
make_manager
(
bob_fd
,
"Bob"
);
alice
->
register_reading
();
alice
->
register_reading
();
bob
->
register_reading
();
bob
->
register_reading
();
CAF_REQUIRE_EQUAL
(
mpx
.
num_socket_managers
(),
3u
);
WHEN
(
"calling shutdown on the multiplexer"
)
{
std
::
thread
mpx_thread
{
run_mpx
};
std
::
unique_lock
<
std
::
mutex
>
lk
(
m
);
cv
.
wait
(
lk
,
[
&
]
{
return
thread_id_set
;
});
mpx
.
shutdown
();
mpx
.
shutdown
();
THEN
(
"the thread terminates and all socket managers get shut down"
)
{
mpx_thread
.
join
();
mpx_thread
.
join
();
CHECK
(
alice
->
read_closed
());
CHECK
(
bob
->
read_closed
());
}
}
}
}
SCENARIO
(
"a multiplexer allows managers to perform socket handovers"
)
{
GIVEN
(
"an initialized multiplexer"
)
{
init
();
WHEN
(
"socket manager triggers a handover"
)
{
auto
[
alice_fd
,
bob_fd
]
=
unbox
(
make_stream_socket_pair
());
auto
alice
=
make_manager
(
alice_fd
,
"Alice"
);
auto
bob
=
make_manager
(
bob_fd
,
"Bob"
);
alice
->
register_reading
();
bob
->
register_reading
();
apply_updates
();
CHECK_EQ
(
mpx
.
num_socket_managers
(),
3u
);
THEN
(
"the multiplexer swaps out the socket managers for the socket"
)
{
alice
->
send
(
"Hello Bob!"
);
alice
->
register_writing
();
exhaust
();
CHECK_EQ
(
bob
->
receive
(),
"Hello Bob!"
);
bob
->
trigger_handover
=
true
;
alice
->
send
(
"Hello Carl!"
);
alice
->
register_writing
();
bob
->
register_reading
();
exhaust
();
CHECK_EQ
(
bob
->
receive
(),
""
);
CHECK_EQ
(
bob
->
handle
(),
invalid_socket
);
if
(
CHECK_NE
(
bob
->
next
,
nullptr
))
{
auto
carl
=
bob
->
next
;
CHECK_EQ
(
carl
->
handle
(),
socket
{
bob_fd
});
carl
->
register_reading
();
exhaust
();
CHECK_EQ
(
carl
->
name
,
"Carl"
);
CHECK_EQ
(
carl
->
receive
(),
"Hello Carl!"
);
}
}
}
}
}
}
CAF_TEST_FIXTURE_SCOPE_END
()
END_FIXTURE_SCOPE
()
libcaf_net/test/net-test.hpp
View file @
f1a3b267
...
@@ -148,3 +148,31 @@ private:
...
@@ -148,3 +148,31 @@ private:
caf
::
error
abort_reason_
;
caf
::
error
abort_reason_
;
};
};
// Drop-in replacement for std::barrier (based on the TS API as of 2020).
class
barrier
{
public:
explicit
barrier
(
ptrdiff_t
num_threads
)
:
num_threads_
(
num_threads
),
count_
(
0
)
{
// nop
}
void
arrive_and_wait
()
{
std
::
unique_lock
<
std
::
mutex
>
guard
{
mx_
};
auto
new_count
=
++
count_
;
if
(
new_count
==
num_threads_
)
{
cv_
.
notify_all
();
}
else
if
(
new_count
>
num_threads_
)
{
count_
=
1
;
cv_
.
wait
(
guard
,
[
this
]
{
return
count_
.
load
()
==
num_threads_
;
});
}
else
{
cv_
.
wait
(
guard
,
[
this
]
{
return
count_
.
load
()
==
num_threads_
;
});
}
}
private:
ptrdiff_t
num_threads_
;
std
::
mutex
mx_
;
std
::
atomic
<
ptrdiff_t
>
count_
;
std
::
condition_variable
cv_
;
};
libcaf_net/test/net/actor_shell.cpp
View file @
f1a3b267
...
@@ -163,6 +163,7 @@ struct fixture : host_fixture, test_coordinator_fixture<> {
...
@@ -163,6 +163,7 @@ struct fixture : host_fixture, test_coordinator_fixture<> {
if
(
!
predicate
())
if
(
!
predicate
())
return
;
return
;
for
(
size_t
i
=
0
;
i
<
1000
;
++
i
)
{
for
(
size_t
i
=
0
;
i
<
1000
;
++
i
)
{
mpx
.
apply_updates
();
mpx
.
poll_once
(
false
);
mpx
.
poll_once
(
false
);
byte
tmp
[
1024
];
byte
tmp
[
1024
];
auto
bytes
=
read
(
self_socket_guard
.
socket
(),
make_span
(
tmp
,
1024
));
auto
bytes
=
read
(
self_socket_guard
.
socket
(),
make_span
(
tmp
,
1024
));
...
...
libcaf_net/test/net/length_prefix_framing.cpp
View file @
f1a3b267
...
@@ -153,22 +153,24 @@ SCENARIO("calling suspend_reading removes message apps temporarily") {
...
@@ -153,22 +153,24 @@ SCENARIO("calling suspend_reading removes message apps temporarily") {
}
}
}};
}};
net
::
multiplexer
mpx
{
nullptr
};
net
::
multiplexer
mpx
{
nullptr
};
mpx
.
set_thread_id
();
if
(
auto
err
=
mpx
.
init
())
if
(
auto
err
=
mpx
.
init
())
FAIL
(
"mpx.init failed: "
<<
err
);
FAIL
(
"mpx.init failed: "
<<
err
);
mpx
.
set_thread_id
();
mpx
.
apply_updates
();
REQUIRE_EQ
(
mpx
.
num_socket_managers
(),
1u
);
REQUIRE_EQ
(
mpx
.
num_socket_managers
(),
1u
);
if
(
auto
err
=
net
::
nonblocking
(
fd2
,
true
))
if
(
auto
err
=
net
::
nonblocking
(
fd2
,
true
))
CAF_FAIL
(
"nonblocking returned an error: "
<<
err
);
CAF_FAIL
(
"nonblocking returned an error: "
<<
err
);
auto
mgr
=
net
::
make_socket_manager
<
app
<
true
>
,
net
::
length_prefix_framing
,
auto
mgr
=
net
::
make_socket_manager
<
app
<
true
>
,
net
::
length_prefix_framing
,
net
::
stream_transport
>
(
fd2
,
&
mpx
);
net
::
stream_transport
>
(
fd2
,
&
mpx
);
CHECK_EQ
(
mgr
->
init
(
settings
{}),
none
);
CHECK_EQ
(
mgr
->
init
(
settings
{}),
none
);
mpx
.
apply_updates
();
REQUIRE_EQ
(
mpx
.
num_socket_managers
(),
2u
);
REQUIRE_EQ
(
mpx
.
num_socket_managers
(),
2u
);
CHECK_EQ
(
m
gr
->
mask
(
),
net
::
operation
::
read
);
CHECK_EQ
(
m
px
.
mask_of
(
mgr
),
net
::
operation
::
read
);
auto
&
state
=
mgr
->
top_layer
();
auto
&
state
=
mgr
->
top_layer
();
WHEN
(
"the app calls suspend_reading"
)
{
WHEN
(
"the app calls suspend_reading"
)
{
while
(
mpx
.
num_socket_managers
()
>
1u
)
while
(
mpx
.
num_socket_managers
()
>
1u
)
mpx
.
poll_once
(
true
);
mpx
.
poll_once
(
true
);
CHECK_EQ
(
m
gr
->
mask
(
),
net
::
operation
::
none
);
CHECK_EQ
(
m
px
.
mask_of
(
mgr
),
net
::
operation
::
none
);
if
(
CHECK_EQ
(
state
.
inputs
.
size
(),
3u
))
{
if
(
CHECK_EQ
(
state
.
inputs
.
size
(),
3u
))
{
CHECK_EQ
(
state
.
inputs
[
0
],
"first"
);
CHECK_EQ
(
state
.
inputs
[
0
],
"first"
);
CHECK_EQ
(
state
.
inputs
[
1
],
"second"
);
CHECK_EQ
(
state
.
inputs
[
1
],
"second"
);
...
@@ -176,7 +178,8 @@ SCENARIO("calling suspend_reading removes message apps temporarily") {
...
@@ -176,7 +178,8 @@ SCENARIO("calling suspend_reading removes message apps temporarily") {
}
}
THEN
(
"users can resume it via continue_reading "
)
{
THEN
(
"users can resume it via continue_reading "
)
{
mgr
->
continue_reading
();
mgr
->
continue_reading
();
CHECK_EQ
(
mgr
->
mask
(),
net
::
operation
::
read
);
mpx
.
apply_updates
();
CHECK_EQ
(
mpx
.
mask_of
(
mgr
),
net
::
operation
::
read
);
while
(
mpx
.
num_socket_managers
()
>
1u
)
while
(
mpx
.
num_socket_managers
()
>
1u
)
mpx
.
poll_once
(
true
);
mpx
.
poll_once
(
true
);
if
(
CHECK_EQ
(
state
.
inputs
.
size
(),
5u
))
{
if
(
CHECK_EQ
(
state
.
inputs
.
size
(),
5u
))
{
...
...
libcaf_net/test/net/producer_adapter.cpp
View file @
f1a3b267
...
@@ -89,8 +89,7 @@ public:
...
@@ -89,8 +89,7 @@ public:
template
<
class
LowerLayerPtr
>
template
<
class
LowerLayerPtr
>
void
abort
(
LowerLayerPtr
,
const
error
&
reason
)
{
void
abort
(
LowerLayerPtr
,
const
error
&
reason
)
{
if
(
reason
==
caf
::
sec
::
socket_disconnected
if
(
reason
==
caf
::
sec
::
socket_disconnected
||
reason
==
caf
::
sec
::
disposed
)
||
reason
==
caf
::
sec
::
discarded
)
adapter_
->
close
();
adapter_
->
close
();
else
else
adapter_
->
abort
(
reason
);
adapter_
->
abort
(
reason
);
...
...
libcaf_net/test/net/typed_actor_shell.cpp
View file @
f1a3b267
...
@@ -166,6 +166,7 @@ struct fixture : host_fixture, test_coordinator_fixture<> {
...
@@ -166,6 +166,7 @@ struct fixture : host_fixture, test_coordinator_fixture<> {
if
(
!
predicate
())
if
(
!
predicate
())
return
;
return
;
for
(
size_t
i
=
0
;
i
<
1000
;
++
i
)
{
for
(
size_t
i
=
0
;
i
<
1000
;
++
i
)
{
mpx
.
apply_updates
();
mpx
.
poll_once
(
false
);
mpx
.
poll_once
(
false
);
byte
tmp
[
1024
];
byte
tmp
[
1024
];
auto
bytes
=
read
(
self_socket_guard
.
socket
(),
make_span
(
tmp
,
1024
));
auto
bytes
=
read
(
self_socket_guard
.
socket
(),
make_span
(
tmp
,
1024
));
...
...
libcaf_net/test/stream_transport.cpp
View file @
f1a3b267
...
@@ -6,8 +6,7 @@
...
@@ -6,8 +6,7 @@
#include "caf/net/stream_transport.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "net-test.hpp"
#include "caf/test/dsl.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/binary_serializer.hpp"
...
@@ -26,6 +25,7 @@ using namespace caf;
...
@@ -26,6 +25,7 @@ using namespace caf;
using
namespace
caf
::
net
;
using
namespace
caf
::
net
;
namespace
{
namespace
{
constexpr
string_view
hello_manager
=
"hello manager!"
;
constexpr
string_view
hello_manager
=
"hello manager!"
;
struct
fixture
:
test_coordinator_fixture
<>
,
host_fixture
{
struct
fixture
:
test_coordinator_fixture
<>
,
host_fixture
{
...
@@ -36,15 +36,16 @@ struct fixture : test_coordinator_fixture<>, host_fixture {
...
@@ -36,15 +36,16 @@ struct fixture : test_coordinator_fixture<>, host_fixture {
recv_buf
(
1024
),
recv_buf
(
1024
),
shared_recv_buf
{
std
::
make_shared
<
byte_buffer
>
()},
shared_recv_buf
{
std
::
make_shared
<
byte_buffer
>
()},
shared_send_buf
{
std
::
make_shared
<
byte_buffer
>
()}
{
shared_send_buf
{
std
::
make_shared
<
byte_buffer
>
()}
{
if
(
auto
err
=
mpx
.
init
())
CAF_FAIL
(
"mpx.init failed: "
<<
err
);
mpx
.
set_thread_id
();
mpx
.
set_thread_id
();
CAF_CHECK_EQUAL
(
mpx
.
num_socket_managers
(),
1u
);
mpx
.
apply_updates
();
if
(
auto
err
=
mpx
.
init
())
FAIL
(
"mpx.init failed: "
<<
err
);
REQUIRE_EQ
(
mpx
.
num_socket_managers
(),
1u
);
auto
sockets
=
unbox
(
make_stream_socket_pair
());
auto
sockets
=
unbox
(
make_stream_socket_pair
());
send_socket_guard
.
reset
(
sockets
.
first
);
send_socket_guard
.
reset
(
sockets
.
first
);
recv_socket_guard
.
reset
(
sockets
.
second
);
recv_socket_guard
.
reset
(
sockets
.
second
);
if
(
auto
err
=
nonblocking
(
recv_socket_guard
.
socket
(),
true
))
if
(
auto
err
=
nonblocking
(
recv_socket_guard
.
socket
(),
true
))
CAF_
FAIL
(
"nonblocking returned an error: "
<<
err
);
FAIL
(
"nonblocking returned an error: "
<<
err
);
}
}
bool
handle_io_event
()
override
{
bool
handle_io_event
()
override
{
...
@@ -82,7 +83,7 @@ public:
...
@@ -82,7 +83,7 @@ public:
template
<
class
ParentPtr
>
template
<
class
ParentPtr
>
bool
prepare_send
(
ParentPtr
parent
)
{
bool
prepare_send
(
ParentPtr
parent
)
{
CAF_
MESSAGE
(
"prepare_send called"
);
MESSAGE
(
"prepare_send called"
);
auto
&
buf
=
parent
->
output_buffer
();
auto
&
buf
=
parent
->
output_buffer
();
auto
data
=
as_bytes
(
make_span
(
hello_manager
));
auto
data
=
as_bytes
(
make_span
(
hello_manager
));
buf
.
insert
(
buf
.
end
(),
data
.
begin
(),
data
.
end
());
buf
.
insert
(
buf
.
end
(),
data
.
begin
(),
data
.
end
());
...
@@ -91,44 +92,30 @@ public:
...
@@ -91,44 +92,30 @@ public:
template
<
class
ParentPtr
>
template
<
class
ParentPtr
>
bool
done_sending
(
ParentPtr
)
{
bool
done_sending
(
ParentPtr
)
{
CAF_
MESSAGE
(
"done_sending called"
);
MESSAGE
(
"done_sending called"
);
return
true
;
return
true
;
}
}
template
<
class
ParentPtr
>
template
<
class
ParentPtr
>
void
continue_reading
(
ParentPtr
)
{
void
continue_reading
(
ParentPtr
)
{
CAF_
FAIL
(
"continue_reading called"
);
FAIL
(
"continue_reading called"
);
}
}
template
<
class
ParentPtr
>
template
<
class
ParentPtr
>
size_t
consume
(
ParentPtr
,
span
<
const
byte
>
data
,
span
<
const
byte
>
)
{
size_t
consume
(
ParentPtr
,
span
<
const
byte
>
data
,
span
<
const
byte
>
)
{
recv_buf_
->
clear
();
recv_buf_
->
clear
();
recv_buf_
->
insert
(
recv_buf_
->
begin
(),
data
.
begin
(),
data
.
end
());
recv_buf_
->
insert
(
recv_buf_
->
begin
(),
data
.
begin
(),
data
.
end
());
CAF_MESSAGE
(
"Received "
<<
recv_buf_
->
size
()
MESSAGE
(
"Received "
<<
recv_buf_
->
size
()
<<
" bytes in dummy_application"
);
<<
" bytes in dummy_application"
);
return
recv_buf_
->
size
();
return
recv_buf_
->
size
();
}
}
template
<
class
ParentPtr
>
void
resolve
(
ParentPtr
parent
,
string_view
path
,
const
actor
&
listener
)
{
actor_id
aid
=
42
;
auto
hid
=
string_view
(
"0011223344556677889900112233445566778899"
);
auto
nid
=
unbox
(
make_node_id
(
42
,
hid
));
actor_config
cfg
;
endpoint_manager_ptr
ptr
{
&
parent
->
manager
()};
auto
p
=
make_actor
<
actor_proxy_impl
,
strong_actor_ptr
>
(
aid
,
nid
,
&
parent
->
system
(),
cfg
,
std
::
move
(
ptr
));
anon_send
(
listener
,
resolve_atom_v
,
std
::
string
{
path
.
begin
(),
path
.
end
()},
p
);
}
static
void
handle_error
(
sec
code
)
{
static
void
handle_error
(
sec
code
)
{
CAF_
FAIL
(
"handle_error called with "
<<
CAF_ARG
(
code
));
FAIL
(
"handle_error called with "
<<
CAF_ARG
(
code
));
}
}
template
<
class
ParentPtr
>
template
<
class
ParentPtr
>
static
void
abort
(
ParentPtr
,
const
error
&
reason
)
{
static
void
abort
(
ParentPtr
,
const
error
&
reason
)
{
CAF_
FAIL
(
"abort called with "
<<
CAF_ARG
(
reason
));
FAIL
(
"abort called with "
<<
CAF_ARG
(
reason
));
}
}
private:
private:
...
@@ -138,20 +125,20 @@ private:
...
@@ -138,20 +125,20 @@ private:
}
// namespace
}
// namespace
CAF_TEST_FIXTURE_SCOPE
(
endpoint_manager_tests
,
fixture
)
BEGIN_FIXTURE_SCOPE
(
fixture
)
CAF_TEST
(
receive
)
{
CAF_TEST
(
receive
)
{
auto
mgr
=
make_socket_manager
<
dummy_application
,
stream_transport
>
(
auto
mgr
=
make_socket_manager
<
dummy_application
,
stream_transport
>
(
recv_socket_guard
.
release
(),
&
mpx
,
shared_recv_buf
,
shared_send_buf
);
recv_socket_guard
.
release
(),
&
mpx
,
shared_recv_buf
,
shared_send_buf
);
C
AF_CHECK_EQUAL
(
mgr
->
init
(
config
),
none
);
C
HECK_EQ
(
mgr
->
init
(
config
),
none
);
CAF_CHECK_EQUAL
(
mpx
.
num_socket_managers
(),
2u
);
mpx
.
apply_updates
(
);
C
AF_CHECK_EQUAL
(
static_cast
<
size_t
>
(
C
HECK_EQ
(
mpx
.
num_socket_managers
(),
2u
);
write
(
send_socket_guard
.
socket
(),
CHECK_EQ
(
static_cast
<
size_t
>
(
write
(
send_socket_guard
.
socket
(),
as_bytes
(
make_span
(
hello_manager
)))),
as_bytes
(
make_span
(
hello_manager
)))),
hello_manager
.
size
());
hello_manager
.
size
());
CAF_
MESSAGE
(
"wrote "
<<
hello_manager
.
size
()
<<
" bytes."
);
MESSAGE
(
"wrote "
<<
hello_manager
.
size
()
<<
" bytes."
);
run
();
run
();
C
AF_CHECK_EQUAL
(
string_view
(
reinterpret_cast
<
char
*>
(
shared_recv_buf
->
data
()),
C
HECK_EQ
(
string_view
(
reinterpret_cast
<
char
*>
(
shared_recv_buf
->
data
()),
shared_recv_buf
->
size
()),
shared_recv_buf
->
size
()),
hello_manager
);
hello_manager
);
}
}
...
@@ -159,18 +146,20 @@ CAF_TEST(receive) {
...
@@ -159,18 +146,20 @@ CAF_TEST(receive) {
CAF_TEST
(
send
)
{
CAF_TEST
(
send
)
{
auto
mgr
=
make_socket_manager
<
dummy_application
,
stream_transport
>
(
auto
mgr
=
make_socket_manager
<
dummy_application
,
stream_transport
>
(
recv_socket_guard
.
release
(),
&
mpx
,
shared_recv_buf
,
shared_send_buf
);
recv_socket_guard
.
release
(),
&
mpx
,
shared_recv_buf
,
shared_send_buf
);
CAF_CHECK_EQUAL
(
mgr
->
init
(
config
),
none
);
CHECK_EQ
(
mgr
->
init
(
config
),
none
);
CAF_CHECK_EQUAL
(
mpx
.
num_socket_managers
(),
2u
);
mpx
.
apply_updates
();
CHECK_EQ
(
mpx
.
num_socket_managers
(),
2u
);
mgr
->
register_writing
();
mgr
->
register_writing
();
mpx
.
apply_updates
();
while
(
handle_io_event
())
while
(
handle_io_event
())
;
;
recv_buf
.
resize
(
hello_manager
.
size
());
recv_buf
.
resize
(
hello_manager
.
size
());
auto
res
=
read
(
send_socket_guard
.
socket
(),
make_span
(
recv_buf
));
auto
res
=
read
(
send_socket_guard
.
socket
(),
make_span
(
recv_buf
));
CAF_
MESSAGE
(
"received "
<<
res
<<
" bytes"
);
MESSAGE
(
"received "
<<
res
<<
" bytes"
);
recv_buf
.
resize
(
res
);
recv_buf
.
resize
(
res
);
C
AF_CHECK_EQUAL
(
string_view
(
reinterpret_cast
<
char
*>
(
recv_buf
.
data
()),
C
HECK_EQ
(
string_view
(
reinterpret_cast
<
char
*>
(
recv_buf
.
data
()),
recv_buf
.
size
()),
recv_buf
.
size
()),
hello_manager
);
hello_manager
);
}
}
CAF_TEST_FIXTURE_SCOPE_END
()
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