diff --git a/dom/canvas/IpdlQueue.h b/dom/canvas/IpdlQueue.h new file mode 100644 index 000000000000..b92bd8b5e350 --- /dev/null +++ b/dom/canvas/IpdlQueue.h @@ -0,0 +1,553 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + */ +/* 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 IPDLQUEUE_H_ +#define IPDLQUEUE_H_ 1 + +#include +#include +#include +#include +#include "mozilla/dom/QueueParamTraits.h" +#include "mozilla/ipc/SharedMemoryBasic.h" +#include "mozilla/Assertions.h" +#include "mozilla/ipc/Shmem.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/Logging.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TypeTraits.h" +#include "nsString.h" +#include "mozilla/WeakPtr.h" + +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace mozilla { +namespace dom { + +using mozilla::webgl::QueueStatus; + +extern LazyLogModule gIpdlQueueLog; +#define IPDLQUEUE_LOG_(lvl, ...) \ + MOZ_LOG(mozilla::dom::gIpdlQueueLog, lvl, (__VA_ARGS__)) +#define IPDLQUEUE_LOGD(...) IPDLQUEUE_LOG_(LogLevel::Debug, __VA_ARGS__) +#define IPDLQUEUE_LOGE(...) IPDLQUEUE_LOG_(LogLevel::Error, __VA_ARGS__) + +template +class IpdlQueue; +template +class SyncConsumerActor; + +constexpr uint64_t kIllegalQueueId = 0; +inline uint64_t NewIpdlQueueId() { + static std::atomic sNextIpdlQueueId = 1; + return sNextIpdlQueueId++; +} + +struct IpdlQueueBuffer { + uint64_t id = kIllegalQueueId; + nsTArray data; + + IpdlQueueBuffer() = default; + IpdlQueueBuffer(const IpdlQueueBuffer&) = delete; + IpdlQueueBuffer(IpdlQueueBuffer&&) = default; +}; + +using IpdlQueueBuffers = nsTArray; + +// Any object larger than this will be inserted into its own Shmem. +// TODO: Base this on something. +static constexpr size_t kMaxIpdlQueueArgSize = 256 * 1024; + +template +class AsyncProducerActor { + public: + virtual bool TransmitIpdlQueueData(bool aSendSync, IpdlQueueBuffer&& aData) { + MOZ_ASSERT(aSendSync == false); + if (mResponseBuffers) { + // We are in the middle of a sync transaction. Store the data so + // that we can return it with the response. + const uint64_t id = aData.id; + for (auto& elt : *mResponseBuffers) { + if (elt.id == id) { + elt.data.AppendElements(aData.data); + return true; + } + } + mResponseBuffers->AppendElement(std::move(aData)); + return true; + } + + // We are not inside of a transaction. Send normally. + Derived* self = static_cast(this); + return self->SendTransmitIpdlQueueData( + std::forward(aData)); + } + + template + bool ShouldSendSync(const Args&...) { + return false; + } + + protected: + friend SyncConsumerActor; + + void SetResponseBuffers(IpdlQueueBuffers* aResponse) { + MOZ_ASSERT(!mResponseBuffers); + mResponseBuffers = aResponse; + } + + void ClearResponseBuffers() { + MOZ_ASSERT(mResponseBuffers); + mResponseBuffers = nullptr; + } + + IpdlQueueBuffers* mResponseBuffers = nullptr; +}; + +template +class SyncProducerActor : public AsyncProducerActor { + public: + bool TransmitIpdlQueueData(bool aSendSync, IpdlQueueBuffer&& aData) override { + Derived* self = static_cast(this); + if (mResponseBuffers || !aSendSync) { + return AsyncProducerActor::TransmitIpdlQueueData( + aSendSync, std::forward(aData)); + } + + IpdlQueueBuffers responses; + if (!self->SendExchangeIpdlQueueData(std::forward(aData), + &responses)) { + return false; + } + + for (auto& buf : responses) { + if (!self->StoreIpdlQueueData(std::move(buf))) { + return false; + } + } + return true; + } + + protected: + using AsyncProducerActor::mResponseBuffers; +}; + +template +class AsyncConsumerActor { + public: + // Returns the ipdlQueue contents that were Recv'ed in a prior IPDL + // transmission. No new data is received via IPDL during this operation. + nsTArray TakeIpdlQueueData(uint64_t aId) { + auto it = mIpdlQueueBuffers.find(aId); + if (it != mIpdlQueueBuffers.end()) { + return std::move(it->second.data); + } + return nsTArray(); + } + + protected: + friend SyncProducerActor; + + // Store data received from the producer, to be read by local IpdlConsumers. + bool StoreIpdlQueueData(IpdlQueueBuffer&& aBuffer) { + auto it = mIpdlQueueBuffers.find(aBuffer.id); + if (it == mIpdlQueueBuffers.end()) { + return mIpdlQueueBuffers.insert({aBuffer.id, std::move(aBuffer)}).second; + } + return it->second.data.AppendElements(aBuffer.data, fallible); + } + + mozilla::ipc::IPCResult RecvTransmitIpdlQueueData(IpdlQueueBuffer&& aBuffer) { + if (StoreIpdlQueueData(std::forward(aBuffer))) { + return IPC_OK(); + } + return IPC_FAIL_NO_REASON(static_cast(this)); + } + + std::unordered_map mIpdlQueueBuffers; +}; + +template +class SyncConsumerActor : public AsyncConsumerActor { + protected: + using AsyncConsumerActor::StoreIpdlQueueData; + + mozilla::ipc::IPCResult RecvExchangeIpdlQueueData( + IpdlQueueBuffer&& aBuffer, IpdlQueueBuffers* aResponse) { + uint64_t id = aBuffer.id; + if (!StoreIpdlQueueData(std::forward(aBuffer))) { + return IPC_FAIL_NO_REASON(static_cast(this)); + } + + // Mark the actor as in a sync operation, then calls handler. + // During handler, if actor is used as producer (for ALL queues) + // then instead of immediately sending, it writes the data into + // aResponse. When handler is done, we unmark the actor. + // Note that we must buffer for _all_ queues associated with the + // actor as the intended response queue is indistinguishable from + // the rest from our vantage point. + Derived* actor = static_cast(this); + actor->SetResponseBuffers(aResponse); + auto clearResponseBuffer = + MakeScopeExit([&] { actor->ClearResponseBuffers(); }); + return actor->RunQueue(id) ? IPC_OK() : IPC_FAIL_NO_REASON(actor); + } +}; + +template +class IpdlProducer final : public SupportsWeakPtr> { + nsTArray mSerializedData; + WeakPtr<_Actor> mActor; + uint64_t mId; + + public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(IpdlProducer<_Actor>) + using Actor = _Actor; + using SelfType = IpdlProducer; + + // For IPDL: + IpdlProducer() : mId(kIllegalQueueId) {} + + /** + * Insert aArgs into the queue. If the operation does not succeed then + * the queue is unchanged. + */ + template + QueueStatus TryInsert(Args&&... aArgs) { + MOZ_ASSERT(mId != kIllegalQueueId); + if (!mActor) { + NS_WARNING("TryInsert with actor that was already freed."); + return QueueStatus::kFatalError; + } + + // Fill mSerializedData with the data to send. Clear it when done. + MOZ_ASSERT(mSerializedData.IsEmpty()); + auto self = *this; + auto clearData = MakeScopeExit([&] { self.mSerializedData.Clear(); }); + const bool toSendSync = mActor->ShouldSendSync(aArgs...); + QueueStatus status = SerializeAllArgs(std::forward(aArgs)...); + if (status != QueueStatus::kSuccess) { + return status; + } + return mActor->TransmitIpdlQueueData( + toSendSync, IpdlQueueBuffer{mId, std::move(mSerializedData)}) + ? QueueStatus::kSuccess + : QueueStatus::kFatalError; + } + + /** + * Same as TryInsert. IPDL send failures are considered fatal to the + * IpdlQueue. + */ + template + QueueStatus TryWaitInsert(const Maybe&, Args&&... aArgs) { + return TryInsert(std::forward(aArgs)...); + } + + protected: + template + friend class IpdlQueue; + friend struct mozilla::ipc::IPDLParamTraits; + + explicit IpdlProducer(uint64_t aId, Actor* aActor = nullptr) + : mActor(aActor), mId(aId) {} + + template + QueueStatus SerializeAllArgs(Args&&... aArgs) { + size_t read = 0; + size_t write = 0; + mozilla::webgl::ProducerView view(this, read, &write); + size_t bytesNeeded = MinSizeofArgs(view, &aArgs...); + if (!mSerializedData.SetLength(bytesNeeded, fallible)) { + return QueueStatus::kOOMError; + } + + return SerializeArgs(view, aArgs...); + } + + QueueStatus SerializeArgs(mozilla::webgl::ProducerView& aView) { + return QueueStatus::kSuccess; + } + + template + QueueStatus SerializeArgs(mozilla::webgl::ProducerView& aView, + const Arg& aArg, const Args&... aArgs) { + QueueStatus status = SerializeArg(aView, aArg); + if (!IsSuccess(status)) { + return status; + } + return SerializeArgs(aView, aArgs...); + } + + template + QueueStatus SerializeArg(mozilla::webgl::ProducerView& aView, + const Arg& aArg) { + return mozilla::webgl::QueueParamTraits< + typename std::remove_volatile::type>::Write(aView, aArg); + } + + public: + template + QueueStatus WriteObject(size_t aRead, size_t* aWrite, const Arg& arg, + size_t aArgSize) { + // TODO: Queue needs one extra byte for PCQ (fixme). + return mozilla::webgl::Marshaller::WriteObject( + mSerializedData.Elements(), mSerializedData.Length() + 1, aRead, aWrite, + arg, aArgSize); + } + + inline bool NeedsSharedMemory(size_t aRequested) { + return aRequested >= kMaxIpdlQueueArgSize; + } + + base::ProcessId OtherPid() { return mActor ? mActor->OtherPid() : 0; } + + protected: + size_t MinSizeofArgs(mozilla::webgl::ProducerView& aView) { + return 0; + } + + template + size_t MinSizeofArgs(mozilla::webgl::ProducerView& aView, + const Arg& aArg, const Args&... aArgs) { + return aView.MinSizeParam(aArg) + MinSizeofArgs(aView, aArgs...); + } + + template + size_t MinSizeofArgs(mozilla::webgl::ProducerView& aView) { + return aView.template MinSizeParam() + MinSizeofArgs(aView); + } +}; + +template +class IpdlConsumer final : public SupportsWeakPtr> { + public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(IpdlConsumer<_Actor>) + using Actor = _Actor; + using SelfType = IpdlConsumer; + + // For IPDL + IpdlConsumer() : mId(kIllegalQueueId) {} + + /** + * Attempts to copy and remove aArgs from the queue. If the operation does + * not succeed then the queue is unchanged. If the operation returns + * kQueueNotReady then the consumer does not yet have enough data to satisfy + * the request. In this case, the IPDL MessageQueue should be given the + * opportunity to run, at which point TryRemove can be attempted again. + */ + template + QueueStatus TryRemove(Args&... aArgs) { + MOZ_ASSERT(mId != kIllegalQueueId); + if (!mActor) { + NS_WARNING("TryRemove with actor that was already freed."); + return QueueStatus::kFatalError; + } + mBuf.AppendElements(mActor->TakeIpdlQueueData(mId)); + return DeserializeAllArgs(aArgs...); + } + + /** + * Equivalent to TryRemove. Duration is ignored as it would need to + * allow the IPDL queue to run to be useful. + */ + template + QueueStatus TryWaitRemove(const Maybe&, Args&... aArgs) { + return TryRemove(aArgs...); + } + + protected: + template + friend class IpdlQueue; + friend struct mozilla::ipc::IPDLParamTraits; + + explicit IpdlConsumer(uint64_t aId, Actor* aActor = nullptr) + : mActor(aActor), mId(aId) {} + + template + QueueStatus DeserializeAllArgs(Args&... aArgs) { + size_t read = 0; + size_t write = mBuf.Length(); + mozilla::webgl::ConsumerView view(this, &read, write); + + QueueStatus status = DeserializeArgs(view, &aArgs...); + if (IsSuccess(status) && (read > 0)) { + mBuf.RemoveElementsAt(0, read); + } + return status; + } + + QueueStatus DeserializeArgs(mozilla::webgl::ConsumerView& aView) { + return QueueStatus::kSuccess; + } + + template + QueueStatus DeserializeArgs(mozilla::webgl::ConsumerView& aView, + Arg* aArg, Args*... aArgs) { + QueueStatus status = DeserializeArg(aView, aArg); + if (!IsSuccess(status)) { + return status; + } + return DeserializeArgs(aView, aArgs...); + } + + template + QueueStatus DeserializeArg(mozilla::webgl::ConsumerView& aView, + Arg* aArg) { + return mozilla::webgl:: + QueueParamTraits::Type>::Read( + aView, const_cast::type*>(aArg)); + } + + public: + template + QueueStatus ReadObject(size_t* aRead, size_t aWrite, Arg* arg, + size_t aArgSize) { + // TODO: Queue needs one extra byte for PCQ (fixme). + return mozilla::webgl::Marshaller::ReadObject( + mBuf.Elements(), mBuf.Length() + 1, aRead, aWrite, arg, aArgSize); + } + + static inline bool NeedsSharedMemory(size_t aRequested) { + return aRequested >= kMaxIpdlQueueArgSize; + } + + base::ProcessId OtherPid() { return mActor ? mActor->OtherPid() : 0; } + + protected: + WeakPtr mActor; + uint64_t mId; + nsTArray mBuf; +}; + +/** + * An IpdlQueue is a queue that uses an actor of type ActorP to send data and + * its reciprocal (i.e. child to its parent or vice-versa) to receive data. + * ActorP must derive from one of: + * AsyncProducerActor, SyncProducerActor + * ActorC must derive from one of: + * AsyncConsumerActor, SyncConsumerActor + */ +template +class IpdlQueue final { + public: + using ActorP = _ActorP; + using ActorC = _ActorC; + using Producer = IpdlProducer; + using Consumer = IpdlConsumer; + + UniquePtr TakeProducer() { return std::move(mProducer); } + UniquePtr TakeConsumer() { return std::move(mConsumer); } + + /** + * Create an IpdlQueue where the given actor is a producer and its + * reciprocal is the consumer. + * The reciprocal actor type must be typedefed in ActorC as OtherSideActor. + * For example, WebGLChild::OtherSideActor is WebGLParent. + */ + static UniquePtr> Create(ActorP* aProducerActor) { + static_assert(std::is_same::value, + "ActorP's reciprocal must be ActorC"); + static_assert(std::is_same::value, + "ActorC's reciprocal must be ActorP"); + + auto id = NewIpdlQueueId(); + return WrapUnique(new IpdlQueue( + std::move(WrapUnique(new Producer(id, aProducerActor))), + std::move(WrapUnique(new Consumer(id))))); + } + + /** + * Create an IpdlQueue where the given actor is a consumer and its + * reciprocal is the producer. + * The reciprocal actor type must be typedefed in ActorC as OtherSideActor. + * For example, WebGLChild::OtherSideActor is WebGLParent. + */ + static UniquePtr> Create(ActorC* aConsumerActor) { + static_assert(std::is_same::value, + "ActorP's reciprocal must be ActorC"); + static_assert(std::is_same::value, + "ActorC's reciprocal must be ActorP"); + + auto id = NewIpdlQueueId(); + return WrapUnique(new IpdlQueue( + std::move(WrapUnique(new Producer(id))), + std::move(WrapUnique(new Consumer(id, aConsumerActor))))); + } + + private: + IpdlQueue(UniquePtr&& aProducer, UniquePtr&& aConsumer) + : mProducer(std::move(aProducer)), mConsumer(std::move(aConsumer)) {} + + UniquePtr mProducer; + UniquePtr mConsumer; +}; + +} // namespace dom + +namespace ipc { + +template +struct IPDLParamTraits> { + typedef mozilla::dom::IpdlProducer paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + MOZ_ASSERT(aParam.mActor == nullptr); + WriteIPDLParam(aMsg, aActor, aParam.mId); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + aResult->mActor = static_cast(aActor); + return ReadIPDLParam(aMsg, aIter, aActor, &aResult->mId); + } +}; + +template +struct IPDLParamTraits> { + typedef mozilla::dom::IpdlConsumer paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + MOZ_ASSERT(aParam.mActor == nullptr); + WriteIPDLParam(aMsg, aActor, aParam.mId); + WriteIPDLParam(aMsg, aActor, aParam.mBuf); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + aResult->mActor = static_cast(aActor); + return ReadIPDLParam(aMsg, aIter, aActor, &aResult->mId) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mBuf); + } +}; + +template <> +struct IPDLParamTraits { + typedef mozilla::dom::IpdlQueueBuffer paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + WriteParam(aMsg, aParam.id); + WriteParam(aMsg, aParam.data); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->id) && + ReadParam(aMsg, aIter, &aResult->data); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // IPDLQUEUE_H_ diff --git a/dom/canvas/ProducerConsumerQueue.cpp b/dom/canvas/Queue.cpp similarity index 89% rename from dom/canvas/ProducerConsumerQueue.cpp rename to dom/canvas/Queue.cpp index a840fbc87e82..30474408f2ac 100644 --- a/dom/canvas/ProducerConsumerQueue.cpp +++ b/dom/canvas/Queue.cpp @@ -9,6 +9,7 @@ namespace mozilla { namespace webgl { mozilla::LazyLogModule gPCQLog("pcq"); +mozilla::LazyLogModule gIpdlQueueLog("ipdlqueue"); } // namespace webgl } // namespace mozilla diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 372576bc0895..65e41a003bee 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -58,6 +58,7 @@ EXPORTS.mozilla.dom += [ 'ImageBitmapSource.h', 'ImageData.h', 'ImageUtils.h', + 'IpdlQueue.h', 'OffscreenCanvas.h', 'ProducerConsumerQueue.h', 'QueueParamTraits.h', @@ -95,7 +96,7 @@ UNIFIED_SOURCES += [ 'ClientWebGLContext.cpp', 'ClientWebGLExtensions.cpp', 'HostWebGLContext.cpp', - 'ProducerConsumerQueue.cpp', + 'Queue.cpp', 'TexUnpackBlob.cpp', 'WebGL2Context.cpp', 'WebGL2ContextBuffers.cpp',