Add RefCounting for TransportProxies

BUG=1574
R=pthatcher@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/37869004

Cr-Commit-Position: refs/heads/master@{#8238}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8238 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index 7144e76..83e613e 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -1482,7 +1482,7 @@
     SignalVideoChannelDestroyed();
     const std::string content_name = video_channel_->content_name();
     channel_manager_->DestroyVideoChannel(video_channel_.release());
-    DestroyTransportProxy(content_name);
+    DestroyTransportProxyWhenUnused(content_name);
   }
 
   const cricket::ContentInfo* voice_info =
@@ -1492,7 +1492,7 @@
     SignalVoiceChannelDestroyed();
     const std::string content_name = voice_channel_->content_name();
     channel_manager_->DestroyVoiceChannel(voice_channel_.release());
-    DestroyTransportProxy(content_name);
+    DestroyTransportProxyWhenUnused(content_name);
   }
 
   const cricket::ContentInfo* data_info =
@@ -1502,7 +1502,7 @@
     SignalDataChannelDestroyed();
     const std::string content_name = data_channel_->content_name();
     channel_manager_->DestroyDataChannel(data_channel_.release());
-    DestroyTransportProxy(content_name);
+    DestroyTransportProxyWhenUnused(content_name);
   }
 }
 
diff --git a/webrtc/p2p/base/session.cc b/webrtc/p2p/base/session.cc
index 93a4a2b..69b6a9c 100644
--- a/webrtc/p2p/base/session.cc
+++ b/webrtc/p2p/base/session.cc
@@ -30,11 +30,12 @@
 using rtc::Bind;
 
 TransportProxy::~TransportProxy() {
-  for (ChannelMap::iterator iter = channels_.begin();
-       iter != channels_.end(); ++iter) {
-    iter->second->SignalDestroyed(iter->second);
-    delete iter->second;
-  }
+  ASSERT(channels_.empty());
+}
+
+bool TransportProxy::HasChannels() const {
+  ASSERT(rtc::Thread::Current() == worker_thread_);
+  return !channels_.empty();
 }
 
 const std::string& TransportProxy::type() const {
@@ -49,22 +50,35 @@
 TransportChannel* TransportProxy::CreateChannel(const std::string& name,
                                                 int component) {
   ASSERT(rtc::Thread::Current() == worker_thread_);
-  ASSERT(GetChannel(component) == NULL);
-  ASSERT(!transport_->get()->HasChannel(component));
 
-  // We always create a proxy in case we need to change out the transport later.
-  TransportChannelProxy* channel_proxy =
-      new TransportChannelProxy(content_name(), name, component);
-  channels_[component] = channel_proxy;
+  TransportChannelProxyRef* channel_proxy;
+  if (channels_.find(component) == channels_.end()) {
+    channel_proxy =
+        new TransportChannelProxyRef(content_name(), name, component);
+    channels_[component] = channel_proxy;
 
-  // If we're already negotiated, create an impl and hook it up to the proxy
-  // channel. If we're connecting, create an impl but don't hook it up yet.
-  if (negotiated_) {
-    CreateChannelImpl_w(component);
-    SetChannelImplFromTransport_w(channel_proxy, component);
-  } else if (connecting_) {
-    CreateChannelImpl_w(component);
+    // TransportProxy maintains its own reference
+    // here. RefCountedObject automatically deletes the pointer when
+    // the refcount hits 0. This prevents RefCountedObject from
+    // deleting the object when all *external* references are
+    // gone. Things need to be done in DestroyChannel prior to the
+    // proxy being deleted.
+    channel_proxy->AddRef();
+
+    // If we're already negotiated, create an impl and hook it up to the proxy
+    // channel. If we're connecting, create an impl but don't hook it up yet.
+    if (negotiated_) {
+      CreateChannelImpl_w(component);
+      SetChannelImplFromTransport_w(channel_proxy, component);
+    } else if (connecting_) {
+      CreateChannelImpl_w(component);
+    }
+  } else {
+    channel_proxy = channels_[component];
   }
+
+  channel_proxy->AddRef();
+
   return channel_proxy;
 }
 
@@ -74,22 +88,37 @@
 
 void TransportProxy::DestroyChannel(int component) {
   ASSERT(rtc::Thread::Current() == worker_thread_);
-  TransportChannelProxy* channel_proxy = GetChannelProxy(component);
-  if (channel_proxy) {
-    // If the state of TransportProxy is not NEGOTIATED then
-    // TransportChannelProxy and its impl are not connected. Both must
-    // be connected before deletion.
-    //
-    // However, if we haven't entered the connecting state then there
-    // is no implementation to hook up.
-    if (connecting_ && !negotiated_) {
-      SetChannelImplFromTransport_w(channel_proxy, component);
-    }
 
-    channels_.erase(component);
-    channel_proxy->SignalDestroyed(channel_proxy);
-    delete channel_proxy;
+  ChannelMap::const_iterator iter = channels_.find(component);
+  if (iter == channels_.end()) {
+    return;
   }
+
+  TransportChannelProxyRef* channel_proxy = iter->second;
+  int ref_count = channel_proxy->Release();
+  if (ref_count > 1) {
+    return;
+  }
+  // TransportProxy owns the last reference on the TransportChannelProxy.
+  // It should *never* be the case that ref_count is less than one
+  // here but this makes me sleep better at night.
+  ASSERT(ref_count == 1);
+
+  // If the state of TransportProxy is not NEGOTIATED then
+  // TransportChannelProxy and its impl are not connected. Both must
+  // be connected before deletion.
+  //
+  // However, if we haven't entered the connecting state then there
+  // is no implementation to hook up.
+  if (connecting_ && !negotiated_) {
+    SetChannelImplFromTransport_w(channel_proxy, component);
+  }
+
+  channels_.erase(component);
+  channel_proxy->SignalDestroyed(channel_proxy);
+
+  // Implicitly deletes the object since ref_count is now 0.
+  channel_proxy->Release();
 }
 
 void TransportProxy::ConnectChannels() {
@@ -590,8 +619,15 @@
   return transports_.begin()->second;
 }
 
-void BaseSession::DestroyTransportProxy(
+void BaseSession::DestroyTransportProxyWhenUnused(
     const std::string& content_name) {
+  TransportProxy *tp = GetTransportProxy(content_name);
+  if(tp && !tp->HasChannels()) {
+    DestroyTransportProxy(content_name);
+  }
+}
+
+void BaseSession::DestroyTransportProxy(const std::string& content_name) {
   TransportMap::iterator iter = transports_.find(content_name);
   if (iter != transports_.end()) {
     delete iter->second;
diff --git a/webrtc/p2p/base/session.h b/webrtc/p2p/base/session.h
index 7f2d816..7e89123 100644
--- a/webrtc/p2p/base/session.h
+++ b/webrtc/p2p/base/session.h
@@ -19,6 +19,7 @@
 #include "webrtc/p2p/base/candidate.h"
 #include "webrtc/p2p/base/port.h"
 #include "webrtc/p2p/base/transport.h"
+#include "webrtc/p2p/base/transportchannelproxy.h"
 #include "webrtc/base/refcount.h"
 #include "webrtc/base/scoped_ptr.h"
 #include "webrtc/base/scoped_ref_ptr.h"
@@ -30,11 +31,12 @@
 class P2PTransportChannel;
 class Transport;
 class TransportChannel;
-class TransportChannelProxy;
 class TransportChannelImpl;
 
-typedef rtc::RefCountedObject<rtc::scoped_ptr<Transport> >
-TransportWrapper;
+// Transport and TransportChannelProxy are incomplete types. Thus we
+// wrap them in a scoped_ptr.
+typedef rtc::RefCountedObject<rtc::scoped_ptr<Transport> > TransportWrapper;
+typedef rtc::RefCountedObject<TransportChannelProxy> TransportChannelProxyRef;
 
 // Bundles a Transport and ChannelMap together. ChannelMap is used to
 // create transport channels before receiving or sending a session
@@ -42,7 +44,7 @@
 // session had one ChannelMap and transport.  Now, with multiple
 // transports per session, we need multiple ChannelMaps as well.
 
-typedef std::map<int, TransportChannelProxy*> ChannelMap;
+typedef std::map<int, TransportChannelProxyRef*> ChannelMap;
 
 class TransportProxy : public sigslot::has_slots<>,
                        public CandidateTranslator {
@@ -82,9 +84,14 @@
   }
 
   TransportChannel* GetChannel(int component);
+
+  // Returns the channel for component. This channel may be used by
+  // others and should only be deleted by calling DestroyChannel.
   TransportChannel* CreateChannel(const std::string& channel_name,
                                   int component);
   bool HasChannel(int component);
+
+  // Destroy the channel for component.
   void DestroyChannel(int component);
 
   void AddSentCandidates(const Candidates& candidates);
@@ -118,6 +125,9 @@
   virtual bool GetComponentFromChannelName(
       const std::string& channel_name, int* component) const;
 
+  // Determine if TransportProxy has any active channels.
+  bool HasChannels() const;
+
   // Called when a transport signals that it has new candidates.
   void OnTransportCandidatesReady(cricket::Transport* transport,
                                   const Candidates& candidates) {
@@ -346,6 +356,8 @@
   TransportProxy* GetTransportProxy(const Transport* transport);
   TransportProxy* GetFirstTransportProxy();
   void DestroyTransportProxy(const std::string& content_name);
+  // Calls DestroyTransportProxy if the TransportProxy is not used.
+  void DestroyTransportProxyWhenUnused(const std::string& content_name);
   // TransportProxy is owned by session.  Return proxy just for convenience.
   TransportProxy* GetOrCreateTransportProxy(const std::string& content_name);
   // Creates the actual transport object. Overridable for testing.
diff --git a/webrtc/p2p/base/session_unittest.cc b/webrtc/p2p/base/session_unittest.cc
new file mode 100644
index 0000000..904cc88
--- /dev/null
+++ b/webrtc/p2p/base/session_unittest.cc
@@ -0,0 +1,54 @@
+/*
+ *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/p2p/base/session.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cricket {
+
+class BaseSessionTest : public testing::Test, public BaseSession {
+ protected:
+  BaseSessionTest() :
+      BaseSession(rtc::Thread::Current(), rtc::Thread::Current(),
+                  NULL, "sid", "video?", true) {
+  }
+
+  ~BaseSessionTest() {}
+};
+
+// Tests that channels are not being deleted until all refcounts are
+// used and that the TransportProxy is not removed unless all channels
+// are removed from the proxy.
+TEST_F(BaseSessionTest, TransportChannelProxyRefCounter) {
+  std::string content_name = "no matter";
+  int component = 10;
+  TransportChannel* channel = CreateChannel(content_name, "", component);
+  TransportChannel* channel_again = CreateChannel(content_name, "", component);
+
+  EXPECT_EQ(channel, channel_again);
+  EXPECT_EQ(channel, GetChannel(content_name, component));
+
+  DestroyChannel(content_name, component);
+  EXPECT_EQ(channel, GetChannel(content_name, component));
+
+  // Try to destroy a non-existant content name.
+  DestroyTransportProxyWhenUnused("other content");
+  EXPECT_TRUE(GetTransportProxy(content_name) != NULL);
+
+  DestroyChannel(content_name, component);
+  EXPECT_EQ(NULL, GetChannel(content_name, component));
+  EXPECT_TRUE(GetTransportProxy(content_name) != NULL);
+
+  DestroyTransportProxyWhenUnused(content_name);
+  EXPECT_TRUE(GetTransportProxy(content_name) == NULL);
+}
+
+}  // namespace cricket
diff --git a/webrtc/p2p/p2p_tests.gypi b/webrtc/p2p/p2p_tests.gypi
index afab33b..f9e6959 100644
--- a/webrtc/p2p/p2p_tests.gypi
+++ b/webrtc/p2p/p2p_tests.gypi
@@ -22,6 +22,7 @@
           'base/pseudotcp_unittest.cc',
           'base/relayport_unittest.cc',
           'base/relayserver_unittest.cc',
+          'base/session_unittest.cc',
           'base/stun_unittest.cc',
           'base/stunport_unittest.cc',
           'base/stunrequest_unittest.cc',