Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
C
capnproto
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
capnproto
Commits
2e3b3413
Commit
2e3b3413
authored
Nov 14, 2013
by
Kenton Varda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Simplify event queuing -- always preempt from same thread, yield cross-thread.
parent
860af726
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
85 additions
and
45 deletions
+85
-45
serialize-async-test.c++
c++/src/capnp/serialize-async-test.c++
+1
-1
async-test.c++
c++/src/kj/async-test.c++
+32
-0
async.c++
c++/src/kj/async.c++
+52
-44
async.h
c++/src/kj/async.h
+0
-0
No files found.
c++/src/capnp/serialize-async-test.c++
View file @
2e3b3413
...
...
@@ -46,7 +46,7 @@ public:
while
(
size
>
0
)
{
size_t
n
=
rand
()
%
size
+
1
;
inner
.
write
(
buffer
,
n
);
usleep
(
5
);
usleep
(
10000
);
buffer
=
reinterpret_cast
<
const
byte
*>
(
buffer
)
+
n
;
size
-=
n
;
}
...
...
c++/src/kj/async-test.c++
View file @
2e3b3413
...
...
@@ -337,6 +337,38 @@ TEST(Async, Ordering) {
EXPECT_EQ
(
7
,
counter
);
}
TEST
(
Async
,
Spark
)
{
// Tests that EventLoop::there() only runs eagerly when queued cross-thread.
SimpleEventLoop
loop
;
auto
notification
=
newPromiseAndFulfiller
<
void
>
();;
Promise
<
void
>
unsparked
=
nullptr
;
Promise
<
void
>
then
=
nullptr
;
Promise
<
void
>
later
=
nullptr
;
// `sparked` will evaluate eagerly, even though we never wait on it, because there() is being
// called from outside the event loop.
Promise
<
void
>
sparked
=
loop
.
there
(
Promise
<
void
>
(
READY_NOW
),
[
&
]()
{
// `unsparked` will never execute because it's attached to the current loop and we never wait
// on it.
unsparked
=
loop
.
there
(
Promise
<
void
>
(
READY_NOW
),
[
&
]()
{
ADD_FAILURE
()
<<
"This continuation shouldn't happen because no one waits on it."
;
});
// `then` will similarly never execute.
then
=
Promise
<
void
>
(
READY_NOW
).
then
([
&
]()
{
ADD_FAILURE
()
<<
"This continuation shouldn't happen because no one waits on it."
;
});
// `evalLater` *does* eagerly execute even when queued to the same loop.
later
=
loop
.
evalLater
([
&
]()
{
notification
.
fulfiller
->
fulfill
();
});
});
loop
.
wait
(
kj
::
mv
(
notification
.
promise
));
}
TEST
(
Async
,
Fork
)
{
SimpleEventLoop
loop
;
...
...
c++/src/kj/async.c++
View file @
2e3b3413
...
...
@@ -53,6 +53,20 @@ public:
}
};
class
YieldPromiseNode
final
:
public
_
::
PromiseNode
{
public
:
bool
onReady
(
EventLoop
::
Event
&
event
)
noexcept
override
{
event
.
arm
(
false
);
return
false
;
}
void
get
(
_
::
ExceptionOrValue
&
output
)
noexcept
override
{
output
.
as
<
_
::
Void
>
().
value
=
_
::
Void
();
}
Maybe
<
const
EventLoop
&>
getSafeEventLoop
()
noexcept
override
{
return
nullptr
;
}
};
}
// namespace
EventLoop
&
EventLoop
::
current
()
{
...
...
@@ -61,6 +75,10 @@ EventLoop& EventLoop::current() {
return
*
result
;
}
bool
EventLoop
::
isCurrent
()
const
{
return
threadLocalEventLoop
==
this
;
}
void
EventLoop
::
EventListHead
::
fire
()
{
KJ_FAIL_ASSERT
(
"Fired event list head."
);
}
...
...
@@ -114,6 +132,10 @@ void EventLoop::waitImpl(Own<_::PromiseNode> node, _::ExceptionOrValue& result)
node
->
get
(
result
);
}
Promise
<
void
>
EventLoop
::
yieldIfSameThread
()
const
{
return
Promise
<
void
>
(
false
,
kj
::
heap
<
YieldPromiseNode
>
());
}
EventLoop
::
Event
::~
Event
()
noexcept
(
false
)
{
if
(
this
!=
&
loop
.
queue
)
{
KJ_ASSERT
(
next
==
nullptr
||
std
::
uncaught_exception
(),
...
...
@@ -122,36 +144,32 @@ EventLoop::Event::~Event() noexcept(false) {
}
}
void
EventLoop
::
Event
::
arm
(
Schedule
schedule
)
{
void
EventLoop
::
Event
::
arm
(
bool
preemptIfSameThread
)
{
loop
.
queue
.
mutex
.
lock
(
_
::
Mutex
::
EXCLUSIVE
);
KJ_DEFER
(
loop
.
queue
.
mutex
.
unlock
(
_
::
Mutex
::
EXCLUSIVE
));
if
(
next
==
nullptr
)
{
bool
queueIsEmpty
=
loop
.
queue
.
next
==
&
loop
.
queue
;
switch
(
schedule
)
{
case
PREEMPT
:
// Insert the event into the queue. We put it at the front rather than the back so that
// related events are executed together and so that increasing the granularity of events
// does not cause your code to "lose priority" compared to simultaneously-running code
// with less granularity.
next
=
loop
.
insertPoint
;
prev
=
next
->
prev
;
next
->
prev
=
this
;
prev
->
next
=
this
;
break
;
case
YIELD
:
// Insert the node at the *end* of the queue.
prev
=
loop
.
queue
.
prev
;
next
=
prev
->
next
;
prev
->
next
=
this
;
next
->
prev
=
this
;
if
(
loop
.
insertPoint
==
&
loop
.
queue
)
{
loop
.
insertPoint
=
this
;
}
break
;
if
(
preemptIfSameThread
&&
threadLocalEventLoop
==
&
loop
)
{
// Insert the event into the queue. We put it at the front rather than the back so that
// related events are executed together and so that increasing the granularity of events
// does not cause your code to "lose priority" compared to simultaneously-running code
// with less granularity.
next
=
loop
.
insertPoint
;
prev
=
next
->
prev
;
next
->
prev
=
this
;
prev
->
next
=
this
;
}
else
{
// Insert the node at the *end* of the queue.
prev
=
loop
.
queue
.
prev
;
next
=
prev
->
next
;
prev
->
next
=
this
;
next
->
prev
=
this
;
if
(
loop
.
insertPoint
==
&
loop
.
queue
)
{
loop
.
insertPoint
=
this
;
}
}
if
(
queueIsEmpty
)
{
...
...
@@ -284,8 +302,7 @@ public:
Task
(
const
Impl
&
taskSet
,
Own
<
_
::
PromiseNode
>&&
nodeParam
)
:
EventLoop
::
Event
(
taskSet
.
loop
),
taskSet
(
taskSet
),
node
(
kj
::
mv
(
nodeParam
))
{
if
(
node
->
onReady
(
*
this
))
{
// TODO(soon): Only yield cross-thread.
arm
(
EventLoop
::
Event
::
YIELD
);
arm
();
}
}
...
...
@@ -361,8 +378,7 @@ bool PromiseNode::atomicOnReady(EventLoop::Event*& onReadyEvent, EventLoop::Even
}
}
void
PromiseNode
::
atomicReady
(
EventLoop
::
Event
*&
onReadyEvent
,
EventLoop
::
Event
::
Schedule
schedule
)
{
void
PromiseNode
::
atomicReady
(
EventLoop
::
Event
*&
onReadyEvent
)
{
// If onReadyEvent is null, atomically set it to _kJ_ALREADY_READY.
// Otherwise, arm whatever it points at.
// Useful for firing events in conjuction with atomicOnReady().
...
...
@@ -370,7 +386,7 @@ void PromiseNode::atomicReady(EventLoop::Event*& onReadyEvent,
EventLoop
::
Event
*
oldEvent
=
nullptr
;
if
(
!
__atomic_compare_exchange_n
(
&
onReadyEvent
,
&
oldEvent
,
_kJ_ALREADY_READY
,
false
,
__ATOMIC_ACQ_REL
,
__ATOMIC_ACQUIRE
))
{
oldEvent
->
arm
(
schedule
);
oldEvent
->
arm
();
}
}
...
...
@@ -439,8 +455,7 @@ ForkBranchBase::~ForkBranchBase() noexcept(false) {
}
void
ForkBranchBase
::
hubReady
()
noexcept
{
// TODO(soon): This should only yield if queuing cross-thread.
atomicReady
(
onReadyEvent
,
EventLoop
::
Event
::
YIELD
);
atomicReady
(
onReadyEvent
);
}
void
ForkBranchBase
::
releaseHub
(
ExceptionOrValue
&
output
)
{
...
...
@@ -467,9 +482,7 @@ ForkHubBase::ForkHubBase(const EventLoop& loop, Own<PromiseNode>&& inner,
ExceptionOrValue
&
resultRef
)
:
EventLoop
::
Event
(
loop
),
inner
(
kj
::
mv
(
inner
)),
resultRef
(
resultRef
)
{
KJ_DREQUIRE
(
this
->
inner
->
isSafeEventLoop
(
loop
));
// TODO(soon): This should only yield if queuing cross-thread.
arm
(
YIELD
);
arm
();
}
ForkHubBase
::~
ForkHubBase
()
noexcept
(
false
)
{
...
...
@@ -503,10 +516,10 @@ void ForkHubBase::fire() {
// -------------------------------------------------------------------
ChainPromiseNode
::
ChainPromiseNode
(
const
EventLoop
&
loop
,
Own
<
PromiseNode
>
inner
,
Schedule
schedule
)
ChainPromiseNode
::
ChainPromiseNode
(
const
EventLoop
&
loop
,
Own
<
PromiseNode
>
inner
)
:
Event
(
loop
),
state
(
PRE_STEP1
),
inner
(
kj
::
mv
(
inner
))
{
KJ_DREQUIRE
(
this
->
inner
->
isSafeEventLoop
(
loop
));
arm
(
schedule
);
arm
();
}
ChainPromiseNode
::~
ChainPromiseNode
()
noexcept
(
false
)
{
...
...
@@ -573,7 +586,7 @@ void ChainPromiseNode::fire() {
if
(
onReadyEvent
!=
nullptr
)
{
if
(
inner
->
onReady
(
*
onReadyEvent
))
{
onReadyEvent
->
arm
(
PREEMPT
);
onReadyEvent
->
arm
();
}
}
}
...
...
@@ -584,12 +597,7 @@ CrossThreadPromiseNodeBase::CrossThreadPromiseNodeBase(
const
EventLoop
&
loop
,
Own
<
PromiseNode
>&&
dependency
,
ExceptionOrValue
&
resultRef
)
:
Event
(
loop
),
dependency
(
kj
::
mv
(
dependency
)),
resultRef
(
resultRef
)
{
KJ_DREQUIRE
(
this
->
dependency
->
isSafeEventLoop
(
loop
));
// The constructor may be called from any thread, so before we can even call onReady() we need
// to switch threads. We yield here so that the event is added to the end of the queue, which
// ensures that multiple events added in sequence are added in order. If we used PREEMPT, events
// we queue cross-thread would end up executing in a non-deterministic order.
arm
(
YIELD
);
arm
();
}
CrossThreadPromiseNodeBase
::~
CrossThreadPromiseNodeBase
()
noexcept
(
false
)
{
...
...
@@ -616,7 +624,7 @@ void CrossThreadPromiseNodeBase::fire() {
}
// If onReadyEvent is null, set it to _kJ_ALREADY_READY. Otherwise, arm it.
PromiseNode
::
atomicReady
(
onReadyEvent
,
YIELD
);
PromiseNode
::
atomicReady
(
onReadyEvent
);
}
}
...
...
c++/src/kj/async.h
View file @
2e3b3413
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