Bug 1288308 - Part 1: implement nsNamedPipeService; r=bagder,mayhemer

MozReview-Commit-ID: Q4shjYz2gK
This commit is contained in:
Liang-Heng Chen 2016-09-30 23:13:36 +08:00
Родитель f928a5954c
Коммит 901781de67
6 изменённых файлов: 498 добавлений и 0 удалений

Просмотреть файл

@ -895,6 +895,18 @@
{ 0x85, 0x44, 0x5a, 0x8d, 0x1a, 0xb7, 0x95, 0x37 } \
}
#ifdef XP_WIN
#define NS_NAMEDPIPESERVICE_CONTRACTID \
"@mozilla.org/network/named-pipe-service;1"
#define NS_NAMEDPIPESERVICE_CID \
{ \
0xae298cf9, \
0x91f4, \
0x4337, \
{ 0x95, 0x69, 0x61, 0x88, 0xb9, 0xd0, 0x21, 0x6e } \
}
#endif
/******************************************************************************
* netwerk/cookie classes
*/

Просмотреть файл

@ -291,6 +291,15 @@ namespace net {
} // namespace net
} // namespace mozilla
#ifdef XP_WIN
#include "../socket/nsNamedPipeService.h"
namespace mozilla {
namespace net {
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NamedPipeService, Init)
} // namespace net
} // namespace mozilla
#endif
#ifdef NECKO_PROTOCOL_res
// resource
#include "nsResProtocolHandler.h"
@ -756,6 +765,9 @@ NS_DEFINE_NAMED_CID(NS_BUFFEREDOUTPUTSTREAM_CID);
NS_DEFINE_NAMED_CID(NS_MIMEINPUTSTREAM_CID);
NS_DEFINE_NAMED_CID(NS_PROTOCOLPROXYSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_STREAMCONVERTERSERVICE_CID);
#if defined(XP_WIN)
NS_DEFINE_NAMED_CID(NS_NAMEDPIPESERVICE_CID);
#endif
NS_DEFINE_NAMED_CID(NS_DASHBOARD_CID);
#ifdef NECKO_PROTOCOL_ftp
NS_DEFINE_NAMED_CID(NS_FTPDIRLISTINGCONVERTER_CID);
@ -902,6 +914,9 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
{ &kNS_MIMEINPUTSTREAM_CID, false, nullptr, nsMIMEInputStreamConstructor },
{ &kNS_PROTOCOLPROXYSERVICE_CID, true, nullptr, nsProtocolProxyServiceConstructor },
{ &kNS_STREAMCONVERTERSERVICE_CID, false, nullptr, CreateNewStreamConvServiceFactory },
#if defined (XP_WIN)
{ &kNS_NAMEDPIPESERVICE_CID, false, NULL, mozilla::net::NamedPipeServiceConstructor },
#endif
{ &kNS_DASHBOARD_CID, false, nullptr, mozilla::net::DashboardConstructor },
#ifdef NECKO_PROTOCOL_ftp
{ &kNS_FTPDIRLISTINGCONVERTER_CID, false, nullptr, CreateNewFTPDirListingConv },
@ -1050,6 +1065,9 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
{ NS_MIMEINPUTSTREAM_CONTRACTID, &kNS_MIMEINPUTSTREAM_CID },
{ NS_PROTOCOLPROXYSERVICE_CONTRACTID, &kNS_PROTOCOLPROXYSERVICE_CID },
{ NS_STREAMCONVERTERSERVICE_CONTRACTID, &kNS_STREAMCONVERTERSERVICE_CID },
#if defined(XP_WIN)
{ NS_NAMEDPIPESERVICE_CONTRACTID, &kNS_NAMEDPIPESERVICE_CID },
#endif
{ NS_DASHBOARD_CONTRACTID, &kNS_DASHBOARD_CID },
#ifdef NECKO_PROTOCOL_ftp
{ NS_ISTREAMCONVERTER_KEY FTP_TO_INDEX, &kNS_FTPDIRLISTINGCONVERTER_CID },

Просмотреть файл

@ -21,4 +21,12 @@ UNIFIED_SOURCES += [
'nsUDPSocketProvider.cpp',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
XPIDL_SOURCES += [
'nsINamedPipeService.idl',
]
UNIFIED_SOURCES += [
'nsNamedPipeService.cpp'
]
FINAL_LIBRARY = 'xul'

Просмотреть файл

@ -0,0 +1,77 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#include "nsISupports.idl"
#include "nsrootidl.idl"
/**
* nsINamedPipeDataObserver
*
* This is the callback interface for nsINamedPipeService.
* The functions are called by the internal thread in the nsINamedPipeService.
*/
[scriptable, uuid(de4f460b-94fd-442c-9002-1637beb2185a)]
interface nsINamedPipeDataObserver : nsISupports
{
/**
* onDataAvailable
*
* @param aBytesTransferred
* Transfered bytes during last transmission.
* @param aOverlapped
* Corresponding overlapped structure used by the async I/O
*/
void onDataAvailable(in unsigned long aBytesTransferred,
in voidPtr aOverlapped);
/**
* onError
*
* @param aError
* Error code of the error.
* @param aOverlapped
* Corresponding overlapped structure used by the async I/O
*/
void onError(in unsigned long aError,
in voidPtr aOverlapped);
};
/**
* nsINamedPipeService
*/
[scriptable, uuid(1bf19133-5625-4ac8-836a-80b1c215f72b)]
interface nsINamedPipeService : nsISupports
{
/**
* addDataObserver
*
* @param aHandle
* The handle that is going to be monitored for read/write operations.
* Only handles that are opened with overlapped IO are supported.
* @param aObserver
* The observer of the handle, the service strong-refs of the observer.
*/
void addDataObserver(in voidPtr aHandle,
in nsINamedPipeDataObserver aObserver);
/**
* removeDataObserver
*
* @param aHandle
The handle associated to the observer, and will be closed by the
service.
* @param aObserver
* The observer to be removed.
*/
void removeDataObserver(in voidPtr aHandle,
in nsINamedPipeDataObserver aObserver);
/**
* isOnCurrentThread
*
* @return the caller runs within the internal thread.
*/
boolean isOnCurrentThread();
};

Просмотреть файл

@ -0,0 +1,324 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "mozilla/Services.h"
#include "nsCOMPtr.h"
#include "nsIObserverService.h"
#include "nsIThread.h"
#include "nsNamedPipeService.h"
#include "nsNetCID.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace net {
static mozilla::LazyLogModule gNamedPipeServiceLog("NamedPipeWin");
#define LOG_NPS_DEBUG(...) \
MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
#define LOG_NPS_ERROR(...) \
MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__))
NS_IMPL_ISUPPORTS(NamedPipeService,
nsINamedPipeService,
nsIObserver,
nsIRunnable)
NamedPipeService::NamedPipeService()
: mIocp(nullptr)
, mIsShutdown(false)
, mLock("NamedPipeServiceLock")
{
}
nsresult
NamedPipeService::Init()
{
MOZ_ASSERT(!mIsShutdown);
nsresult rv;
// nsIObserverService must be accessed in main thread.
// register shutdown event to stop NamedPipeSrv thread.
nsCOMPtr<nsIObserver> self(this);
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self = Move(self)] () -> void {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> svc = mozilla::services::GetObserverService();
if (NS_WARN_IF(!svc)) {
return;
}
if (NS_WARN_IF(NS_FAILED(svc->AddObserver(self,
NS_XPCOM_SHUTDOWN_OBSERVER_ID,
false)))) {
return;
}
});
if (NS_IsMainThread()) {
rv = r->Run();
} else {
rv = NS_DispatchToMainThread(r);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
if (NS_WARN_IF(!mIocp || mIocp == INVALID_HANDLE_VALUE)) {
Shutdown();
return NS_ERROR_FAILURE;
}
rv = NS_NewNamedThread("NamedPipeSrv", getter_AddRefs(mThread));
if (NS_WARN_IF(NS_FAILED(rv))) {
Shutdown();
return rv;
}
return NS_OK;
}
void
NamedPipeService::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
// remove observer
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
}
// stop thread
if (mThread && !mIsShutdown) {
mIsShutdown = true;
// invoke ERROR_ABANDONED_WAIT_0 to |GetQueuedCompletionStatus|
CloseHandle(mIocp);
mIocp = nullptr;
mThread->Shutdown();
}
// close I/O Completion Port
if (mIocp && mIocp != INVALID_HANDLE_VALUE) {
CloseHandle(mIocp);
mIocp = nullptr;
}
}
void
NamedPipeService::RemoveRetiredObjects()
{
MOZ_ASSERT(NS_GetCurrentThread() == mThread);
mLock.AssertCurrentThreadOwns();
if (!mRetiredHandles.IsEmpty()) {
for (auto& handle : mRetiredHandles) {
CloseHandle(handle);
}
mRetiredHandles.Clear();
}
mRetiredObservers.Clear();
}
/**
* Implement nsINamedPipeService
*/
NS_IMETHODIMP
NamedPipeService::AddDataObserver(void* aHandle,
nsINamedPipeDataObserver* aObserver)
{
if (!aHandle || aHandle == INVALID_HANDLE_VALUE || !aObserver) {
return NS_ERROR_ILLEGAL_VALUE;
}
nsresult rv;
HANDLE h = CreateIoCompletionPort(aHandle,
mIocp,
reinterpret_cast<ULONG_PTR>(aObserver),
1);
if (NS_WARN_IF(!h)) {
LOG_NPS_ERROR("CreateIoCompletionPort error (%d)", GetLastError());
return NS_ERROR_FAILURE;
}
if (NS_WARN_IF(h != mIocp)) {
LOG_NPS_ERROR("CreateIoCompletionPort got unexpected value %p (should be %p)",
h,
mIocp);
CloseHandle(h);
return NS_ERROR_FAILURE;
}
{
MutexAutoLock lock(mLock);
MOZ_ASSERT(!mObservers.Contains(aObserver));
mObservers.AppendElement(aObserver);
// start event loop
if (mObservers.Length() == 1) {
rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
LOG_NPS_ERROR("Dispatch to thread failed (%08x)", rv);
mObservers.Clear();
return rv;
}
}
}
return NS_OK;
}
NS_IMETHODIMP
NamedPipeService::RemoveDataObserver(void* aHandle,
nsINamedPipeDataObserver* aObserver)
{
MutexAutoLock lock(mLock);
mObservers.RemoveElement(aObserver);
mRetiredHandles.AppendElement(aHandle);
mRetiredObservers.AppendElement(aObserver);
return NS_OK;
}
NS_IMETHODIMP
NamedPipeService::IsOnCurrentThread(bool* aRetVal)
{
MOZ_ASSERT(mThread);
MOZ_ASSERT(aRetVal);
if (!mThread) {
*aRetVal = false;
return NS_OK;
}
return mThread->IsOnCurrentThread(aRetVal);
}
/**
* Implement nsIObserver
*/
NS_IMETHODIMP
NamedPipeService::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(NS_IsMainThread());
if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
Shutdown();
}
return NS_OK;
}
/**
* Implement nsIRunnable
*/
NS_IMETHODIMP
NamedPipeService::Run()
{
MOZ_ASSERT(NS_GetCurrentThread() == mThread);
MOZ_ASSERT(mIocp && mIocp != INVALID_HANDLE_VALUE);
while (!mIsShutdown) {
{
MutexAutoLock lock(mLock);
if (mObservers.IsEmpty()) {
LOG_NPS_DEBUG("no observer, stop loop");
break;
}
RemoveRetiredObjects();
}
DWORD bytesTransferred = 0;
ULONG_PTR key = 0;
LPOVERLAPPED overlapped = nullptr;
BOOL success = GetQueuedCompletionStatus(mIocp,
&bytesTransferred,
&key,
&overlapped,
1000); // timeout, 1s
auto err = GetLastError();
if (!success) {
if (err == WAIT_TIMEOUT) {
continue;
} else if (err == ERROR_ABANDONED_WAIT_0) { // mIocp was closed
break;
} else if (!overlapped) {
/**
* Did not dequeue a completion packet from the completion port, and
* bytesTransferred/key are meaningless.
* See remarks of |GetQueuedCompletionStatus| API.
*/
LOG_NPS_ERROR("invalid overlapped (%d)", err);
continue;
}
MOZ_ASSERT(key);
}
/**
* Windows doesn't provide a method to remove created I/O Completion Port,
* all we can do is just close the handle we monitored before.
* In some cases, there's race condition that the monitored handle has an
* I/O status after the observer is being removed and destroyed.
* To avoid changing the ref-count of a dangling pointer, don't use nsCOMPtr
* here.
*/
nsINamedPipeDataObserver* target =
reinterpret_cast<nsINamedPipeDataObserver*>(key);
nsCOMPtr<nsINamedPipeDataObserver> obs;
{
MutexAutoLock lock(mLock);
auto idx = mObservers.IndexOf(target);
if (idx == decltype(mObservers)::NoIndex) {
LOG_NPS_ERROR("observer %p not found", target);
continue;
}
obs = target;
}
MOZ_ASSERT(obs.get());
if (success) {
LOG_NPS_DEBUG("OnDataAvailable: obs=%p, bytes=%d",
obs.get(),
bytesTransferred);
obs->OnDataAvailable(bytesTransferred, overlapped);
} else {
LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%d",
obs.get(),
err);
obs->OnError(err, overlapped);
}
}
{
MutexAutoLock lock(mLock);
RemoveRetiredObjects();
}
return NS_OK;
}
static NS_DEFINE_CID(kNamedPipeServiceCID, NS_NAMEDPIPESERVICE_CID);
} // namespace net
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,59 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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_netwerk_socket_nsNamedPipeService_h
#define mozilla_netwerk_socket_nsNamedPipeService_h
#include <Windows.h>
#include "mozilla/Atomics.h"
#include "mozilla/Mutex.h"
#include "nsINamedPipeService.h"
#include "nsIObserver.h"
#include "nsIRunnable.h"
#include "nsTArray.h"
namespace mozilla {
namespace net {
class NamedPipeService final : public nsINamedPipeService
, public nsIObserver
, public nsIRunnable
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSINAMEDPIPESERVICE
NS_DECL_NSIOBSERVER
NS_DECL_NSIRUNNABLE
explicit NamedPipeService();
nsresult Init();
private:
virtual ~NamedPipeService() = default;
void Shutdown();
void RemoveRetiredObjects();
HANDLE mIocp; // native handle to the I/O completion port.
Atomic<bool> mIsShutdown; // set to true to stop the event loop running by mThread.
nsCOMPtr<nsIThread> mThread; // worker thread to get I/O events.
/**
* The observers is maintained in |mObservers| to ensure valid life-cycle.
* We don't remove the handle and corresponding observer directly, instead
* the handle and observer into a "retired" list and close/remove them in
* the worker thread to avoid a race condition that might happen between
* |CloseHandle()| and |GetQueuedCompletionStatus()|.
*/
Mutex mLock;
nsTArray<nsCOMPtr<nsINamedPipeDataObserver>> mObservers; // protected by mLock
nsTArray<nsCOMPtr<nsINamedPipeDataObserver>> mRetiredObservers; // protected by mLock
nsTArray<HANDLE> mRetiredHandles; // protected by mLock
};
} // namespace net
} // namespace mozilla
#endif // mozilla_netwerk_socket_nsNamedPipeService_h