Bug 1093025: Add |ListenSocket|, r=kyle
authorThomas Zimmermann <tdz@users.sourceforge.net>
Tue, 02 Dec 2014 15:00:36 -0800
changeset 244071 1840df9fd1906227e7d782e1e964e5ee333d77c3
parent 244070 62f3c073833a0dc39c02b62e5cc8eaaa7c7bdd3c
child 244072 73e1dda14ec9fcdc43523bace69ac6cda2c86661
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskyle
bugs1093025
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1093025: Add |ListenSocket|, r=kyle The new class |ListenSocket| listens on Unix sockets for incoming connections. Accepted connections are handed over to a compatible connection-oriented socket.
ipc/unixsocket/ConnectionOrientedSocket.cpp
ipc/unixsocket/ConnectionOrientedSocket.h
ipc/unixsocket/ListenSocket.cpp
ipc/unixsocket/ListenSocket.h
ipc/unixsocket/moz.build
new file mode 100644
--- /dev/null
+++ b/ipc/unixsocket/ConnectionOrientedSocket.cpp
@@ -0,0 +1,19 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=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/. */
+
+#include "ConnectionOrientedSocket.h"
+
+namespace mozilla {
+namespace ipc {
+
+ConnectionOrientedSocketIO::~ConnectionOrientedSocketIO()
+{ }
+
+ConnectionOrientedSocket::~ConnectionOrientedSocket()
+{ }
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/ipc/unixsocket/ConnectionOrientedSocket.h
@@ -0,0 +1,47 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=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_connectionorientedsocket_h
+#define mozilla_ipc_connectionorientedsocket_h
+
+#include <sys/socket.h>
+#include "nsError.h"
+
+namespace mozilla {
+namespace ipc {
+
+union sockaddr_any;
+
+/*
+ * |ConnectionOrientedSocketIO| and |ConnectionOrientedSocket| define
+ * interfaces for implementing stream sockets on I/O and main thread.
+ * |ListenSocket| uses these classes to handle accepted sockets.
+ */
+
+class ConnectionOrientedSocketIO
+{
+public:
+  virtual nsresult Accept(int aFd,
+                          const union sockaddr_any* aAddr,
+                          socklen_t aAddrLen) = 0;
+
+protected:
+  virtual ~ConnectionOrientedSocketIO();
+};
+
+class ConnectionOrientedSocket
+{
+public:
+  virtual ConnectionOrientedSocketIO* GetIO() = 0;
+
+protected:
+  virtual ~ConnectionOrientedSocket();
+};
+
+}
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/ipc/unixsocket/ListenSocket.cpp
@@ -0,0 +1,439 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=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/. */
+
+#include "ListenSocket.h"
+#include <fcntl.h>
+#include "ConnectionOrientedSocket.h"
+#include "mozilla/RefPtr.h"
+#include "nsXULAppAPI.h"
+#include "UnixSocketConnector.h"
+
+static const size_t MAX_READ_SIZE = 1; /* any small constant */
+
+namespace mozilla {
+namespace ipc {
+
+//
+// ListenSocketIO
+//
+
+class ListenSocketIO MOZ_FINAL : public UnixSocketWatcher
+                               , protected SocketIOBase
+{
+public:
+  class ListenTask;
+
+  ListenSocketIO(MessageLoop* mIOLoop,
+                 ListenSocket* aListenSocket,
+                 UnixSocketConnector* aConnector,
+                 const nsACString& aAddress);
+  ~ListenSocketIO();
+
+  void                GetSocketAddr(nsAString& aAddrStr) const;
+  SocketConsumerBase* GetConsumer();
+  SocketBase*         GetSocketBase();
+
+  // Shutdown state
+  //
+
+  bool IsShutdownOnMainThread() const;
+  void ShutdownOnMainThread();
+
+  bool IsShutdownOnIOThread() const;
+  void ShutdownOnIOThread();
+
+  // Task callback methods
+  //
+
+  /**
+   * Run bind/listen to prepare for further runs of accept()
+   */
+  void Listen(ConnectionOrientedSocketIO* aCOSocketIO);
+
+  // I/O callback methods
+  //
+
+  void OnAccepted(int aFd, const sockaddr_any* aAddr,
+                  socklen_t aAddrLen) MOZ_OVERRIDE;
+  void OnConnected() MOZ_OVERRIDE;
+  void OnError(const char* aFunction, int aErrno) MOZ_OVERRIDE;
+  void OnListening() MOZ_OVERRIDE;
+
+private:
+  void FireSocketError();
+
+  // Set up flags on file descriptor.
+  static bool SetSocketFlags(int aFd);
+
+  /**
+   * Consumer pointer. Non-thread safe RefPtr, so should only be manipulated
+   * directly from main thread. All non-main-thread accesses should happen with
+   * mIO as container.
+   */
+  RefPtr<ListenSocket> mListenSocket;
+
+  /**
+   * Connector object used to create the connection we are currently using.
+   */
+  nsAutoPtr<UnixSocketConnector> mConnector;
+
+  /**
+   * If true, do not requeue whatever task we're running
+   */
+  bool mShuttingDownOnIOThread;
+
+  /**
+   * Address we are connecting to, assuming we are creating a client connection.
+   */
+  nsCString mAddress;
+
+  /**
+   * Size of the socket address struct
+   */
+  socklen_t mAddrSize;
+
+  /**
+   * Address struct of the socket currently in use
+   */
+  sockaddr_any mAddr;
+
+  ConnectionOrientedSocketIO* mCOSocketIO;
+};
+
+ListenSocketIO::ListenSocketIO(MessageLoop* mIOLoop,
+                               ListenSocket* aListenSocket,
+                               UnixSocketConnector* aConnector,
+                               const nsACString& aAddress)
+: UnixSocketWatcher(mIOLoop)
+, SocketIOBase(MAX_READ_SIZE)
+, mListenSocket(aListenSocket)
+, mConnector(aConnector)
+, mShuttingDownOnIOThread(false)
+, mAddress(aAddress)
+, mCOSocketIO(nullptr)
+{
+  MOZ_ASSERT(mListenSocket);
+  MOZ_ASSERT(mConnector);
+}
+
+ListenSocketIO::~ListenSocketIO()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsShutdownOnMainThread());
+}
+
+void
+ListenSocketIO::GetSocketAddr(nsAString& aAddrStr) const
+{
+  if (!mConnector) {
+    NS_WARNING("No connector to get socket address from!");
+    aAddrStr.Truncate();
+    return;
+  }
+  mConnector->GetSocketAddr(mAddr, aAddrStr);
+}
+
+SocketBase*
+ListenSocketIO::GetSocketBase()
+{
+  return mListenSocket.get();
+}
+
+bool
+ListenSocketIO::IsShutdownOnMainThread() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  return mListenSocket == nullptr;
+}
+
+void
+ListenSocketIO::ShutdownOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!IsShutdownOnMainThread());
+
+  mListenSocket = nullptr;
+}
+
+bool
+ListenSocketIO::IsShutdownOnIOThread() const
+{
+  return mShuttingDownOnIOThread;
+}
+
+void
+ListenSocketIO::ShutdownOnIOThread()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!mShuttingDownOnIOThread);
+
+  Close(); // will also remove fd from I/O loop
+  mShuttingDownOnIOThread = true;
+}
+
+void
+ListenSocketIO::Listen(ConnectionOrientedSocketIO* aCOSocketIO)
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(mConnector);
+  MOZ_ASSERT(aCOSocketIO);
+
+  if (!IsOpen()) {
+    int fd = mConnector->Create();
+    if (fd < 0) {
+      NS_WARNING("Cannot create socket fd!");
+      FireSocketError();
+      return;
+    }
+    if (!SetSocketFlags(fd)) {
+      NS_WARNING("Cannot set socket flags!");
+      FireSocketError();
+      return;
+    }
+    SetFd(fd);
+  }
+
+  mCOSocketIO = aCOSocketIO;
+
+  // This will set things we don't particularly care about, but
+  // it will hand back the correct structure size which is what
+  // we do care about.
+  if (!mConnector->CreateAddr(true, mAddrSize, mAddr, nullptr)) {
+    NS_WARNING("Cannot create socket address!");
+    FireSocketError();
+    return;
+  }
+
+  // calls OnListening on success, or OnError otherwise
+  nsresult rv = UnixSocketWatcher::Listen(
+    reinterpret_cast<struct sockaddr*>(&mAddr), mAddrSize);
+  NS_WARN_IF(NS_FAILED(rv));
+}
+
+void
+ListenSocketIO::OnAccepted(int aFd,
+                           const sockaddr_any* aAddr,
+                           socklen_t aAddrLen)
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
+  MOZ_ASSERT(mCOSocketIO);
+
+  RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
+
+  nsRefPtr<nsRunnable> runnable;
+
+  if (NS_SUCCEEDED(mCOSocketIO->Accept(aFd, aAddr, aAddrLen))) {
+    runnable =
+      new SocketIOEventRunnable<ListenSocketIO>(
+        this, SocketIOEventRunnable<ListenSocketIO>::CONNECT_SUCCESS);
+    return;
+  } else {
+    runnable =
+      new SocketIOEventRunnable<ListenSocketIO>(
+        this, SocketIOEventRunnable<ListenSocketIO>::CONNECT_ERROR);
+  }
+
+  NS_DispatchToMainThread(runnable);
+}
+
+void
+ListenSocketIO::OnConnected()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+
+  NS_NOTREACHED("Invalid call to |ListenSocketIO::OnConnected|");
+}
+
+void
+ListenSocketIO::OnListening()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_LISTENING);
+
+  if (!mConnector->SetUpListenSocket(GetFd())) {
+    NS_WARNING("Could not set up listen socket!");
+    FireSocketError();
+    return;
+  }
+
+  AddWatchers(READ_WATCHER, true);
+}
+
+void
+ListenSocketIO::OnError(const char* aFunction, int aErrno)
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+
+  UnixFdWatcher::OnError(aFunction, aErrno);
+  FireSocketError();
+}
+
+void
+ListenSocketIO::FireSocketError()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+
+  // Clean up watchers, statuses, fds
+  Close();
+
+  // Tell the main thread we've errored
+  nsRefPtr<nsRunnable> r =
+    new SocketIOEventRunnable<ListenSocketIO>(
+      this, SocketIOEventRunnable<ListenSocketIO>::CONNECT_ERROR);
+
+  NS_DispatchToMainThread(r);
+}
+
+bool
+ListenSocketIO::SetSocketFlags(int aFd)
+{
+  static const int reuseaddr = 1;
+
+  // Set socket addr to be reused even if kernel is still waiting to close
+  int res = setsockopt(aFd, SOL_SOCKET, SO_REUSEADDR,
+                       &reuseaddr, sizeof(reuseaddr));
+  if (res < 0) {
+    return false;
+  }
+
+  // Set close-on-exec bit.
+  int flags = TEMP_FAILURE_RETRY(fcntl(aFd, F_GETFD));
+  if (-1 == flags) {
+    return false;
+  }
+  flags |= FD_CLOEXEC;
+  if (-1 == TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFD, flags))) {
+    return false;
+  }
+
+  // Set non-blocking status flag.
+  flags = TEMP_FAILURE_RETRY(fcntl(aFd, F_GETFL));
+  if (-1 == flags) {
+    return false;
+  }
+  flags |= O_NONBLOCK;
+  if (-1 == TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags))) {
+    return false;
+  }
+
+  return true;
+}
+
+//
+// Socket tasks
+//
+
+class ListenSocketIO::ListenTask MOZ_FINAL
+  : public SocketIOTask<ListenSocketIO>
+{
+public:
+  ListenTask(ListenSocketIO* aIO, ConnectionOrientedSocketIO* aCOSocketIO)
+  : SocketIOTask<ListenSocketIO>(aIO)
+  , mCOSocketIO(aCOSocketIO)
+  {
+    MOZ_ASSERT(mCOSocketIO);
+  }
+
+  void Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    if (!IsCanceled()) {
+      GetIO()->Listen(mCOSocketIO);
+    }
+  }
+
+private:
+  ConnectionOrientedSocketIO* mCOSocketIO;
+};
+
+//
+// UnixSocketConsumer
+//
+
+ListenSocket::ListenSocket()
+: mIO(nullptr)
+{ }
+
+ListenSocket::~ListenSocket()
+{
+  MOZ_ASSERT(!mIO);
+}
+
+void
+ListenSocket::Close()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mIO) {
+    return;
+  }
+
+  // From this point on, we consider mIO as being deleted. We sever
+  // the relationship here so any future calls to listen or connect
+  // will create a new implementation.
+  mIO->ShutdownOnMainThread();
+
+  XRE_GetIOMessageLoop()->PostTask(
+    FROM_HERE, new SocketIOShutdownTask<ListenSocketIO>(mIO));
+
+  mIO = nullptr;
+
+  NotifyDisconnect();
+}
+
+bool
+ListenSocket::Listen(UnixSocketConnector* aConnector,
+                     ConnectionOrientedSocket* aCOSocket)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aConnector);
+  MOZ_ASSERT(aCOSocket);
+
+  nsAutoPtr<UnixSocketConnector> connector(aConnector);
+
+  if (mIO) {
+    NS_WARNING("Socket already connecting/connected!");
+    return false;
+  }
+
+  mIO = new ListenSocketIO(
+    XRE_GetIOMessageLoop(), this, connector.forget(), EmptyCString());
+
+  // Prepared I/O object, now start listening.
+  return Listen(aCOSocket);
+}
+
+bool
+ListenSocket::Listen(ConnectionOrientedSocket* aCOSocket)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mIO);
+  MOZ_ASSERT(aCOSocket);
+
+  SetConnectionStatus(SOCKET_LISTENING);
+
+  XRE_GetIOMessageLoop()->PostTask(
+    FROM_HERE, new ListenSocketIO::ListenTask(mIO, aCOSocket->GetIO()));
+
+  return true;
+}
+
+void
+ListenSocket::GetSocketAddr(nsAString& aAddrStr)
+{
+  aAddrStr.Truncate();
+  if (!mIO || GetConnectionStatus() != SOCKET_CONNECTED) {
+    NS_WARNING("No socket currently open!");
+    return;
+  }
+  mIO->GetSocketAddr(aAddrStr);
+}
+
+} // namespace ipc
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/unixsocket/ListenSocket.h
@@ -0,0 +1,78 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=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_listensocket_h
+#define mozilla_ipc_listensocket_h
+
+#include "nsString.h"
+#include "mozilla/ipc/SocketBase.h"
+
+namespace mozilla {
+namespace ipc {
+
+class ConnectionOrientedSocket;
+class ListenSocketIO;
+class UnixSocketConnector;
+
+class ListenSocket : public SocketBase
+{
+protected:
+  virtual ~ListenSocket();
+
+public:
+  ListenSocket();
+
+  /**
+   * Starts a task on the socket that will try to accept a new connection
+   * in a non-blocking manner.
+   *
+   * @param aConnector Connector object for socket-type-specific functions
+   * @param aCOSocket The connection-oriented socket for handling the
+   *                  accepted connection.
+   *
+   * @return true on listen started, false otherwise
+   */
+  bool Listen(UnixSocketConnector* aConnector,
+              ConnectionOrientedSocket* aCOSocket);
+
+  /**
+   * Starts a task on the socket that will try to accept a new connection
+   * in a non-blocking manner. This method re-uses a previously created
+   * listen socket.
+   *
+   * @param aCOSocket The connection-oriented socket for handling the
+   *                  accepted connection.
+   *
+   * @return true on listen started, false otherwise
+   */
+  bool Listen(ConnectionOrientedSocket* aCOSocket);
+
+  /**
+   * Queues the internal representation of socket for deletion. Can be called
+   * from main thread.
+   */
+  void Close();
+
+  /**
+   * Get the current sockaddr for the socket
+   */
+  void GetSocketAddr(nsAString& aAddrStr);
+
+private:
+
+  // Legacy interface from |SocketBase|; should be replaced by |Close|.
+  void CloseSocket() MOZ_OVERRIDE
+  {
+    Close();
+  }
+
+  ListenSocketIO* mIO;
+};
+
+} // namespace ipc
+} // namepsace mozilla
+
+#endif // mozilla_ipc_listensocket_h
--- a/ipc/unixsocket/moz.build
+++ b/ipc/unixsocket/moz.build
@@ -1,21 +1,25 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS.mozilla.ipc += [
+    'ConnectionOrientedSocket.h',
+    'ListenSocket.h',
     'SocketBase.h',
     'UnixSocket.h',
     'UnixSocketConnector.h'
 ]
 
 SOURCES += [
+    'ConnectionOrientedSocket.cpp',
+    'ListenSocket.cpp',
     'SocketBase.cpp',
     'UnixSocket.cpp',
     'UnixSocketConnector.cpp'
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')