Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
folly
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
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
Libraries
folly
Commits
07a7d5c4
Commit
07a7d5c4
authored
Oct 16, 2018
by
Kirk Shoop
Committed by
Facebook Github Bot
Oct 17, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add new thread cancellation test
fbshipit-source-id: 9d12db7b6b8ea4bc2ba87de78a35046884c30e3d
parent
ff57e8ff
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
630 additions
and
150 deletions
+630
-150
folly/experimental/pushmi/include/pushmi.h
folly/experimental/pushmi/include/pushmi.h
+45
-42
folly/experimental/pushmi/include/pushmi/boosters.h
folly/experimental/pushmi/include/pushmi/boosters.h
+4
-4
folly/experimental/pushmi/include/pushmi/concepts.h
folly/experimental/pushmi/include/pushmi/concepts.h
+13
-13
folly/experimental/pushmi/include/pushmi/entangle.h
folly/experimental/pushmi/include/pushmi/entangle.h
+210
-0
folly/experimental/pushmi/include/pushmi/extension_points.h
folly/experimental/pushmi/include/pushmi/extension_points.h
+11
-11
folly/experimental/pushmi/include/pushmi/flow_single.h
folly/experimental/pushmi/include/pushmi/flow_single.h
+14
-14
folly/experimental/pushmi/include/pushmi/trampoline.h
folly/experimental/pushmi/include/pushmi/trampoline.h
+3
-0
folly/experimental/pushmi/test/FlowTest.cpp
folly/experimental/pushmi/test/FlowTest.cpp
+330
-66
No files found.
folly/experimental/pushmi/include/pushmi.h
View file @
07a7d5c4
...
@@ -1330,9 +1330,9 @@ void set_stopping(S& s) noexcept(noexcept(s.stopping())) {
...
@@ -1330,9 +1330,9 @@ void set_stopping(S& s) noexcept(noexcept(s.stopping())) {
s
.
stopping
();
s
.
stopping
();
}
}
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
(
requires
requires
(
std
::
declval
<
S
&>
().
starting
(
std
::
declval
<
Up
&
>
())))
(
requires
requires
(
std
::
declval
<
S
&>
().
starting
(
std
::
declval
<
Up
>
())))
void
set_starting
(
S
&
s
,
Up
&
up
)
noexcept
(
noexcept
(
s
.
starting
(
up
)))
{
void
set_starting
(
S
&
s
,
Up
up
)
noexcept
(
noexcept
(
s
.
starting
(
std
::
move
(
up
)
)))
{
s
.
starting
(
up
);
s
.
starting
(
std
::
move
(
up
)
);
}
}
PUSHMI_TEMPLATE
(
class
SD
,
class
Out
)
PUSHMI_TEMPLATE
(
class
SD
,
class
Out
)
...
@@ -1404,10 +1404,10 @@ void set_stopping(std::reference_wrapper<S> s) noexcept(
...
@@ -1404,10 +1404,10 @@ void set_stopping(std::reference_wrapper<S> s) noexcept(
set_stopping
(
s
.
get
());
set_stopping
(
s
.
get
());
}
}
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
(
requires
requires
(
set_starting
(
std
::
declval
<
S
&>
(),
std
::
declval
<
Up
&
>
())
))
(
requires
requires
(
set_starting
(
std
::
declval
<
S
&>
(),
std
::
declval
<
Up
>
())
))
void
set_starting
(
std
::
reference_wrapper
<
S
>
s
,
Up
&
up
)
noexcept
(
void
set_starting
(
std
::
reference_wrapper
<
S
>
s
,
Up
up
)
noexcept
(
noexcept
(
set_starting
(
s
.
get
(),
up
)))
{
noexcept
(
set_starting
(
s
.
get
(),
std
::
move
(
up
)
)))
{
set_starting
(
s
.
get
(),
up
);
set_starting
(
s
.
get
(),
std
::
move
(
up
)
);
}
}
PUSHMI_TEMPLATE
(
class
SD
,
class
Out
)
PUSHMI_TEMPLATE
(
class
SD
,
class
Out
)
(
requires
requires
(
submit
(
std
::
declval
<
SD
&>
(),
std
::
declval
<
Out
>
())
))
(
requires
requires
(
submit
(
std
::
declval
<
SD
&>
(),
std
::
declval
<
Out
>
())
))
...
@@ -1485,13 +1485,13 @@ struct set_stopping_fn {
...
@@ -1485,13 +1485,13 @@ struct set_stopping_fn {
struct
set_starting_fn
{
struct
set_starting_fn
{
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
(
requires
requires
(
(
requires
requires
(
set_starting
(
std
::
declval
<
S
&>
(),
std
::
declval
<
Up
&
>
()),
set_starting
(
std
::
declval
<
S
&>
(),
std
::
declval
<
Up
>
()),
set_error
(
std
::
declval
<
S
&>
(),
std
::
current_exception
())
set_error
(
std
::
declval
<
S
&>
(),
std
::
current_exception
())
))
))
void
operator
()(
S
&&
s
,
Up
&
up
)
const
void
operator
()(
S
&&
s
,
Up
up
)
const
noexcept
(
noexcept
(
set_starting
(
s
,
up
)))
{
noexcept
(
noexcept
(
set_starting
(
s
,
std
::
move
(
up
)
)))
{
try
{
try
{
set_starting
(
s
,
up
);
set_starting
(
s
,
std
::
move
(
up
)
);
}
catch
(...)
{
}
catch
(...)
{
set_error
(
s
,
std
::
current_exception
());
set_error
(
s
,
std
::
current_exception
());
}
}
...
@@ -1961,13 +1961,13 @@ PUSHMI_CONCEPT_DEF(
...
@@ -1961,13 +1961,13 @@ PUSHMI_CONCEPT_DEF(
// silent does not really make sense, but cannot test for
// silent does not really make sense, but cannot test for
// None without the error type, use is_none<> to strengthen
// None without the error type, use is_none<> to strengthen
// requirements
// requirements
PUSHMI_CONCEPT_DEF
(
PUSHMI_CONCEPT_DEF
(
template
(
class
D
,
class
...
PropertyN
)
template
(
class
D
,
class
...
PropertyN
)
(
concept
Sender
)(
D
,
PropertyN
...),
(
concept
Sender
)(
D
,
PropertyN
...),
SemiMovable
<
D
>
&&
SemiMovable
<
D
>
&&
None
<
D
>
&&
None
<
D
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
is_sender_v
<
D
>
is_sender_v
<
D
>
);
);
...
@@ -1999,19 +1999,19 @@ PUSHMI_CONCEPT_DEF(
...
@@ -1999,19 +1999,19 @@ PUSHMI_CONCEPT_DEF(
PUSHMI_CONCEPT_DEF
(
PUSHMI_CONCEPT_DEF
(
template
(
template
(
class
N
,
class
N
,
class
Up
,
class
Up
,
class
PE
=
std
::
exception_ptr
,
class
PE
=
std
::
exception_ptr
,
class
E
=
PE
)
class
E
=
PE
)
(
concept
FlowNoneReceiver
)(
N
,
Up
,
PE
,
E
),
(
concept
FlowNoneReceiver
)(
N
,
Up
,
PE
,
E
),
requires
(
N
&
n
,
Up
&
up
)
(
requires
(
N
&
n
,
Up
&
&
up
)
(
::
pushmi
::
set_starting
(
n
,
up
)
::
pushmi
::
set_starting
(
n
,
(
Up
&&
)
up
)
)
&&
)
&&
FlowReceiver
<
N
>
&&
FlowReceiver
<
N
>
&&
Receiver
<
Up
>
&&
Receiver
<
Up
>
&&
SemiMovable
<
PE
>
&&
SemiMovable
<
PE
>
&&
SemiMovable
<
E
>
&&
SemiMovable
<
E
>
&&
NoneReceiver
<
Up
,
PE
>
&&
NoneReceiver
<
Up
,
PE
>
&&
NoneReceiver
<
N
,
E
>
NoneReceiver
<
N
,
E
>
);
);
...
@@ -2023,7 +2023,7 @@ PUSHMI_CONCEPT_DEF(
...
@@ -2023,7 +2023,7 @@ PUSHMI_CONCEPT_DEF(
class
PE
=
std
::
exception_ptr
,
class
PE
=
std
::
exception_ptr
,
class
E
=
PE
)
class
E
=
PE
)
(
concept
FlowSingleReceiver
)(
S
,
Up
,
T
,
PE
,
E
),
(
concept
FlowSingleReceiver
)(
S
,
Up
,
T
,
PE
,
E
),
SingleReceiver
<
S
,
T
,
E
>
&&
SingleReceiver
<
S
,
T
,
E
>
&&
FlowNoneReceiver
<
S
,
Up
,
PE
,
E
>
FlowNoneReceiver
<
S
,
Up
,
PE
,
E
>
);
);
...
@@ -2035,7 +2035,7 @@ PUSHMI_CONCEPT_DEF(
...
@@ -2035,7 +2035,7 @@ PUSHMI_CONCEPT_DEF(
class
PE
=
std
::
exception_ptr
,
class
PE
=
std
::
exception_ptr
,
class
E
=
PE
)
class
E
=
PE
)
(
concept
FlowManyReceiver
)(
S
,
Up
,
T
,
PE
,
E
),
(
concept
FlowManyReceiver
)(
S
,
Up
,
T
,
PE
,
E
),
ManyReceiver
<
S
,
T
,
E
>
&&
ManyReceiver
<
S
,
T
,
E
>
&&
FlowSingleReceiver
<
S
,
Up
,
T
,
PE
,
E
>
FlowSingleReceiver
<
S
,
Up
,
T
,
PE
,
E
>
);
);
...
@@ -2065,9 +2065,9 @@ PUSHMI_CONCEPT_DEF(
...
@@ -2065,9 +2065,9 @@ PUSHMI_CONCEPT_DEF(
::
pushmi
::
now
(
d
),
::
pushmi
::
now
(
d
),
requires_
<
Regular
<
decltype
(
::
pushmi
::
now
(
d
))>
>
requires_
<
Regular
<
decltype
(
::
pushmi
::
now
(
d
))>
>
)
&&
)
&&
Sender
<
D
>
&&
Sender
<
D
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
Time
<
D
>
&&
Time
<
D
>
&&
None
<
D
>
None
<
D
>
);
);
...
@@ -2077,7 +2077,7 @@ PUSHMI_CONCEPT_DEF(
...
@@ -2077,7 +2077,7 @@ PUSHMI_CONCEPT_DEF(
requires
(
D
&
d
,
S
&&
s
)
(
requires
(
D
&
d
,
S
&&
s
)
(
::
pushmi
::
submit
(
d
,
::
pushmi
::
now
(
d
),
(
S
&&
)
s
)
::
pushmi
::
submit
(
d
,
::
pushmi
::
now
(
d
),
(
S
&&
)
s
)
)
&&
)
&&
TimeSender
<
D
>
&&
TimeSender
<
D
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
Receiver
<
S
>
Receiver
<
S
>
);
);
...
@@ -2188,7 +2188,7 @@ struct ignoreStpF {
...
@@ -2188,7 +2188,7 @@ struct ignoreStpF {
struct
ignoreStrtF
{
struct
ignoreStrtF
{
template
<
class
Up
>
template
<
class
Up
>
void
operator
()(
Up
&
)
{}
void
operator
()(
Up
&
&
)
{}
};
};
...
@@ -2240,10 +2240,10 @@ struct passDStpF {
...
@@ -2240,10 +2240,10 @@ struct passDStpF {
struct
passDStrtF
{
struct
passDStrtF
{
PUSHMI_TEMPLATE
(
class
Up
,
class
Data
)
PUSHMI_TEMPLATE
(
class
Up
,
class
Data
)
(
requires
requires
(
(
requires
requires
(
::
pushmi
::
set_starting
(
std
::
declval
<
Data
&>
(),
std
::
declval
<
Up
&
>
())
::
pushmi
::
set_starting
(
std
::
declval
<
Data
&>
(),
std
::
declval
<
Up
>
())
)
&&
Receiver
<
Data
>
)
)
&&
Receiver
<
Data
>
)
void
operator
()(
Data
&
out
,
Up
&
up
)
const
{
void
operator
()(
Data
&
out
,
Up
up
)
const
{
::
pushmi
::
set_starting
(
out
,
up
);
::
pushmi
::
set_starting
(
out
,
std
::
move
(
up
)
);
}
}
};
};
...
@@ -4045,13 +4045,13 @@ class flow_single<V, PE, E> {
...
@@ -4045,13 +4045,13 @@ class flow_single<V, PE, E> {
static
void
s_error
(
data
&
,
E
)
noexcept
{
std
::
terminate
();
}
static
void
s_error
(
data
&
,
E
)
noexcept
{
std
::
terminate
();
}
static
void
s_value
(
data
&
,
V
)
{}
static
void
s_value
(
data
&
,
V
)
{}
static
void
s_stopping
(
data
&
)
noexcept
{}
static
void
s_stopping
(
data
&
)
noexcept
{}
static
void
s_starting
(
data
&
,
any_none
<
PE
>
&
)
{}
static
void
s_starting
(
data
&
,
any_none
<
PE
>
)
{}
void
(
*
op_
)(
data
&
,
data
*
)
=
vtable
::
s_op
;
void
(
*
op_
)(
data
&
,
data
*
)
=
vtable
::
s_op
;
void
(
*
done_
)(
data
&
)
=
vtable
::
s_done
;
void
(
*
done_
)(
data
&
)
=
vtable
::
s_done
;
void
(
*
error_
)(
data
&
,
E
)
noexcept
=
vtable
::
s_error
;
void
(
*
error_
)(
data
&
,
E
)
noexcept
=
vtable
::
s_error
;
void
(
*
value_
)(
data
&
,
V
)
=
vtable
::
s_value
;
void
(
*
value_
)(
data
&
,
V
)
=
vtable
::
s_value
;
void
(
*
stopping_
)(
data
&
)
noexcept
=
vtable
::
s_stopping
;
void
(
*
stopping_
)(
data
&
)
noexcept
=
vtable
::
s_stopping
;
void
(
*
starting_
)(
data
&
,
any_none
<
PE
>
&
)
=
vtable
::
s_starting
;
void
(
*
starting_
)(
data
&
,
any_none
<
PE
>
)
=
vtable
::
s_starting
;
};
};
static
constexpr
vtable
const
noop_
{};
static
constexpr
vtable
const
noop_
{};
vtable
const
*
vptr_
=
&
noop_
;
vtable
const
*
vptr_
=
&
noop_
;
...
@@ -4075,8 +4075,8 @@ class flow_single<V, PE, E> {
...
@@ -4075,8 +4075,8 @@ class flow_single<V, PE, E> {
static
void
stopping
(
data
&
src
)
noexcept
{
static
void
stopping
(
data
&
src
)
noexcept
{
::
pushmi
::
set_stopping
(
*
static_cast
<
Wrapped
*>
(
src
.
pobj_
));
::
pushmi
::
set_stopping
(
*
static_cast
<
Wrapped
*>
(
src
.
pobj_
));
}
}
static
void
starting
(
data
&
src
,
any_none
<
PE
>
&
up
)
{
static
void
starting
(
data
&
src
,
any_none
<
PE
>
up
)
{
::
pushmi
::
set_starting
(
*
static_cast
<
Wrapped
*>
(
src
.
pobj_
),
up
);
::
pushmi
::
set_starting
(
*
static_cast
<
Wrapped
*>
(
src
.
pobj_
),
std
::
move
(
up
)
);
}
}
};
};
static
const
vtable
vtbl
{
s
::
op
,
s
::
done
,
s
::
error
,
s
::
value
,
s
::
stopping
,
s
::
starting
};
static
const
vtable
vtbl
{
s
::
op
,
s
::
done
,
s
::
error
,
s
::
value
,
s
::
stopping
,
s
::
starting
};
...
@@ -4106,8 +4106,8 @@ class flow_single<V, PE, E> {
...
@@ -4106,8 +4106,8 @@ class flow_single<V, PE, E> {
static
void
stopping
(
data
&
src
)
noexcept
{
static
void
stopping
(
data
&
src
)
noexcept
{
::
pushmi
::
set_stopping
(
*
static_cast
<
Wrapped
*>
((
void
*
)
src
.
buffer_
));
::
pushmi
::
set_stopping
(
*
static_cast
<
Wrapped
*>
((
void
*
)
src
.
buffer_
));
}
}
static
void
starting
(
data
&
src
,
any_none
<
PE
>
&
up
)
{
static
void
starting
(
data
&
src
,
any_none
<
PE
>
up
)
{
::
pushmi
::
set_starting
(
*
static_cast
<
Wrapped
*>
((
void
*
)
src
.
buffer_
),
up
);
::
pushmi
::
set_starting
(
*
static_cast
<
Wrapped
*>
((
void
*
)
src
.
buffer_
),
std
::
move
(
up
)
);
}
}
};
};
static
const
vtable
vtbl
{
s
::
op
,
s
::
done
,
s
::
error
,
s
::
value
,
s
::
stopping
,
s
::
starting
};
static
const
vtable
vtbl
{
s
::
op
,
s
::
done
,
s
::
error
,
s
::
value
,
s
::
stopping
,
s
::
starting
};
...
@@ -4150,8 +4150,8 @@ public:
...
@@ -4150,8 +4150,8 @@ public:
void
stopping
()
noexcept
{
void
stopping
()
noexcept
{
vptr_
->
stopping_
(
data_
);
vptr_
->
stopping_
(
data_
);
}
}
void
starting
(
any_none
<
PE
>
&
up
)
{
void
starting
(
any_none
<
PE
>
up
)
{
vptr_
->
starting_
(
data_
,
up
);
vptr_
->
starting_
(
data_
,
std
::
move
(
up
)
);
}
}
};
};
...
@@ -4219,9 +4219,9 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
...
@@ -4219,9 +4219,9 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
stpf_
();
stpf_
();
}
}
PUSHMI_TEMPLATE
(
class
Up
)
PUSHMI_TEMPLATE
(
class
Up
)
(
requires
Receiver
<
Up
,
is_none
<>>
&&
Invocable
<
StrtF
&
,
Up
&>
)
(
requires
Receiver
<
Up
,
is_none
<>>
&&
Invocable
<
StrtF
&
,
Up
&
&
>
)
void
starting
(
Up
&
up
)
{
void
starting
(
Up
&
&
up
)
{
strtf_
(
up
);
strtf_
(
(
Up
&&
)
up
);
}
}
};
};
...
@@ -4291,9 +4291,9 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
...
@@ -4291,9 +4291,9 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
stpf_
(
data_
);
stpf_
(
data_
);
}
}
PUSHMI_TEMPLATE
(
class
Up
)
PUSHMI_TEMPLATE
(
class
Up
)
(
requires
Invocable
<
DStrtF
&
,
Data
&
,
Up
&>
)
(
requires
Invocable
<
DStrtF
&
,
Data
&
,
Up
&
&
>
)
void
starting
(
Up
&
up
)
{
void
starting
(
Up
&
&
up
)
{
strtf_
(
data_
,
up
);
strtf_
(
data_
,
(
Up
&&
)
up
);
}
}
};
};
...
@@ -4936,6 +4936,9 @@ class trampoline {
...
@@ -4936,6 +4936,9 @@ class trampoline {
auto
item
=
std
::
move
(
pending
(
pending_store
).
front
());
auto
item
=
std
::
move
(
pending
(
pending_store
).
front
());
pending
(
pending_store
).
pop_front
();
pending
(
pending_store
).
pop_front
();
auto
&
when
=
std
::
get
<
0
>
(
item
);
auto
&
when
=
std
::
get
<
0
>
(
item
);
if
(
when
>
trampoline
<
E
>::
now
())
{
std
::
this_thread
::
sleep_until
(
when
);
}
auto
&
what
=
std
::
get
<
1
>
(
item
);
auto
&
what
=
std
::
get
<
1
>
(
item
);
any_time_executor_ref
<
error_type
,
time_point
>
anythis
{
that
};
any_time_executor_ref
<
error_type
,
time_point
>
anythis
{
that
};
::
pushmi
::
set_value
(
what
,
anythis
);
::
pushmi
::
set_value
(
what
,
anythis
);
...
...
folly/experimental/pushmi/include/pushmi/boosters.h
View file @
07a7d5c4
...
@@ -62,7 +62,7 @@ struct ignoreStpF {
...
@@ -62,7 +62,7 @@ struct ignoreStpF {
struct
ignoreStrtF
{
struct
ignoreStrtF
{
template
<
class
Up
>
template
<
class
Up
>
void
operator
()(
Up
&
)
{}
void
operator
()(
Up
&
&
)
{}
};
};
...
@@ -114,10 +114,10 @@ struct passDStpF {
...
@@ -114,10 +114,10 @@ struct passDStpF {
struct
passDStrtF
{
struct
passDStrtF
{
PUSHMI_TEMPLATE
(
class
Up
,
class
Data
)
PUSHMI_TEMPLATE
(
class
Up
,
class
Data
)
(
requires
requires
(
(
requires
requires
(
::
pushmi
::
set_starting
(
std
::
declval
<
Data
&>
(),
std
::
declval
<
Up
&
>
())
::
pushmi
::
set_starting
(
std
::
declval
<
Data
&>
(),
std
::
declval
<
Up
>
())
)
&&
Receiver
<
Data
>
)
)
&&
Receiver
<
Data
>
)
void
operator
()(
Data
&
out
,
Up
&
up
)
const
{
void
operator
()(
Data
&
out
,
Up
up
)
const
{
::
pushmi
::
set_starting
(
out
,
up
);
::
pushmi
::
set_starting
(
out
,
std
::
move
(
up
)
);
}
}
};
};
...
...
folly/experimental/pushmi/include/pushmi/concepts.h
View file @
07a7d5c4
...
@@ -228,13 +228,13 @@ PUSHMI_CONCEPT_DEF(
...
@@ -228,13 +228,13 @@ PUSHMI_CONCEPT_DEF(
// silent does not really make sense, but cannot test for
// silent does not really make sense, but cannot test for
// None without the error type, use is_none<> to strengthen
// None without the error type, use is_none<> to strengthen
// requirements
// requirements
PUSHMI_CONCEPT_DEF
(
PUSHMI_CONCEPT_DEF
(
template
(
class
D
,
class
...
PropertyN
)
template
(
class
D
,
class
...
PropertyN
)
(
concept
Sender
)(
D
,
PropertyN
...),
(
concept
Sender
)(
D
,
PropertyN
...),
SemiMovable
<
D
>
&&
SemiMovable
<
D
>
&&
None
<
D
>
&&
None
<
D
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
is_sender_v
<
D
>
is_sender_v
<
D
>
);
);
...
@@ -266,19 +266,19 @@ PUSHMI_CONCEPT_DEF(
...
@@ -266,19 +266,19 @@ PUSHMI_CONCEPT_DEF(
PUSHMI_CONCEPT_DEF
(
PUSHMI_CONCEPT_DEF
(
template
(
template
(
class
N
,
class
N
,
class
Up
,
class
Up
,
class
PE
=
std
::
exception_ptr
,
class
PE
=
std
::
exception_ptr
,
class
E
=
PE
)
class
E
=
PE
)
(
concept
FlowNoneReceiver
)(
N
,
Up
,
PE
,
E
),
(
concept
FlowNoneReceiver
)(
N
,
Up
,
PE
,
E
),
requires
(
N
&
n
,
Up
&
up
)
(
requires
(
N
&
n
,
Up
&
&
up
)
(
::
pushmi
::
set_starting
(
n
,
up
)
::
pushmi
::
set_starting
(
n
,
(
Up
&&
)
up
)
)
&&
)
&&
FlowReceiver
<
N
>
&&
FlowReceiver
<
N
>
&&
Receiver
<
Up
>
&&
Receiver
<
Up
>
&&
SemiMovable
<
PE
>
&&
SemiMovable
<
PE
>
&&
SemiMovable
<
E
>
&&
SemiMovable
<
E
>
&&
NoneReceiver
<
Up
,
PE
>
&&
NoneReceiver
<
Up
,
PE
>
&&
NoneReceiver
<
N
,
E
>
NoneReceiver
<
N
,
E
>
);
);
...
@@ -290,7 +290,7 @@ PUSHMI_CONCEPT_DEF(
...
@@ -290,7 +290,7 @@ PUSHMI_CONCEPT_DEF(
class
PE
=
std
::
exception_ptr
,
class
PE
=
std
::
exception_ptr
,
class
E
=
PE
)
class
E
=
PE
)
(
concept
FlowSingleReceiver
)(
S
,
Up
,
T
,
PE
,
E
),
(
concept
FlowSingleReceiver
)(
S
,
Up
,
T
,
PE
,
E
),
SingleReceiver
<
S
,
T
,
E
>
&&
SingleReceiver
<
S
,
T
,
E
>
&&
FlowNoneReceiver
<
S
,
Up
,
PE
,
E
>
FlowNoneReceiver
<
S
,
Up
,
PE
,
E
>
);
);
...
@@ -302,7 +302,7 @@ PUSHMI_CONCEPT_DEF(
...
@@ -302,7 +302,7 @@ PUSHMI_CONCEPT_DEF(
class
PE
=
std
::
exception_ptr
,
class
PE
=
std
::
exception_ptr
,
class
E
=
PE
)
class
E
=
PE
)
(
concept
FlowManyReceiver
)(
S
,
Up
,
T
,
PE
,
E
),
(
concept
FlowManyReceiver
)(
S
,
Up
,
T
,
PE
,
E
),
ManyReceiver
<
S
,
T
,
E
>
&&
ManyReceiver
<
S
,
T
,
E
>
&&
FlowSingleReceiver
<
S
,
Up
,
T
,
PE
,
E
>
FlowSingleReceiver
<
S
,
Up
,
T
,
PE
,
E
>
);
);
...
@@ -332,9 +332,9 @@ PUSHMI_CONCEPT_DEF(
...
@@ -332,9 +332,9 @@ PUSHMI_CONCEPT_DEF(
::
pushmi
::
now
(
d
),
::
pushmi
::
now
(
d
),
requires_
<
Regular
<
decltype
(
::
pushmi
::
now
(
d
))>
>
requires_
<
Regular
<
decltype
(
::
pushmi
::
now
(
d
))>
>
)
&&
)
&&
Sender
<
D
>
&&
Sender
<
D
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
Time
<
D
>
&&
Time
<
D
>
&&
None
<
D
>
None
<
D
>
);
);
...
@@ -344,7 +344,7 @@ PUSHMI_CONCEPT_DEF(
...
@@ -344,7 +344,7 @@ PUSHMI_CONCEPT_DEF(
requires
(
D
&
d
,
S
&&
s
)
(
requires
(
D
&
d
,
S
&&
s
)
(
::
pushmi
::
submit
(
d
,
::
pushmi
::
now
(
d
),
(
S
&&
)
s
)
::
pushmi
::
submit
(
d
,
::
pushmi
::
now
(
d
),
(
S
&&
)
s
)
)
&&
)
&&
TimeSender
<
D
>
&&
TimeSender
<
D
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
property_query_v
<
D
,
PropertyN
...
>
&&
Receiver
<
S
>
Receiver
<
S
>
);
);
...
...
folly/experimental/pushmi/include/pushmi/entangle.h
0 → 100644
View file @
07a7d5c4
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include "forwards.h"
namespace
pushmi
{
#if 0
template <class T, class Dual>
struct entangled {
T t;
entangled<Dual, T>* dual;
~entangled() {
if (!!dual) {
dual->dual = nullptr;
}
}
explicit entangled(T t) : t(std::move(t)), dual(nullptr) {}
entangled(entangled&& o) : t(std::move(o.t)), dual(o.dual) {
o.dual = nullptr;
if (!!dual) {
dual->dual = this;
}
}
entangled() = delete;
entangled(const entangled&) = delete;
entangled& operator=(const entangled&) = delete;
entangled& operator=(entangled&&) = delete;
Dual* lockPointerToDual() {
if (!!dual) {
return std::addressof(dual->t);
}
return nullptr;
}
void unlockPointerToDual() {
}
};
#else
// This class can be used to keep a pair of values with pointers to each other
// in sync, even when both objects are allowed to move. Ordinarily you'd do this
// with a heap-allocated, refcounted, control block (or do something else using
// external storage, like a lock table chosen by the current addresses of both
// objects).
// Another thing you could do is have locks, and a backoff strategy for dealing
// with deadlock: lock locally, trylock your dual, if the trylock fails,
// unlock yourself, wait a little while (giving a thread touching the dual a
// chance to acquire the local lock), and repeat. That's kind of ugly.
// This algorithm (which, to be clear, is untested and I haven't really even
// thought through carefully) provides the same guarantees, but without using
// external storage or backoff-based deadlock recovery.
template
<
class
T
,
class
Dual
>
struct
entangled
{
// must be constructed first so that the other.lockBoth() in the move
// constructor is run before moving other.t and other.dual
std
::
atomic
<
int
>
stateMachine
;
T
t
;
// In a couple places, we can save on some atomic ops by making this atomic,
// and adding a "dual == null" fast-path without locking.
entangled
<
Dual
,
T
>*
dual
;
const
static
int
kUnlocked
=
0
;
const
static
int
kLocked
=
1
;
const
static
int
kLockedAndLossAcknowledged
=
2
;
// Note: *not* thread-safe; it's a bug for two threads to concurrently call
// lockBoth() on the same entangled (just as it's a bug for two threads to
// concurrently move from the same object).
// However, calling lockBoth() on two entangled objects at once is
// thread-safe.
// Note also that this may wait indefinitely; it's not the usual non-blocking
// tryLock().
bool
tryLockBoth
()
{
// Try to acquire the local lock. We have to start locally, since local
// addresses are the only ones we know are safe at first. The rule is, you
// have to hold *both* locks to write any of either entangled object's
// metadata, but need only one to read it.
int
expected
=
kUnlocked
;
if
(
!
stateMachine
.
compare_exchange_weak
(
expected
,
kLocked
))
{
return
false
;
}
// Having *either* object local-locked protects the data in both objects.
// Once we hold our lock, no control data can change, in either object.
if
(
dual
==
nullptr
)
{
return
true
;
}
expected
=
kUnlocked
;
if
(
dual
->
stateMachine
.
compare_exchange_strong
(
expected
,
kLocked
))
{
return
true
;
}
// We got here, and so hit the race; we're deadlocked if we stick to
// locking. Revert to address-ordering. Note that address-ordering would
// not be safe on its own, because of the lifetime issues involved; the
// addresses here are only stable *because* we know both sides are locked,
// and because of the invariant that you must hold both locks to modify
// either piece of data.
if
((
uintptr_t
)
this
<
(
uintptr_t
)
dual
)
{
// I get to win the race. I'll acquire the locks, but have to make sure
// my memory stays valid until the other thread acknowledges its loss.
while
(
stateMachine
.
load
()
!=
kLockedAndLossAcknowledged
)
{
// Spin.
}
stateMachine
.
store
(
kLocked
);
return
true
;
}
else
{
// I lose the race, but have to coordinate with the winning thread, so
// that it knows that I'm not about to try to touch it's data
dual
->
stateMachine
.
store
(
kLockedAndLossAcknowledged
);
return
false
;
}
}
void
lockBoth
()
{
while
(
!
tryLockBoth
())
{
// Spin. But, note that all the unbounded spinning in tryLockBoth can be
// straightforwardly futex-ified. There's a potentialy starvation issue
// here, but note that it can be dealt with by adding a "priority" bit to
// the state machine (i.e. if my priority bit is set, the thread for whom
// I'm the local member of the pair gets to win the race, rather than
// using address-ordering).
}
}
void
unlockBoth
()
{
// Note that unlocking locally and then remotely is the right order. There
// are no concurrent accesses to this object (as an API constraint -- lock
// and unlock are not thread safe!), and no other thread will touch the
// other object so long as its locked. Going in the other order could let
// another thread incorrectly think we're going down the deadlock-avoidance
// path in tryLock().
stateMachine
.
store
(
kUnlocked
);
if
(
dual
!=
nullptr
)
{
dual
->
stateMachine
.
store
(
kUnlocked
);
}
}
entangled
()
=
delete
;
entangled
(
const
entangled
&
)
=
delete
;
entangled
&
operator
=
(
const
entangled
&
)
=
delete
;
entangled
&
operator
=
(
entangled
&&
)
=
delete
;
explicit
entangled
(
T
t
)
:
t
(
std
::
move
(
t
)),
dual
(
nullptr
),
stateMachine
(
kUnlocked
)
{}
entangled
(
entangled
&&
other
)
:
stateMachine
((
other
.
lockBoth
(),
kLocked
)),
t
(
std
::
move
(
other
.
t
)),
dual
(
std
::
move
(
other
.
dual
))
{
// Note that, above, we initialized stateMachine to the locked state; the
// address of this object hasn't escaped yet, and won't (until we unlock
// the dual), so it doesn't *really* matter, but it's conceptually helpful
// to maintain that invariant.
// Update our dual's data.
if
(
dual
!=
nullptr
)
{
dual
->
dual
=
this
;
}
// Update other's data.
other
.
dual
=
nullptr
;
// unlock other so that its destructor can complete
other
.
stateMachine
.
store
(
kUnlocked
);
// We locked on other, but will unlock on *this. The locking protocol
// ensured that no accesses to other will occur after lock() returns, and
// since then we updated dual's dual to be us.
unlockBoth
();
}
~
entangled
()
{
lockBoth
();
if
(
dual
!=
nullptr
)
{
dual
->
dual
=
nullptr
;
}
unlockBoth
();
}
// Must unlock later even if dual is nullptr. This is fixable.
Dual
*
lockPointerToDual
()
{
lockBoth
();
return
!!
dual
?
std
::
addressof
(
dual
->
t
)
:
nullptr
;
}
void
unlockPointerToDual
()
{
unlockBoth
();
}
};
#endif
template
<
class
First
,
class
Second
>
auto
entangle
(
First
f
,
Second
s
)
->
std
::
pair
<
entangled
<
First
,
Second
>
,
entangled
<
Second
,
First
>>
{
entangled
<
First
,
Second
>
ef
(
std
::
move
(
f
));
entangled
<
Second
,
First
>
es
(
std
::
move
(
s
));
ef
.
dual
=
std
::
addressof
(
es
);
es
.
dual
=
std
::
addressof
(
ef
);
return
{
std
::
move
(
ef
),
std
::
move
(
es
)};
}
}
// namespace pushmi
folly/experimental/pushmi/include/pushmi/extension_points.h
View file @
07a7d5c4
...
@@ -33,9 +33,9 @@ void set_stopping(S& s) noexcept(noexcept(s.stopping())) {
...
@@ -33,9 +33,9 @@ void set_stopping(S& s) noexcept(noexcept(s.stopping())) {
s
.
stopping
();
s
.
stopping
();
}
}
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
(
requires
requires
(
std
::
declval
<
S
&>
().
starting
(
std
::
declval
<
Up
&
>
())))
(
requires
requires
(
std
::
declval
<
S
&>
().
starting
(
std
::
declval
<
Up
>
())))
void
set_starting
(
S
&
s
,
Up
&
up
)
noexcept
(
noexcept
(
s
.
starting
(
up
)))
{
void
set_starting
(
S
&
s
,
Up
up
)
noexcept
(
noexcept
(
s
.
starting
(
std
::
move
(
up
)
)))
{
s
.
starting
(
up
);
s
.
starting
(
std
::
move
(
up
)
);
}
}
PUSHMI_TEMPLATE
(
class
SD
,
class
Out
)
PUSHMI_TEMPLATE
(
class
SD
,
class
Out
)
...
@@ -107,10 +107,10 @@ void set_stopping(std::reference_wrapper<S> s) noexcept(
...
@@ -107,10 +107,10 @@ void set_stopping(std::reference_wrapper<S> s) noexcept(
set_stopping
(
s
.
get
());
set_stopping
(
s
.
get
());
}
}
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
(
requires
requires
(
set_starting
(
std
::
declval
<
S
&>
(),
std
::
declval
<
Up
&
>
())
))
(
requires
requires
(
set_starting
(
std
::
declval
<
S
&>
(),
std
::
declval
<
Up
>
())
))
void
set_starting
(
std
::
reference_wrapper
<
S
>
s
,
Up
&
up
)
noexcept
(
void
set_starting
(
std
::
reference_wrapper
<
S
>
s
,
Up
up
)
noexcept
(
noexcept
(
set_starting
(
s
.
get
(),
up
)))
{
noexcept
(
set_starting
(
s
.
get
(),
std
::
move
(
up
)
)))
{
set_starting
(
s
.
get
(),
up
);
set_starting
(
s
.
get
(),
std
::
move
(
up
)
);
}
}
PUSHMI_TEMPLATE
(
class
SD
,
class
Out
)
PUSHMI_TEMPLATE
(
class
SD
,
class
Out
)
(
requires
requires
(
submit
(
std
::
declval
<
SD
&>
(),
std
::
declval
<
Out
>
())
))
(
requires
requires
(
submit
(
std
::
declval
<
SD
&>
(),
std
::
declval
<
Out
>
())
))
...
@@ -188,13 +188,13 @@ struct set_stopping_fn {
...
@@ -188,13 +188,13 @@ struct set_stopping_fn {
struct
set_starting_fn
{
struct
set_starting_fn
{
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
PUSHMI_TEMPLATE
(
class
S
,
class
Up
)
(
requires
requires
(
(
requires
requires
(
set_starting
(
std
::
declval
<
S
&>
(),
std
::
declval
<
Up
&
>
()),
set_starting
(
std
::
declval
<
S
&>
(),
std
::
declval
<
Up
>
()),
set_error
(
std
::
declval
<
S
&>
(),
std
::
current_exception
())
set_error
(
std
::
declval
<
S
&>
(),
std
::
current_exception
())
))
))
void
operator
()(
S
&&
s
,
Up
&
up
)
const
void
operator
()(
S
&&
s
,
Up
up
)
const
noexcept
(
noexcept
(
set_starting
(
s
,
up
)))
{
noexcept
(
noexcept
(
set_starting
(
s
,
std
::
move
(
up
)
)))
{
try
{
try
{
set_starting
(
s
,
up
);
set_starting
(
s
,
std
::
move
(
up
)
);
}
catch
(...)
{
}
catch
(...)
{
set_error
(
s
,
std
::
current_exception
());
set_error
(
s
,
std
::
current_exception
());
}
}
...
...
folly/experimental/pushmi/include/pushmi/flow_single.h
View file @
07a7d5c4
...
@@ -25,13 +25,13 @@ class flow_single<V, PE, E> {
...
@@ -25,13 +25,13 @@ class flow_single<V, PE, E> {
static
void
s_error
(
data
&
,
E
)
noexcept
{
std
::
terminate
();
}
static
void
s_error
(
data
&
,
E
)
noexcept
{
std
::
terminate
();
}
static
void
s_value
(
data
&
,
V
)
{}
static
void
s_value
(
data
&
,
V
)
{}
static
void
s_stopping
(
data
&
)
noexcept
{}
static
void
s_stopping
(
data
&
)
noexcept
{}
static
void
s_starting
(
data
&
,
any_none
<
PE
>
&
)
{}
static
void
s_starting
(
data
&
,
any_none
<
PE
>
)
{}
void
(
*
op_
)(
data
&
,
data
*
)
=
vtable
::
s_op
;
void
(
*
op_
)(
data
&
,
data
*
)
=
vtable
::
s_op
;
void
(
*
done_
)(
data
&
)
=
vtable
::
s_done
;
void
(
*
done_
)(
data
&
)
=
vtable
::
s_done
;
void
(
*
error_
)(
data
&
,
E
)
noexcept
=
vtable
::
s_error
;
void
(
*
error_
)(
data
&
,
E
)
noexcept
=
vtable
::
s_error
;
void
(
*
value_
)(
data
&
,
V
)
=
vtable
::
s_value
;
void
(
*
value_
)(
data
&
,
V
)
=
vtable
::
s_value
;
void
(
*
stopping_
)(
data
&
)
noexcept
=
vtable
::
s_stopping
;
void
(
*
stopping_
)(
data
&
)
noexcept
=
vtable
::
s_stopping
;
void
(
*
starting_
)(
data
&
,
any_none
<
PE
>
&
)
=
vtable
::
s_starting
;
void
(
*
starting_
)(
data
&
,
any_none
<
PE
>
)
=
vtable
::
s_starting
;
};
};
static
constexpr
vtable
const
noop_
{};
static
constexpr
vtable
const
noop_
{};
vtable
const
*
vptr_
=
&
noop_
;
vtable
const
*
vptr_
=
&
noop_
;
...
@@ -55,8 +55,8 @@ class flow_single<V, PE, E> {
...
@@ -55,8 +55,8 @@ class flow_single<V, PE, E> {
static
void
stopping
(
data
&
src
)
noexcept
{
static
void
stopping
(
data
&
src
)
noexcept
{
::
pushmi
::
set_stopping
(
*
static_cast
<
Wrapped
*>
(
src
.
pobj_
));
::
pushmi
::
set_stopping
(
*
static_cast
<
Wrapped
*>
(
src
.
pobj_
));
}
}
static
void
starting
(
data
&
src
,
any_none
<
PE
>
&
up
)
{
static
void
starting
(
data
&
src
,
any_none
<
PE
>
up
)
{
::
pushmi
::
set_starting
(
*
static_cast
<
Wrapped
*>
(
src
.
pobj_
),
up
);
::
pushmi
::
set_starting
(
*
static_cast
<
Wrapped
*>
(
src
.
pobj_
),
std
::
move
(
up
)
);
}
}
};
};
static
const
vtable
vtbl
{
s
::
op
,
s
::
done
,
s
::
error
,
s
::
value
,
s
::
stopping
,
s
::
starting
};
static
const
vtable
vtbl
{
s
::
op
,
s
::
done
,
s
::
error
,
s
::
value
,
s
::
stopping
,
s
::
starting
};
...
@@ -86,8 +86,8 @@ class flow_single<V, PE, E> {
...
@@ -86,8 +86,8 @@ class flow_single<V, PE, E> {
static
void
stopping
(
data
&
src
)
noexcept
{
static
void
stopping
(
data
&
src
)
noexcept
{
::
pushmi
::
set_stopping
(
*
static_cast
<
Wrapped
*>
((
void
*
)
src
.
buffer_
));
::
pushmi
::
set_stopping
(
*
static_cast
<
Wrapped
*>
((
void
*
)
src
.
buffer_
));
}
}
static
void
starting
(
data
&
src
,
any_none
<
PE
>
&
up
)
{
static
void
starting
(
data
&
src
,
any_none
<
PE
>
up
)
{
::
pushmi
::
set_starting
(
*
static_cast
<
Wrapped
*>
((
void
*
)
src
.
buffer_
),
up
);
::
pushmi
::
set_starting
(
*
static_cast
<
Wrapped
*>
((
void
*
)
src
.
buffer_
),
std
::
move
(
up
)
);
}
}
};
};
static
const
vtable
vtbl
{
s
::
op
,
s
::
done
,
s
::
error
,
s
::
value
,
s
::
stopping
,
s
::
starting
};
static
const
vtable
vtbl
{
s
::
op
,
s
::
done
,
s
::
error
,
s
::
value
,
s
::
stopping
,
s
::
starting
};
...
@@ -130,8 +130,8 @@ public:
...
@@ -130,8 +130,8 @@ public:
void
stopping
()
noexcept
{
void
stopping
()
noexcept
{
vptr_
->
stopping_
(
data_
);
vptr_
->
stopping_
(
data_
);
}
}
void
starting
(
any_none
<
PE
>
&
up
)
{
void
starting
(
any_none
<
PE
>
up
)
{
vptr_
->
starting_
(
data_
,
up
);
vptr_
->
starting_
(
data_
,
std
::
move
(
up
)
);
}
}
};
};
...
@@ -199,9 +199,9 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
...
@@ -199,9 +199,9 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
stpf_
();
stpf_
();
}
}
PUSHMI_TEMPLATE
(
class
Up
)
PUSHMI_TEMPLATE
(
class
Up
)
(
requires
Receiver
<
Up
,
is_none
<>>
&&
Invocable
<
StrtF
&
,
Up
&>
)
(
requires
Receiver
<
Up
,
is_none
<>>
&&
Invocable
<
StrtF
&
,
Up
&
&
>
)
void
starting
(
Up
&
up
)
{
void
starting
(
Up
&
&
up
)
{
strtf_
(
up
);
strtf_
(
(
Up
&&
)
up
);
}
}
};
};
...
@@ -271,9 +271,9 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
...
@@ -271,9 +271,9 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
stpf_
(
data_
);
stpf_
(
data_
);
}
}
PUSHMI_TEMPLATE
(
class
Up
)
PUSHMI_TEMPLATE
(
class
Up
)
(
requires
Invocable
<
DStrtF
&
,
Data
&
,
Up
&>
)
(
requires
Invocable
<
DStrtF
&
,
Data
&
,
Up
&
&
>
)
void
starting
(
Up
&
up
)
{
void
starting
(
Up
&
&
up
)
{
strtf_
(
data_
,
up
);
strtf_
(
data_
,
(
Up
&&
)
up
);
}
}
};
};
...
...
folly/experimental/pushmi/include/pushmi/trampoline.h
View file @
07a7d5c4
...
@@ -225,6 +225,9 @@ class trampoline {
...
@@ -225,6 +225,9 @@ class trampoline {
auto
item
=
std
::
move
(
pending
(
pending_store
).
front
());
auto
item
=
std
::
move
(
pending
(
pending_store
).
front
());
pending
(
pending_store
).
pop_front
();
pending
(
pending_store
).
pop_front
();
auto
&
when
=
std
::
get
<
0
>
(
item
);
auto
&
when
=
std
::
get
<
0
>
(
item
);
if
(
when
>
trampoline
<
E
>::
now
())
{
std
::
this_thread
::
sleep_until
(
when
);
}
auto
&
what
=
std
::
get
<
1
>
(
item
);
auto
&
what
=
std
::
get
<
1
>
(
item
);
any_time_executor_ref
<
error_type
,
time_point
>
anythis
{
that
};
any_time_executor_ref
<
error_type
,
time_point
>
anythis
{
that
};
::
pushmi
::
set_value
(
what
,
anythis
);
::
pushmi
::
set_value
(
what
,
anythis
);
...
...
folly/experimental/pushmi/test/FlowTest.cpp
View file @
07a7d5c4
This diff is collapsed.
Click to expand it.
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