Bug 1313200 - Allow IPC messages to async return MozPromises. r=billm,jwwang

This patch implements async returns for IPDL using MozPromises. There
are following changes:

* Initialize AbstractThreads for MessageLoops
* Record promises and their reject functions
  * When async message returns, call their resolve functions
  * When send error or channel close, call their reject functions
* Implement "unresolved-ipc-promises" count for about:memory
* Test cases

See bug attachment for generated code from test cases

MozReview-Commit-ID: 7xmg8gwDGaW

--HG--
rename : ipc/ipdl/test/ipdl/error/AsyncReturn.ipdl => ipc/ipdl/test/ipdl/ok/AsyncReturn.ipdl
extra : rebase_source : 9a5821d6c0e5f7152b8152a17a409b94e8258dc3
This commit is contained in:
Kan-Ru Chen 2017-03-16 17:36:15 +08:00
parent 36e1ce5909
commit ffab50c6f8
16 changed files with 852 additions and 47 deletions

View File

@ -6,17 +6,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ipc/MessageChannel.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/dom/ScriptSettings.h"
#include "MessageLoopAbstractThreadWrapper.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/Move.h"
#include "mozilla/SizePrintfMacros.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Logging.h"
#include "mozilla/TimeStamp.h"
#include "nsAppRunner.h"
#include "nsAutoPtr.h"
@ -488,6 +489,27 @@ private:
nsAutoPtr<IPC::Message> mReply;
};
class PromiseReporter final : public nsIMemoryReporter
{
~PromiseReporter() {}
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_IMETHOD
CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
bool aAnonymize) override
{
MOZ_COLLECT_REPORT(
"unresolved-ipc-promises", KIND_OTHER, UNITS_COUNT, MessageChannel::gUnresolvedPromises,
"Outstanding IPC async message promises that is still not resolved.");
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(PromiseReporter, nsIMemoryReporter)
Atomic<size_t> MessageChannel::gUnresolvedPromises;
MessageChannel::MessageChannel(const char* aName,
IToplevelProtocol *aListener)
: mName(aName),
@ -530,6 +552,11 @@ MessageChannel::MessageChannel(const char* aName,
mEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
MOZ_RELEASE_ASSERT(mEvent, "CreateEvent failed! Nothing is going to work!");
#endif
static Atomic<bool> registered;
if (registered.compareExchange(false, true)) {
RegisterStrongMemoryReporter(new PromiseReporter());
}
}
MessageChannel::~MessageChannel()
@ -672,6 +699,12 @@ MessageChannel::Clear()
mWorkerLoop->RemoveDestructionObserver(this);
}
gUnresolvedPromises -= mPendingPromises.size();
for (auto& pair : mPendingPromises) {
pair.second.mRejectFunction(__func__);
}
mPendingPromises.clear();
mWorkerLoop = nullptr;
delete mLink;
mLink = nullptr;
@ -703,9 +736,13 @@ MessageChannel::Open(Transport* aTransport, MessageLoop* aIOLoop, Side aSide)
mMonitor = new RefCountedMonitor();
mWorkerLoop = MessageLoop::current();
mWorkerLoopID = mWorkerLoop->id();
mWorkerLoop->AddDestructionObserver(this);
if (!AbstractThread::GetCurrent()) {
mAbstractThread = MessageLoopAbstractThreadWrapper::Create(mWorkerLoop);
}
ProcessLink *link = new ProcessLink(this);
link->Open(aTransport, aIOLoop, aSide); // :TODO: n.b.: sets mChild
mLink = link;
@ -783,6 +820,11 @@ MessageChannel::CommonThreadOpenInit(MessageChannel *aTargetChan, Side aSide)
mWorkerLoop = MessageLoop::current();
mWorkerLoopID = mWorkerLoop->id();
mWorkerLoop->AddDestructionObserver(this);
if (!AbstractThread::GetCurrent()) {
mAbstractThread = MessageLoopAbstractThreadWrapper::Create(mWorkerLoop);
}
mLink = new ThreadLink(this, aTargetChan);
mSide = aSide;
}
@ -851,6 +893,19 @@ MessageChannel::Send(Message* aMsg)
return true;
}
already_AddRefed<MozPromiseRefcountable>
MessageChannel::PopPromise(const Message& aMsg)
{
auto iter = mPendingPromises.find(aMsg.seqno());
if (iter != mPendingPromises.end()) {
PromiseHolder ret = iter->second;
mPendingPromises.erase(iter);
gUnresolvedPromises--;
return ret.mPromise.forget();
}
return nullptr;
}
class BuildIDMessage : public IPC::Message
{
public:

View File

@ -11,8 +11,11 @@
#include "base/basictypes.h"
#include "base/message_loop.h"
#include "nsIMemoryReporter.h"
#include "mozilla/Atomics.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Monitor.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Vector.h"
#if defined(OS_WIN)
#include "mozilla/ipc/Neutering.h"
@ -26,10 +29,13 @@
#include <deque>
#include <functional>
#include <stack>
#include <map>
#include <math.h>
#include <stack>
namespace mozilla {
class AbstractThread;
namespace ipc {
class MessageChannel;
@ -61,6 +67,13 @@ enum class SyncSendError {
ReplyError,
};
enum class PromiseRejectReason {
SendError,
ChannelClosed,
HandlerRejected,
EndGuard_,
};
enum ChannelState {
ChannelClosed,
ChannelOpening,
@ -82,6 +95,14 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver
typedef mozilla::Monitor Monitor;
struct PromiseHolder
{
RefPtr<MozPromiseRefcountable> mPromise;
std::function<void(const char*)> mRejectFunction;
};
static Atomic<size_t> gUnresolvedPromises;
friend class PromiseReporter;
public:
static const int32_t kNoTimeout;
@ -154,6 +175,25 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver
// Asynchronously send a message to the other side of the channel
bool Send(Message* aMsg);
// Asynchronously send a message to the other side of the channel
// and wait for asynchronous reply
template<typename Promise>
bool Send(Message* aMsg, Promise* aPromise) {
int32_t seqno = NextSeqno();
aMsg->set_seqno(seqno);
if (!Send(aMsg)) {
return false;
}
PromiseHolder holder;
holder.mPromise = aPromise;
holder.mRejectFunction = [aPromise](const char* aRejectSite) {
aPromise->Reject(PromiseRejectReason::ChannelClosed, aRejectSite);
};
mPendingPromises.insert(std::make_pair(seqno, Move(holder)));
gUnresolvedPromises++;
return true;
}
void SendBuildID();
// Asynchronously deliver a message back to this side of the
@ -171,6 +211,9 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver
bool CanSend() const;
// Remove and return a promise that needs reply
already_AddRefed<MozPromiseRefcountable> PopPromise(const Message& aMsg);
// If sending a sync message returns an error, this function gives a more
// descriptive error message.
SyncSendError LastSendError() const {
@ -490,6 +533,7 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver
typedef LinkedList<RefPtr<MessageTask>> MessageQueue;
typedef std::map<size_t, Message> MessageMap;
typedef std::map<size_t, PromiseHolder> PromiseMap;
typedef IPC::Message::msgid_t msgid_t;
void WillDestroyCurrentMessageLoop() override;
@ -506,6 +550,7 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver
Side mSide;
MessageLink* mLink;
MessageLoop* mWorkerLoop; // thread where work is done
RefPtr<AbstractThread> mAbstractThread;
RefPtr<CancelableRunnable> mChannelErrorTask; // NotifyMaybeChannelError runnable
// id() of mWorkerLoop. This persists even after mWorkerLoop is cleared
@ -520,7 +565,7 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver
bool mInTimeoutSecondHalf;
// Worker-thread only; sequence numbers for messages that require
// synchronous replies.
// replies.
int32_t mNextSeqno;
static bool sIsPumpingMessages;
@ -689,6 +734,9 @@ class MessageChannel : HasResultCodes, MessageLoop::DestructionObserver
// https://bugzilla.mozilla.org/show_bug.cgi?id=521929.
MessageMap mOutOfTurnReplies;
// Map of async Promises that are still waiting replies.
PromiseMap mPendingPromises;
// Stack of Interrupt in-calls that were deferred because of race
// conditions.
std::stack<Message> mDeferred;
@ -722,4 +770,13 @@ CancelCPOWs();
} // namespace ipc
} // namespace mozilla
namespace IPC {
template <>
struct ParamTraits<mozilla::ipc::PromiseRejectReason>
: public ContiguousEnumSerializer<mozilla::ipc::PromiseRejectReason,
mozilla::ipc::PromiseRejectReason::SendError,
mozilla::ipc::PromiseRejectReason::EndGuard_>
{ };
} // namespace IPC
#endif // ifndef ipc_glue_MessageChannel_h

View File

@ -0,0 +1,159 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ipc_glue_MessageLoopAbstractThreadWrapper_h
#define mozilla_ipc_glue_MessageLoopAbstractThreadWrapper_h
#include "mozilla/AbstractThread.h"
#include "base/message_loop.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace ipc {
class MessageLoopAbstractThreadWrapper : public AbstractThread
{
public:
static already_AddRefed<AbstractThread>
Create(MessageLoop* aMessageLoop)
{
RefPtr<MessageLoopAbstractThreadWrapper> wrapper =
new MessageLoopAbstractThreadWrapper(aMessageLoop);
bool onCurrentThread = (aMessageLoop == MessageLoop::current());
if (onCurrentThread) {
sCurrentThreadTLS.set(wrapper);
return wrapper.forget();
}
// Set the thread-local sCurrentThreadTLS to point to the wrapper on the
// target thread. This ensures that sCurrentThreadTLS is as expected by
// AbstractThread::GetCurrent() on the target thread.
RefPtr<Runnable> r =
NS_NewRunnableFunction([wrapper]() { sCurrentThreadTLS.set(wrapper); });
aMessageLoop->PostTask(r.forget());
return wrapper.forget();
}
virtual void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
DispatchReason aReason = NormalDispatch) override
{
MOZ_RELEASE_ASSERT(aReason == NormalDispatch, "Only supports NormalDispatch");
RefPtr<Runnable> runner(new Runner(this, Move(aRunnable)));
mMessageLoop->PostTask(runner.forget());
}
virtual bool IsCurrentThreadIn() override
{
MessageLoop* messageLoop = MessageLoop::current();
bool in = (mMessageLoop == messageLoop);
return in;
}
virtual TaskDispatcher& TailDispatcher() override
{
MOZ_CRASH("Not supported!");
TaskDispatcher* dispatcher = nullptr;
return *dispatcher;
}
virtual bool MightHaveTailTasks() override
{
return false;
}
private:
explicit MessageLoopAbstractThreadWrapper(MessageLoop* aMessageLoop)
: AbstractThread(false)
, mMessageLoop(aMessageLoop)
{
}
MessageLoop* mMessageLoop;
class Runner : public CancelableRunnable {
class MOZ_STACK_CLASS AutoTaskGuard final {
public:
explicit AutoTaskGuard(MessageLoopAbstractThreadWrapper* aThread)
: mLastCurrentThread(nullptr)
{
MOZ_ASSERT(aThread);
mLastCurrentThread = sCurrentThreadTLS.get();
sCurrentThreadTLS.set(aThread);
}
~AutoTaskGuard()
{
sCurrentThreadTLS.set(mLastCurrentThread);
}
private:
AbstractThread* mLastCurrentThread;
};
public:
explicit Runner(MessageLoopAbstractThreadWrapper* aThread,
already_AddRefed<nsIRunnable> aRunnable)
: mThread(aThread)
, mRunnable(aRunnable)
{
}
NS_IMETHOD Run() override
{
AutoTaskGuard taskGuard(mThread);
MOZ_ASSERT(mThread == AbstractThread::GetCurrent());
MOZ_ASSERT(mThread->IsCurrentThreadIn());
nsresult rv = mRunnable->Run();
return rv;
}
nsresult Cancel() override
{
// Set the TLS during Cancel() just in case it calls Run().
AutoTaskGuard taskGuard(mThread);
nsresult rv = NS_OK;
// Try to cancel the runnable if it implements the right interface.
// Otherwise just skip the runnable.
nsCOMPtr<nsICancelableRunnable> cr = do_QueryInterface(mRunnable);
if (cr) {
rv = cr->Cancel();
}
return rv;
}
NS_IMETHOD GetName(nsACString& aName) override
{
aName.AssignLiteral("AbstractThread::Runner");
if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) {
nsAutoCString name;
named->GetName(name);
if (!name.IsEmpty()) {
aName.AppendLiteral(" for ");
aName.Append(name);
}
}
return NS_OK;
}
private:
RefPtr<MessageLoopAbstractThreadWrapper> mThread;
RefPtr<nsIRunnable> mRunnable;
};
};
} // namespace ipc
} // namespace mozilla
#endif // mozilla_ipc_glue_MessageLoopAbstractThreadWrapper_h

View File

@ -24,6 +24,7 @@
#include "mozilla/ipc/MessageLink.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Mutex.h"
#include "mozilla/NotNull.h"
#include "mozilla/UniquePtr.h"

View File

@ -168,6 +168,9 @@ class Visitor:
def visitExprSizeof(self, es):
self.visitExprCall(es)
def visitExprLambda(self, l):
self.visitBlock(l)
def visitStmtBlock(self, sb):
self.visitBlock(sb)
@ -292,8 +295,12 @@ class Type(Node):
ptr=0, ptrconst=0, ptrptr=0, ptrconstptr=0,
ref=0,
hasimplicitcopyctor=True,
T=None):
T=None,
inner=None):
"""
Represents the type |name<T>::inner| with the ptr and const
modifiers as specified.
To avoid getting fancy with recursive types, we limit the kinds
of pointer types that can be be constructed.
@ -318,6 +325,7 @@ Any type, naked or pointer, can be const (const T) or ref (T&).
self.ref = ref
self.hasimplicitcopyctor = hasimplicitcopyctor
self.T = T
self.inner = inner
# XXX could get serious here with recursive types, but shouldn't
# need that for this codegen
def __deepcopy__(self, memo):
@ -326,7 +334,8 @@ Any type, naked or pointer, can be const (const T) or ref (T&).
ptr=self.ptr, ptrconst=self.ptrconst,
ptrptr=self.ptrptr, ptrconstptr=self.ptrconstptr,
ref=self.ref,
T=copy.deepcopy(self.T, memo))
T=copy.deepcopy(self.T, memo),
inner=copy.deepcopy(self.inner, memo))
Type.BOOL = Type('bool')
Type.INT = Type('int')
Type.INT32 = Type('int32_t')
@ -642,12 +651,15 @@ class ExprSelect(Node):
def __init__(self, obj, op, field):
assert obj and op and field
assert not isinstance(obj, str)
assert isinstance(field, str)
assert isinstance(op, str)
Node.__init__(self)
self.obj = obj
self.op = op
self.field = field
if isinstance(field, str):
self.field = ExprVar(field)
else:
self.field = field
class ExprAssn(Node):
def __init__(self, lhs, rhs, op='='):
@ -693,6 +705,15 @@ class ExprSizeof(ExprCall):
def __init__(self, t):
ExprCall.__init__(self, ExprVar('sizeof'), [ t ])
class ExprLambda(Block):
def __init__(self, captures=[ ], params=[ ], ret=None):
Block.__init__(self)
assert isinstance(captures, list)
assert isinstance(params, list)
self.captures = captures
self.params = params
self.ret = ret
##------------------------------
# statements etc.
class StmtBlock(Block):

View File

@ -38,9 +38,19 @@ class CxxCodeGen(CodePrinter, Visitor):
if t.T is not None:
self.write('<')
t.T.accept(self)
if type(t.T) is list:
t.T[0].accept(self)
for tt in t.T[1:]:
self.write(', ')
tt.accept(self)
else:
t.T.accept(self)
self.write('>')
if t.inner is not None:
self.write('::')
t.inner.accept(self)
ts = ''
if t.ptr: ts += '*'
elif t.ptrconst: ts += '* const'
@ -345,7 +355,8 @@ class CxxCodeGen(CodePrinter, Visitor):
self.write('(')
es.obj.accept(self)
self.write(')')
self.write(es.op + es.field)
self.write(es.op)
es.field.accept(self)
def visitExprAssn(self, ea):
ea.lhs.accept(self)
@ -377,6 +388,24 @@ class CxxCodeGen(CodePrinter, Visitor):
self.write('delete ')
ed.obj.accept(self)
def visitExprLambda(self, l):
self.write('[')
ncaptures = len(l.captures)
for i, c in enumerate(l.captures):
c.accept(self)
if i != (ncaptures-1):
self.write(', ')
self.write('](')
self.writeDeclList(l.params)
self.write(')')
if l.ret:
self.write(' -> ')
l.ret.accept(self)
self.println(' {')
self.indent()
self.visitBlock(l)
self.dedent()
self.printdent('}')
def visitStmtBlock(self, b):
self.printdentln('{')

View File

@ -308,8 +308,8 @@ def _abortIfFalse(cond, msg):
ExprVar('MOZ_RELEASE_ASSERT'),
[ cond, ExprLiteral.String(msg) ]))
def _refptr(T):
return Type('RefPtr', T=T)
def _refptr(T, ptr=0, ref=0):
return Type('RefPtr', T=T, ptr=ptr, ref=ref)
def _refptrGet(expr):
return ExprCall(ExprSelect(expr, '.', 'get'))
@ -326,6 +326,22 @@ def _uniqueptr(T):
def _uniqueptrGet(expr):
return ExprCall(ExprSelect(expr, '.', 'get'))
def _tuple(types, const=0, ref=0):
return Type('Tuple', T=types, const=const, ref=ref)
def _promise(resolvetype, rejecttype, tail, resolver=False):
inner = Type('Private') if resolver else None
return Type('MozPromise', T=[resolvetype, rejecttype, tail], inner=inner)
def _makePromise(returns, side, resolver=False):
if len(returns) > 1:
resolvetype = _tuple([d.bareType(side) for d in returns])
else:
resolvetype = returns[0].bareType(side)
return _promise(resolvetype,
_PromiseRejectReason.Type(),
ExprLiteral.FALSE, resolver=resolver)
def _cxxArrayType(basetype, const=0, ref=0):
return Type('nsTArray', T=basetype, const=const, ref=ref, hasimplicitcopyctor=False)
@ -490,6 +506,15 @@ class _DestroyReason:
AbnormalShutdown = ExprVar('AbnormalShutdown')
FailedConstructor = ExprVar('FailedConstructor')
class _PromiseRejectReason:
@staticmethod
def Type():
return Type('PromiseRejectReason')
SendError = ExprVar('PromiseRejectReason::SendError')
ChannelClosed = ExprVar('PromiseRejectReason::ChannelClosed')
HandlerRejected = ExprVar('PromiseRejectReason::HandlerRejected')
##-----------------------------------------------------------------------------
## Intermediate representation (IR) nodes used during lowering
@ -914,6 +939,10 @@ class MessageDecl(ipdl.ast.MessageDecl):
or self.decl.type.isCtor()
or self.decl.type.isDtor())
def hasAsyncReturns(self):
return (self.decl.type.isAsync() and
self.returns)
def msgCtorFunc(self):
return 'Msg_%s'% (self.decl.progname)
@ -940,6 +969,13 @@ class MessageDecl(ipdl.ast.MessageDecl):
def prettyReplyName(self, pfx=''):
return pfx + self.replyCtorFunc()
def promiseName(self):
name = self.baseName()
if self.decl.type.isCtor():
name += 'Constructor'
name += 'Promise'
return name
def actorDecl(self):
return self.params[0]
@ -957,11 +993,19 @@ class MessageDecl(ipdl.ast.MessageDecl):
return Decl(d.outType(side), d.name)
else: assert 0
def makeResolverDecl(returns):
return Decl(_refptr(Type(self.promiseName()), ref=2),
'aPromise')
cxxparams = [ ]
if paramsems is not None:
cxxparams.extend([ makeDecl(d, paramsems) for d in self.params ])
if returnsems is not None:
if returnsems is 'promise' and self.returns:
pass
elif returnsems is 'resolver' and self.returns:
cxxparams.extend([ makeResolverDecl(self.returns) ])
elif returnsems is not None:
cxxparams.extend([ makeDecl(r, returnsems) for r in self.returns ])
if not implicit and self.decl.type.hasImplicitActorParam():
@ -994,6 +1038,10 @@ class MessageDecl(ipdl.ast.MessageDecl):
elif retcallsems is 'out':
cxxargs.append(ret.var())
else: assert 0
elif retsems is 'resolver':
pass
if retsems is 'resolver':
cxxargs.append(ExprMove(ExprVar('promise')))
if not implicit:
assert self.decl.type.hasImplicitActorParam()
@ -1252,7 +1300,9 @@ with some new IPDL/C++ nodes that are tuned for C++ codegen."""
Typedef(Type('mozilla::ipc::Endpoint'),
'Endpoint', ['FooSide']),
Typedef(Type('mozilla::ipc::TransportDescriptor'),
'TransportDescriptor') ])
'TransportDescriptor'),
Typedef(Type('mozilla::ipc::PromiseRejectReason'),
'PromiseRejectReason') ])
self.protocolName = None
def visitTranslationUnit(self, tu):
@ -2566,6 +2616,11 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
self.cls.addstmt(typedef)
for typedef in self.includedActorTypedefs:
self.cls.addstmt(typedef)
for md in p.messageDecls:
if self.receivesMessage(md) and md.hasAsyncReturns():
self.cls.addstmt(
Typedef(_makePromise(md.returns, self.side, resolver=True),
md.promiseName()))
self.cls.addstmt(Whitespace.NL)
@ -2578,9 +2633,10 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
if self.receivesMessage(md):
# generate Recv/Answer* interface
implicit = (not isdtor)
returnsems = 'resolver' if md.decl.type.isAsync() else 'out'
recvDecl = MethodDecl(
md.recvMethod().name,
params=md.makeCxxParams(paramsems='move', returnsems='out',
params=md.makeCxxParams(paramsems='move', returnsems=returnsems,
side=self.side, implicit=implicit),
ret=Type('mozilla::ipc::IPCResult'), virtual=1)
@ -3727,7 +3783,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
elif isdtor:
sendmethod = self.genBlockingDtorMethod(md)
elif isasync:
sendmethod = self.genAsyncSendMethod(md)
sendmethod, (recvlbl, recvcase) = self.genAsyncSendMethod(md)
else:
sendmethod = self.genBlockingSendMethod(md)
@ -3949,17 +4005,59 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
def dtorEpilogue(self, md, actorexpr):
return self.destroyActor(md, actorexpr)
def genRecvAsyncReplyCase(self, md):
lbl = CaseLabel(md.pqReplyId())
case = StmtBlock()
resolve, reason, prologue, desrej, desstmts = self.deserializeAsyncReply(
md, self.side, errfnRecv, errfnSentinel(_Result.ValuError))
ifnotpromise = StmtIf(ExprNot(ExprVar('promise')))
ifnotpromise.addifstmts(errfnRecv("Error unknown promise",
_Result.ProcessingError))
promise = _makePromise(md.returns, self.side, resolver=True)
promiseptr = _makePromise(md.returns, self.side, resolver=True)
promiseptr.ptr = 1
getpromise = [ Whitespace.NL,
StmtDecl(Decl(_refptr(promise), 'promise'),
init=ExprCall(ExprSelect(ExprCall(ExprSelect(self.protocol.callGetChannel(), '->', 'PopPromise'),
args=[ self.msgvar ]),
'.', Type('downcast', T=promise)))),
ifnotpromise ]
if len(md.returns) > 1:
resolvearg = ExprCall(ExprVar('MakeTuple'),
args=[p.var() for p in md.returns])
else:
resolvearg = md.returns[0].var()
resolvepromise = [ StmtExpr(ExprCall(ExprSelect(ExprVar('promise'), '->', 'Resolve'),
args=[ resolvearg,
ExprVar('__func__')])) ]
rejectpromise = [ StmtExpr(ExprCall(ExprSelect(ExprVar('promise'), '->', 'Reject'),
args=[ reason, ExprVar('__func__') ])) ]
ifresolve = StmtIf(resolve)
ifresolve.addifstmts(desstmts)
ifresolve.addifstmts(resolvepromise)
ifresolve.addelsestmts(desrej)
ifresolve.addelsestmts(rejectpromise)
case.addstmts(prologue)
case.addstmts(getpromise)
case.addstmt(ifresolve)
case.addstmt(StmtReturn(_Result.Processed))
return (lbl, case)
def genAsyncSendMethod(self, md):
method = MethodDefn(self.makeSendMethodDecl(md))
msgvar, stmts = self.makeMessage(md, errfnSend)
sendok, sendstmts = self.sendAsync(md, msgvar)
retvar, sendstmts = self.sendAsync(md, msgvar)
method.addstmts(stmts
+[ Whitespace.NL ]
+ self.genVerifyMessage(md.decl.type.verify, md.params,
errfnSend, ExprVar('msg__'))
+ sendstmts
+[ StmtReturn(sendok) ])
return method
+[ StmtReturn(retvar) ])
(lbl, case) = self.genRecvAsyncReplyCase(md) if md.returns else (None, None)
return method, (lbl, case)
def genBlockingSendMethod(self, md, fromActor=None):
@ -4058,12 +4156,15 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
errfnSent=errfnSentinel(_Result.ValuError))
idvar, saveIdStmts = self.saveActorId(md)
declstmts = [ StmtDecl(Decl(r.bareType(self.side), r.var().name))
for r in md.returns ]
if md.decl.type.isAsync() and md.returns:
declstmts = self.makePromise(md, errfnRecv, routingId=idvar)
case.addstmts(
stmts
+ self.transition(md)
+ [ StmtDecl(Decl(r.bareType(self.side), r.var().name))
for r in md.returns ]
+ saveIdStmts
+ declstmts
+ self.invokeRecvHandler(md)
+ [ Whitespace.NL ]
+ self.makeReply(md, errfnRecv, routingId=idvar)
@ -4104,12 +4205,90 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
return msgvar, stmts
def makePromise(self, md, errfn, routingId):
if routingId is None:
routingId = self.protocol.routingId()
if not md.decl.type.isAsync() or not md.hasReply():
return [ ]
sendok = ExprVar('sendok__')
seqno = ExprVar('seqno__')
resolve = ExprVar('resolve__')
reason = ExprVar('reason__')
promise = Type(md.promiseName())
failifsendok = StmtIf(ExprNot(sendok))
failifsendok.addifstmt(_printWarningMessage('Error sending reply'))
sendmsg = (self.setMessageFlags(md, self.replyvar, reply=1, seqno=seqno)
+ [ self.logMessage(md, self.replyvar, 'Sending reply '),
StmtDecl(Decl(Type.BOOL, sendok.name),
init=ExprCall(
ExprSelect(self.protocol.callGetChannel(),
'->', 'Send'),
args=[ self.replyvar ])),
failifsendok ])
if len(md.returns) > 1:
resolvedecl = Decl(_tuple([p.bareType(self.side) for p in md.returns],
const=1, ref=1),
'aParam')
destructexpr = ExprCall(ExprVar('Tie'),
args=[ p.var() for p in md.returns ])
else:
resolvedecl = Decl(md.returns[0].bareType(self.side), 'aParam')
destructexpr = md.returns[0].var()
promisethen = ExprLambda([ExprVar.THIS, routingId, seqno],
[resolvedecl])
promisethen.addstmts([ StmtDecl(Decl(Type.BOOL, resolve.name),
init=ExprLiteral.TRUE) ]
+ [ StmtDecl(Decl(p.bareType(self.side), p.var().name))
for p in md.returns ]
+ [ StmtExpr(ExprAssn(destructexpr, ExprVar('aParam'))),
StmtDecl(Decl(Type('IPC::Message', ptr=1), self.replyvar.name),
init=ExprCall(ExprVar(md.pqReplyCtorFunc()),
args=[ routingId ])) ]
+ [ self.checkedWrite(None, resolve, self.replyvar,
sentinelKey=resolve.name) ]
+ [ self.checkedWrite(r.ipdltype, r.var(), self.replyvar,
sentinelKey=r.name)
for r in md.returns ])
promisethen.addstmts(sendmsg)
promiserej = ExprLambda([ExprVar.THIS, routingId, seqno],
[Decl(_PromiseRejectReason.Type(), reason.name)])
promiserej.addstmts([ StmtExpr(ExprCall(ExprVar('MOZ_ASSERT'),
args=[ ExprBinary(reason, '==',
_PromiseRejectReason.HandlerRejected) ])),
StmtExpr(ExprAssn(reason, _PromiseRejectReason.HandlerRejected)),
StmtDecl(Decl(Type.BOOL, resolve.name),
init=ExprLiteral.FALSE),
StmtDecl(Decl(Type('IPC::Message', ptr=1), self.replyvar.name),
init=ExprCall(ExprVar(md.pqReplyCtorFunc()),
args=[ routingId ])),
self.checkedWrite(None, resolve, self.replyvar,
sentinelKey=resolve.name),
self.checkedWrite(None, reason, self.replyvar,
sentinelKey=reason.name) ])
promiserej.addstmts(sendmsg)
makepromise = [ Whitespace.NL,
StmtDecl(Decl(Type.INT32, seqno.name),
init=ExprCall(ExprSelect(self.msgvar, '.', 'seqno'))),
StmtDecl(Decl(_refptr(promise), 'promise'),
init=ExprNew(promise, args=[ExprVar('__func__')])),
StmtExpr(ExprCall(
ExprSelect(ExprVar('promise'), '->', 'Then'),
args=[ ExprCall(ExprVar('AbstractThread::GetCurrent')),
ExprVar('__func__'),
promisethen,
promiserej ])) ]
return makepromise
def makeReply(self, md, errfn, routingId):
if routingId is None:
routingId = self.protocol.routingId()
# TODO special cases for async ctor/dtor replies
if not md.decl.type.hasReply():
return [ ]
if md.decl.type.isAsync() and md.decl.type.hasReply():
return [ ]
replyvar = self.replyvar
return (
@ -4161,7 +4340,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
return stmts
def setMessageFlags(self, md, var, reply):
def setMessageFlags(self, md, var, reply, seqno=None):
stmts = [ ]
if md.decl.type.isSync():
@ -4179,6 +4358,11 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
stmts.append(StmtExpr(ExprCall(
ExprSelect(var, '->', 'set_reply'))))
if seqno:
stmts.append(StmtExpr(ExprCall(
ExprSelect(var, '->', 'set_seqno'),
args=[ seqno ])))
return stmts + [ Whitespace.NL ]
@ -4225,8 +4409,65 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
return stmts
def deserializeAsyncReply(self, md, side, errfn, errfnSent):
msgvar = self.msgvar
itervar = self.itervar
msgexpr = ExprAddrOf(msgvar)
isctor = md.decl.type.isCtor()
resolve = ExprVar('resolve__')
reason = ExprVar('reason__')
desresolve = [ StmtDecl(Decl(Type.BOOL, resolve.name)),
self.checkedRead(None, ExprAddrOf(resolve), msgexpr,
ExprAddrOf(itervar),
errfn, "'%s'" % resolve.name,
sentinelKey=resolve.name, errfnSentinel=errfnSent) ]
desrej = [ StmtDecl(Decl(_PromiseRejectReason.Type(), reason.name)),
self.checkedRead(None, ExprAddrOf(reason), msgexpr,
ExprAddrOf(itervar),
errfn, "'%s'" % reason.name,
sentinelKey=reason.name, errfnSentinel=errfnSent),
self.endRead(msgvar, itervar) ]
prologue = ([
self.logMessage(md, msgexpr, 'Received ',
receiving=True),
self.profilerLabel(md),
Whitespace.NL
])
def deserializeReply(self, md, replyexpr, side, errfn, errfnSentinel, actor=None):
if not md.returns:
return prologue
prologue.extend([ StmtDecl(Decl(_iterType(ptr=0), itervar.name),
initargs=[ msgvar ]) ]
+ desresolve)
start, decls, reads = 0, [], []
if isctor:
# return the raw actor handle so that its ID can be used
# to construct the "real" actor
handlevar = self.handlevar
handletype = Type('ActorHandle')
decls = [ StmtDecl(Decl(handletype, handlevar.name)) ]
reads = [ self.checkedRead(None, ExprAddrOf(handlevar), msgexpr,
ExprAddrOf(itervar),
errfn, "'%s'" % handletype.name,
sentinelKey='actor', errfnSentinel=errfnSent) ]
start = 1
stmts = (
decls + [ StmtDecl(Decl(p.bareType(side), p.var().name))
for p in md.returns ]
+ [ Whitespace.NL ]
+ reads + [ self.checkedRead(p.ipdltype, ExprAddrOf(p.var()),
msgexpr, ExprAddrOf(itervar),
errfn, "'%s'" % p.bareType(side).name,
sentinelKey=p.name, errfnSentinel=errfnSent)
for p in md.returns[start:] ]
+ [ self.endRead(msgvar, itervar) ])
return resolve, reason, prologue, desrej, stmts
def deserializeReply(self, md, replyexpr, side, errfn, errfnSentinel, actor=None, decls=False):
stmts = [ Whitespace.NL,
self.logMessage(md, replyexpr,
'Received reply ', actor, receiving=True) ]
@ -4234,10 +4475,16 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
return stmts
itervar = self.itervar
declstmts = []
if decls:
declstmts = [ StmtDecl(Decl(p.bareType(side), p.var().name))
for p in md.returns ]
stmts.extend(
[ Whitespace.NL,
StmtDecl(Decl(_iterType(ptr=0), itervar.name),
initargs= [ self.replyvar ]) ]
+ declstmts
+ [ Whitespace.NL ]
+ [ self.checkedRead(r.ipdltype, r.var(),
ExprAddrOf(self.replyvar),
ExprAddrOf(self.itervar),
@ -4250,20 +4497,38 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
def sendAsync(self, md, msgexpr, actor=None):
sendok = ExprVar('sendok__')
return (
sendok,
([ Whitespace.NL,
self.logMessage(md, msgexpr, 'Sending ', actor),
self.profilerLabel(md) ]
+ self.transition(md, actor)
+ [ Whitespace.NL,
StmtDecl(Decl(Type.BOOL, sendok.name),
init=ExprCall(
ExprSelect(self.protocol.callGetChannel(actor),
'->', 'Send'),
args=[ msgexpr ]))
])
)
retvar = sendok
if md.returns:
retpromise = ExprVar('promise__')
promise = _makePromise(md.returns, self.side, resolver=True)
promisedecl = [ Whitespace.NL,
StmtDecl(Decl(_refptr(promise), retpromise.name),
init=ExprNew(promise, args=[ExprVar('__func__')])) ]
rejectifsendok = StmtIf(ExprNot(sendok))
rejectifsendok.addifstmts(
[ StmtExpr(ExprCall(ExprSelect(retpromise, '->', 'Reject'),
args=[ _PromiseRejectReason.SendError,
ExprVar('__func__') ])) ])
sendargs = [ msgexpr ]
stmts = [ Whitespace.NL,
self.logMessage(md, msgexpr, 'Sending ', actor),
self.profilerLabel(md) ] + self.transition(md, actor)
if md.returns:
sendargs.append(ExprCall(ExprSelect(retpromise, '.', 'get')));
stmts.extend(promisedecl)
retvar = retpromise
stmts.extend([ Whitespace.NL,
StmtDecl(Decl(Type.BOOL, sendok.name),
init=ExprCall(
ExprSelect(self.protocol.callGetChannel(actor),
'->', 'Send'),
args=sendargs)) ])
if md.returns:
stmts.append(rejectifsendok)
return (retvar, stmts)
def sendBlocking(self, md, msgexpr, replyexpr, actor=None):
sendok = ExprVar('sendok__')
@ -4317,9 +4582,12 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
return ExprCall(ExprSelect(actorexpr, '->', 'DeallocSubtree'))
def invokeRecvHandler(self, md, implicit=1):
retsems = 'in'
if md.decl.type.isAsync() and md.returns:
retsems = 'resolver'
failif = StmtIf(ExprNot(
ExprCall(md.recvMethod(),
args=md.makeCxxArgs(paramsems='move', retsems='in',
args=md.makeCxxArgs(paramsems='move', retsems=retsems,
retcallsems='out',
implicit=implicit))))
failif.addifstmts([
@ -4336,12 +4604,18 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
def makeSendMethodDecl(self, md):
implicit = md.decl.type.hasImplicitActorParam()
if md.decl.type.isAsync() and md.returns:
returnsems = 'promise'
rettype = _refptr(_makePromise(md.returns, self.side))
else:
returnsems = 'out'
rettype = Type.BOOL
decl = MethodDecl(
md.sendMethod().name,
params=md.makeCxxParams(paramsems='in', returnsems='out',
params=md.makeCxxParams(paramsems='in', returnsems=returnsems,
side=self.side, implicit=implicit),
warn_unused=(self.side == 'parent'),
ret=Type.BOOL)
ret=rettype)
if md.decl.type.isCtor():
decl.ret = md.actorDecl().bareType(self.side)
return decl

View File

@ -209,6 +209,8 @@ class MessageType(IPDLType):
def isOut(self): return self.direction is OUT
def isInout(self): return self.direction is INOUT
def hasReply(self): return len(self.returns) or IPDLType.hasReply(self)
def hasImplicitActorParam(self):
return self.isCtor() or self.isDtor()
@ -1119,11 +1121,10 @@ class CheckTypes(TcheckVisitor):
"message `%s' requires more powerful send semantics than its protocol `%s' provides",
mname, pname)
if mtype.isAsync() and len(mtype.returns):
# XXX/cjones could modify grammar to disallow this ...
if (mtype.isCtor() or mtype.isDtor()) and mtype.isAsync() and mtype.returns:
self.error(loc,
"asynchronous message `%s' declares return values",
mname)
"asynchronous ctor/dtor message `%s' declares return values",
mname);
if (mtype.compress and
(not mtype.isAsync() or mtype.isCtor() or mtype.isDtor())):

View File

@ -0,0 +1,17 @@
namespace mozilla {
namespace _ipdltest {
protocol PTestAsyncReturns {
child:
async Ping() returns (bool one);
async NoReturn() returns (bool unused);
parent:
async Pong() returns (uint32_t param1, uint32_t param2);
};
} // namespace mozilla
} // namespace _ipdltest

View File

@ -0,0 +1,109 @@
#include "TestAsyncReturns.h"
#include "IPDLUnitTests.h" // fail etc.
namespace mozilla {
namespace _ipdltest {
static uint32_t sMagic1 = 0x105b59fb;
static uint32_t sMagic2 = 0x09b6f5e3;
//-----------------------------------------------------------------------------
// parent
TestAsyncReturnsParent::TestAsyncReturnsParent()
{
MOZ_COUNT_CTOR(TestAsyncReturnsParent);
}
TestAsyncReturnsParent::~TestAsyncReturnsParent()
{
MOZ_COUNT_DTOR(TestAsyncReturnsParent);
}
void
TestAsyncReturnsParent::Main()
{
if (!AbstractThread::GetCurrent()) {
fail("AbstractThread not initalized");
}
SendNoReturn()->Then(AbstractThread::GetCurrent(), __func__,
[](bool unused) {
fail("resolve handler should not be called");
},
[](PromiseRejectReason aReason) {
// MozPromise asserts in debug build if the
// handler is not called
if (aReason != PromiseRejectReason::ChannelClosed) {
fail("reject with wrong reason");
}
passed("reject handler called on channel close");
});
SendPing()->Then(AbstractThread::GetCurrent(), __func__,
[this](bool one) {
if (one) {
passed("take one argument");
} else {
fail("get one argument but has wrong value");
}
Close();
},
[](PromiseRejectReason aReason) {
fail("sending Ping");
});
}
mozilla::ipc::IPCResult
TestAsyncReturnsParent::RecvPong(RefPtr<PongPromise>&& aPromise)
{
aPromise->Resolve(MakeTuple(sMagic1, sMagic2), __func__);
return IPC_OK();
}
//-----------------------------------------------------------------------------
// child
TestAsyncReturnsChild::TestAsyncReturnsChild()
{
MOZ_COUNT_CTOR(TestAsyncReturnsChild);
}
TestAsyncReturnsChild::~TestAsyncReturnsChild()
{
MOZ_COUNT_DTOR(TestAsyncReturnsChild);
}
mozilla::ipc::IPCResult
TestAsyncReturnsChild::RecvNoReturn(RefPtr<NoReturnPromise>&& aPromise)
{
// Leak the promise intentionally
aPromise->AddRef();
return IPC_OK();
}
mozilla::ipc::IPCResult
TestAsyncReturnsChild::RecvPing(RefPtr<PingPromise>&& aPromise)
{
if (!AbstractThread::GetCurrent()) {
fail("AbstractThread not initalized");
}
SendPong()->Then(AbstractThread::GetCurrent(), __func__,
[aPromise](const Tuple<uint32_t, uint32_t>& aParam) {
if (Get<0>(aParam) == sMagic1 && Get<1>(aParam) == sMagic2) {
passed("take two arguments");
} else {
fail("get two argument but has wrong value");
}
aPromise->Resolve(true, __func__);
},
[](PromiseRejectReason aReason) {
fail("sending Pong");
});
return IPC_OK();
}
} // namespace _ipdltest
} // namespace mozilla

View File

@ -0,0 +1,62 @@
#ifndef mozilla__ipdltest_TestAsyncReturns_h
#define mozilla__ipdltest_TestAsyncReturns_h 1
#include "mozilla/_ipdltest/IPDLUnitTests.h"
#include "mozilla/_ipdltest/PTestAsyncReturnsParent.h"
#include "mozilla/_ipdltest/PTestAsyncReturnsChild.h"
namespace mozilla {
namespace _ipdltest {
class TestAsyncReturnsParent :
public PTestAsyncReturnsParent
{
public:
TestAsyncReturnsParent();
virtual ~TestAsyncReturnsParent();
static bool RunTestInProcesses() { return true; }
static bool RunTestInThreads() { return true; }
void Main();
protected:
mozilla::ipc::IPCResult RecvPong(RefPtr<PongPromise>&& aPromise) override;
virtual void ActorDestroy(ActorDestroyReason why) override
{
if (NormalShutdown != why)
fail("unexpected destruction!");
passed("ok");
QuitParent();
}
};
class TestAsyncReturnsChild :
public PTestAsyncReturnsChild
{
public:
TestAsyncReturnsChild();
virtual ~TestAsyncReturnsChild();
protected:
mozilla::ipc::IPCResult RecvPing(RefPtr<PingPromise>&& aPromise) override;
mozilla::ipc::IPCResult RecvNoReturn(RefPtr<NoReturnPromise>&& aPromise) override;
virtual void ActorDestroy(ActorDestroyReason why) override
{
if (NormalShutdown != why)
fail("unexpected destruction!");
QuitChild();
}
};
} // namespace _ipdltest
} // namespace mozilla
#endif // ifndef mozilla__ipdltest_TestAsyncReturns_h

View File

@ -15,6 +15,7 @@ EXPORTS.mozilla._ipdltest += [
SOURCES += [
'TestActorPunning.cpp',
'TestAsyncReturns.cpp',
'TestBadActor.cpp',
'TestCancel.cpp',
'TestCrashCleanup.cpp',
@ -62,6 +63,7 @@ IPDL_SOURCES += [
'PTestActorPunning.ipdl',
'PTestActorPunningPunned.ipdl',
'PTestActorPunningSub.ipdl',
'PTestAsyncReturns.ipdl',
'PTestBadActor.ipdl',
'PTestBadActorSub.ipdl',
'PTestCancel.ipdl',

View File

@ -0,0 +1,8 @@
include protocol AsyncCtorReturnsManagee;
protocol AsyncCtorReturns {
manages AsyncCtorReturnsManagee;
child:
async AsyncCtorReturnsManagee() returns (bool unused);
};

View File

@ -0,0 +1,8 @@
include protocol AsyncCtorReturns;
protocol AsyncCtorReturnsManagee {
manager AsyncCtorReturns;
parent:
async __delete__();
};

View File

@ -779,6 +779,8 @@ XRE_InitParentProcess(int aArgc,
// Set main thread before we initialize the profiler
NS_SetMainThread();
mozilla::LogModule::Init();
char aLocal;
GeckoProfilerInitRAII profiler(&aLocal);