bug 66150 - don't allow more than N open connections to news servers. r/sr=bienvenu
authorJoshua Cranmer <Pidgeot18@gmail.com>
Thu, 18 Dec 2008 11:31:25 -0500
changeset 1451 8d9d29ef8bac01a7891da3301e913e8bef515de9
parent 1450 76d42f0ec79fb14d2dd4eda02927d6194af56ef9
child 1452 ecfbea3ab9e1e7a2d4de7d880e6b6ad20b6dea87
push idunknown
push userunknown
push dateunknown
bugs66150
bug 66150 - don't allow more than N open connections to news servers. r/sr=bienvenu
mailnews/news/public/nsINntpIncomingServer.idl
mailnews/news/src/Makefile.in
mailnews/news/src/nsNNTPProtocol.cpp
mailnews/news/src/nsNntpIncomingServer.cpp
mailnews/news/src/nsNntpIncomingServer.h
mailnews/news/src/nsNntpMockChannel.cpp
mailnews/news/src/nsNntpMockChannel.h
mailnews/news/src/nsNntpService.cpp
mailnews/news/src/nsNntpService.h
mailnews/news/test/unit/head_server_setup.js
mailnews/news/test/unit/test_server.js
--- a/mailnews/news/public/nsINntpIncomingServer.idl
+++ b/mailnews/news/public/nsINntpIncomingServer.idl
@@ -15,16 +15,17 @@
  * The Original Code is mozilla.org code.
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1998
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
+ *   Joshua Cranmer <Pidgeot18@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -35,20 +36,22 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsILocalFile;
 interface nsIMsgNewsFolder;
 interface nsINNTPProtocol;
+interface nsNNTPProtocol;
+interface nsIChannel;
 interface nsIURI;
 interface nsIMsgWindow;
 
-[scriptable, uuid(325e8bac-7640-4263-a735-958140dda20b)]
+[scriptable, uuid(113e9e21-3313-4e73-83f1-0d675251b607)]
 interface nsINntpIncomingServer : nsISupports {
     /* the on-disk path to the newsrc file for this server */
     attribute nsILocalFile newsrcFilePath;
 
     /* the newsrc root path (the directories all the newsrc files live) */
     attribute nsILocalFile newsrcRootPath;
     
     /* ask the user before downloading more than maxArticles? */
@@ -74,30 +77,70 @@ interface nsINntpIncomingServer : nsISup
     /* the server keeps track of all the newsgroups we are subscribed to */
     void addNewsgroup(in AString name);
     void removeNewsgroup(in AString name);
 
     void writeNewsrcFile();
 
     attribute boolean newsrcHasChanged;
 
+    /**
+     * The maximum number of connections to make to the server.
+     *
+     * This preference (internally max_cached_connections) controls how many
+     * connections we can make. A negative connection count is treated as only
+     * one connection, while 0 (the default) loads the default number of
+     * connections, presently 2.
+     */
     attribute long maximumConnectionsNumber;
 
     readonly attribute long numGroupsNeedingCounts;
     readonly attribute nsISupports firstGroupNeedingCounts;
 
     void displaySubscribedGroup(in nsIMsgNewsFolder msgFolder,
                                 in long firstMessage, in long lastMessage,
                                 in long totalMessages);
 
 
-    void getNntpConnection(in nsIURI url, in nsIMsgWindow window,
-                           out nsINNTPProtocol aNntpConnection);
+    /**
+     * Get a new NNTP channel to run the URI.
+     *
+     * If the server has used up all of its connections, this will place the URI
+     * in the queue to be run when one is freed.
+     *
+     * @param uri    The URI to run.
+     * @param window The standard message window object.
+     */
+    nsIChannel getNntpChannel(in nsIURI uri, in nsIMsgWindow window);
+    /**
+     * Enqueues a URI to be run when we have a free connection.
+     *
+     * If there is one already free, it will be immediately started.
+     *
+     * @param uri      The URI to run.
+     * @param window   The standard message window object.
+     * @param consumer An argument to be passed to nsINNTPProtocol:LoadNewUrl.
+     */
+    void loadNewsUrl(in nsIURI uri, in nsIMsgWindow window,
+                     in nsISupports consumer);
+
+    /**
+     * Remove a connection from our connection cache.
+     *
+     * @param aNntpConnection The connection to be removed.
+     */
     void removeConnection(in nsINNTPProtocol aNntpConnection);
 
+    /**
+     * Load the next URI in the queue to the given connection.
+     *
+     * @param aNntpConnection The newly-freed connection.
+     */
+    [noscript] void prepareForNextUrl(in nsNNTPProtocol aNntpConnection);
+
     /* used for auto subscribing */
     boolean containsNewsgroup(in AUTF8String name);
     void subscribeToNewsgroup(in AUTF8String name);
 
     /* used for the subscribe dialog.
        name is encoded in |charset|  (attribute declared above) */
     [noscript] void addNewsgroupToList(in string name);
 
--- a/mailnews/news/src/Makefile.in
+++ b/mailnews/news/src/Makefile.in
@@ -75,16 +75,17 @@ CPPSRCS		= \
 		nsNNTPArticleList.cpp \
 		nsNNTPNewsgroupList.cpp \
 		nsNNTPNewsgroupPost.cpp \
 		nsNNTPProtocol.cpp \
 		nsNntpUrl.cpp \
 		nsNntpService.cpp \
 		nsNewsFolder.cpp \
 		nsNntpIncomingServer.cpp \
+		nsNntpMockChannel.cpp \
 		nsNewsUtils.cpp \
 		nsNewsDownloadDialogArgs.cpp \
 		nsNewsDownloader.cpp \
 		$(NULL)
 
 EXTRA_DSO_LDOPTS = \
 		$(MOZ_COMPONENT_LIBS) \
 		$(MOZ_UNICHARUTIL_LIBS) \
--- a/mailnews/news/src/nsNNTPProtocol.cpp
+++ b/mailnews/news/src/nsNNTPProtocol.cpp
@@ -5251,18 +5251,21 @@ nsresult nsNNTPProtocol::ProcessProtocol
       * a new connection.  If it was not new lets start it over
       * again.  But only if we didn't have any successful protocol
       * dialog at all.
       */
       FinishMemCacheEntry(PR_FALSE);  // cleanup mem cache entry
       if (m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NOTFOUND && m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
         return CloseConnection();
     case NEWS_FREE:
-      m_lastActiveTimeStamp = PR_Now(); // remmeber when we last used this connection.
-      return CleanupAfterRunningUrl();
+      // Remember when we last used this connection
+      m_lastActiveTimeStamp = PR_Now();
+      CleanupAfterRunningUrl();
+      if (m_nntpServer)
+        m_nntpServer->PrepareForNextUrl(this);
     case NEWS_FINISHED:
       return NS_OK;
       break;
     default:
       /* big error */
       return NS_ERROR_FAILURE;
 
     } // end switch
--- a/mailnews/news/src/nsNntpIncomingServer.cpp
+++ b/mailnews/news/src/nsNntpIncomingServer.cpp
@@ -512,79 +512,147 @@ nsNntpIncomingServer::CreateProtocolInst
   if (NS_SUCCEEDED(rv) && *aNntpConnection)
     mConnectionCache.AppendObject(*aNntpConnection);
   return rv;
 }
 
 /* By default, allow the user to open at most this many connections to one news host */
 #define kMaxConnectionsPerHost 2
 
-NS_IMETHODIMP
+nsresult
 nsNntpIncomingServer::GetNntpConnection(nsIURI * aUri, nsIMsgWindow *aMsgWindow,
-                                           nsINNTPProtocol ** aNntpConnection)
+                                        nsINNTPProtocol ** aNntpConnection)
 {
-  nsresult rv = NS_OK;
-  nsCOMPtr<nsINNTPProtocol> connection;
-  nsCOMPtr<nsINNTPProtocol> freeConnection;
-  PRBool isBusy = PR_TRUE;
-
-
+  // Get our maximum connection count. We need at least 1. If the value is 0,
+  // we use the default. If it's negative, we treat that as 1.
   PRInt32 maxConnections = kMaxConnectionsPerHost;
-  rv = GetMaximumConnectionsNumber(&maxConnections);
+  nsresult rv = GetMaximumConnectionsNumber(&maxConnections);
   if (NS_FAILED(rv) || maxConnections == 0)
   {
     maxConnections = kMaxConnectionsPerHost;
-    rv = SetMaximumConnectionsNumber(maxConnections);
+    SetMaximumConnectionsNumber(maxConnections);
   }
   else if (maxConnections < 1)
-  {   // forced to use at least 1
+  {
     maxConnections = 1;
-    rv = SetMaximumConnectionsNumber(maxConnections);
+    SetMaximumConnectionsNumber(maxConnections);
   }
 
-  *aNntpConnection = nsnull;
-  // iterate through the connection cache for a connection that can handle this url.
+  // Find a non-busy connection
+  nsCOMPtr<nsINNTPProtocol> connection;
   PRInt32 cnt = mConnectionCache.Count();
-
-#ifdef DEBUG_seth
-  printf("XXX there are %d nntp connections in the conn cache.\n", (int)cnt);
-#endif
-  for (PRInt32 i = 0; i < cnt && isBusy; i++)
+  for (PRInt32 i = 0; i < cnt; i++)
   {
     connection = mConnectionCache[i];
     if (connection)
-        rv = connection->GetIsBusy(&isBusy);
-    if (NS_FAILED(rv))
     {
-        connection = nsnull;
-        continue;
-    }
-    if (!freeConnection && !isBusy && connection)
-    {
-       freeConnection = connection;
+      PRBool isBusy;
+      connection->GetIsBusy(&isBusy);
+      if (!isBusy)
+        break;
+      connection = nsnull;
     }
   }
 
-  if (ConnectionTimeOut(freeConnection))
-      freeConnection = nsnull;
+  if (ConnectionTimeOut(connection))
+  {
+    connection = nsnull;
+    // We have one less connection, since we closed this one.
+    --cnt;
+  }
 
-  // if we got here and we have a connection, then we should return it!
-  if (!isBusy && freeConnection)
+  if (connection)
+  {
+    NS_IF_ADDREF(*aNntpConnection = connection);
+    connection->SetIsCachedConnection(PR_TRUE);
+  }
+  else if (cnt < maxConnections)
   {
-    *aNntpConnection = freeConnection;
-    freeConnection->SetIsCachedConnection(PR_TRUE);
-    NS_IF_ADDREF(*aNntpConnection);
+    // We have room for another connection. Create this connection and return
+    // it to the caller.
+    rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
-  else // have no queueing mechanism - just create the protocol instance.
+  else
   {
-    rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow);
+    // We maxed out our connection count. The caller must therefore enqueue the
+    // call.
+    *aNntpConnection = nsnull;
+    return NS_OK;
   }
-  return rv;
+
+  // Initialize the URI here and now.
+  return (*aNntpConnection)->Initialize(aUri, aMsgWindow);
 }
 
+NS_IMETHODIMP
+nsNntpIncomingServer::GetNntpChannel(nsIURI *aURI, nsIMsgWindow *aMsgWindow,
+                                     nsIChannel **aChannel)
+{
+  NS_ENSURE_ARG_POINTER(aChannel);
+
+  nsCOMPtr<nsINNTPProtocol> protocol;
+  nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (protocol)
+    return CallQueryInterface(protocol, aChannel);
+
+  // No protocol? We need our mock channel.
+  nsNntpMockChannel *channel = new nsNntpMockChannel(aURI, aMsgWindow);
+  if (!channel)
+    return NS_ERROR_OUT_OF_MEMORY;
+  NS_ADDREF(*aChannel = channel);
+
+  m_queuedChannels.AppendElement(channel);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::LoadNewsUrl(nsIURI *aURI, nsIMsgWindow *aMsgWindow,
+                                  nsISupports *aConsumer)
+{
+  nsCOMPtr<nsINNTPProtocol> protocol;
+  nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (protocol)
+    return protocol->LoadNewsUrl(aURI, aConsumer);
+
+  // No protocol? We need our mock channel.
+  nsNntpMockChannel *channel = new nsNntpMockChannel(aURI, aMsgWindow,
+                                                     aConsumer);
+  if (!channel)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  m_queuedChannels.AppendElement(channel);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpIncomingServer::PrepareForNextUrl(nsNNTPProtocol *aConnection)
+{
+  NS_ENSURE_ARG(aConnection);
+
+  // Start the connection on the next URL in the queue. If it can't get a URL to
+  // work, drop that URL (the channel will handle failure notification) and move
+  // on.
+  while (m_queuedChannels.Length() > 0)
+  {
+    nsRefPtr<nsNntpMockChannel> channel = m_queuedChannels[0];
+    m_queuedChannels.RemoveElementAt(0);
+    nsresult rv = channel->AttachNNTPConnection(*aConnection);
+    // If this succeeded, the connection is now running the URL.
+    if (NS_SUCCEEDED(rv))
+      return NS_OK;
+  }
+  
+  // No queued uris.
+  return NS_OK;
+}
 
 /* void RemoveConnection (in nsINNTPProtocol aNntpConnection); */
 NS_IMETHODIMP nsNntpIncomingServer::RemoveConnection(nsINNTPProtocol *aNntpConnection)
 {
   if (aNntpConnection)
     mConnectionCache.RemoveObject(aNntpConnection);
 
   return NS_OK;
--- a/mailnews/news/src/nsNntpIncomingServer.h
+++ b/mailnews/news/src/nsNntpIncomingServer.h
@@ -52,16 +52,19 @@
 #include "nsISubscribableServer.h"
 #include "nsITimer.h"
 #include "nsILocalFile.h"
 #include "nsITreeView.h"
 #include "nsITreeSelection.h"
 #include "nsIAtom.h"
 #include "nsCOMArray.h"
 
+#include "nsNntpMockChannel.h"
+#include "nsAutoPtr.h"
+
 class nsINntpUrl;
 class nsIMsgMailNewsUrl;
 
 /* get some implementation from nsMsgIncomingServer */
 class nsNntpIncomingServer : public nsMsgIncomingServer,
                              public nsINntpIncomingServer,
                              public nsIUrlListener,
                              public nsISubscribableServer,
@@ -100,20 +103,24 @@ public:
     NS_IMETHOD GetSocketType(PRInt32 *aSocketType); // override nsMsgIncomingServer impl
     NS_IMETHOD SetSocketType(PRInt32 aSocketType); // override nsMsgIncomingServer impl
 
     nsresult AppendIfSearchMatch(nsCString& newsgroupName);
 
 protected:
    virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri,
                                             nsIMsgFolder **rootFolder);
-    nsresult CreateProtocolInstance(nsINNTPProtocol ** aNntpConnection, nsIURI *url,
-                                             nsIMsgWindow *window);
+    nsresult GetNntpConnection(nsIURI *url, nsIMsgWindow *window,
+                               nsINNTPProtocol **aNntpConnection);
+    nsresult CreateProtocolInstance(nsINNTPProtocol **aNntpConnection,
+                                    nsIURI *url, nsIMsgWindow *window);
     PRBool ConnectionTimeOut(nsINNTPProtocol* aNntpConnection);
     nsCOMArray<nsINNTPProtocol> mConnectionCache;
+    nsTArray<nsRefPtr<nsNntpMockChannel> > m_queuedChannels;
+
     NS_IMETHOD GetServerRequiresPasswordForBiff(PRBool *aServerRequiresPasswordForBiff);
     nsresult SetupNewsrcSaveTimer();
     static void OnNewsrcSaveTimer(nsITimer *timer, void *voidIncomingServer);
     void WriteLine(nsIOutputStream *stream, nsCString &str);
 
 private:
     nsCStringArray mSubscribedNewsgroups;
     nsCStringArray mGroupsOnServer;
new file mode 100644
--- /dev/null
+++ b/mailnews/news/src/nsNntpMockChannel.cpp
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joshua Cranmer <Pidgeot18@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsNntpMockChannel.h"
+
+#include "msgCore.h"
+#include "nsNNTPProtocol.h"
+#include "nsNetUtil.h"
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsNntpMockChannel, nsIChannel, nsIRequest)
+
+nsNntpMockChannel::nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow)
+: m_url(aUri),
+  m_msgWindow(aMsgWindow),
+  m_channelState(CHANNEL_UNOPENED),
+  m_protocol(nsnull),
+  m_cancelStatus(NS_OK),
+  m_loadFlags(0),
+  m_contentLength(-1)
+{
+}
+
+nsNntpMockChannel::nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow,
+                                     nsISupports *aConsumer)
+: m_url(aUri),
+  m_context(aConsumer),
+  m_msgWindow(aMsgWindow),
+  m_channelState(CHANNEL_OPEN_WITH_LOAD),
+  m_protocol(nsnull),
+  m_cancelStatus(NS_OK),
+  m_loadFlags(0),
+  m_contentLength(-1)
+{
+}
+
+nsNntpMockChannel::~nsNntpMockChannel()
+{
+}
+
+#define FORWARD_CALL(function, argument) \
+  if (m_protocol) \
+    return m_protocol->function(argument);
+
+////////////////////////
+// nsIRequest methods //
+////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::GetName(nsACString &result)
+{
+  FORWARD_CALL(GetName, result)
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::IsPending(PRBool *result)
+{
+  FORWARD_CALL(IsPending, result)
+  // We haven't been loaded yet, so we're still pending.
+  *result = PR_TRUE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetStatus(nsresult *status)
+{
+  FORWARD_CALL(GetStatus, status)
+  *status = m_cancelStatus;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Cancel(nsresult status)
+{
+  m_cancelStatus = status;
+  m_channelState = CHANNEL_CLOSED;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Suspend()
+{
+  NS_NOTREACHED("nsNntpMockChannel::Suspend");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Resume()
+{
+  NS_NOTREACHED("nsNntpMockChannel::Resume");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+  FORWARD_CALL(SetLoadGroup, aLoadGroup)
+  m_loadGroup = aLoadGroup;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+  FORWARD_CALL(GetLoadGroup, aLoadGroup)
+  NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+  FORWARD_CALL(GetLoadFlags, aLoadFlags)
+  *aLoadFlags = m_loadFlags;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+  FORWARD_CALL(SetLoadFlags, aLoadFlags)
+  m_loadFlags = aLoadFlags;
+  return NS_OK;
+}
+
+////////////////////////
+// nsIChannel methods //
+////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::GetOriginalURI(nsIURI **aURI)
+{
+  FORWARD_CALL(GetOriginalURI, aURI)
+  NS_IF_ADDREF(*aURI = m_originalUrl);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetOriginalURI(nsIURI *aURI)
+{
+  FORWARD_CALL(SetOriginalURI, aURI)
+  m_originalUrl = aURI;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetURI(nsIURI **aURI)
+{
+  NS_IF_ADDREF(*aURI = m_url);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetOwner(nsISupports **owner)
+{
+  FORWARD_CALL(GetOwner, owner)
+  NS_IF_ADDREF(*owner = m_owner);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetOwner(nsISupports *aOwner)
+{
+  FORWARD_CALL(SetOwner, aOwner)
+  m_owner = aOwner;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetNotificationCallbacks(nsIInterfaceRequestor **callbacks)
+{
+  FORWARD_CALL(GetNotificationCallbacks, callbacks)
+  NS_IF_ADDREF(*callbacks = m_notificationCallbacks);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+  FORWARD_CALL(SetNotificationCallbacks, aCallbacks)
+  m_notificationCallbacks = aCallbacks;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetSecurityInfo(nsISupports **securityInfo)
+{
+  FORWARD_CALL(GetSecurityInfo, securityInfo)
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentType(nsACString &aContentType)
+{
+  FORWARD_CALL(GetContentType, aContentType)
+  aContentType = m_contentType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetContentType(const nsACString &aContentType)
+{
+  FORWARD_CALL(SetContentType, aContentType)
+  return NS_ParseContentType(aContentType, m_contentType, m_contentCharset);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentCharset(nsACString &aCharset)
+{
+  FORWARD_CALL(GetContentCharset, aCharset)
+  aCharset = m_contentCharset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetContentCharset(const nsACString &aCharset)
+{
+  FORWARD_CALL(SetContentCharset, aCharset)
+  m_contentCharset = aCharset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentLength(PRInt32 *length)
+{
+  FORWARD_CALL(GetContentLength, length)
+  *length = m_contentLength;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetContentLength(PRInt32 aLength)
+{
+  FORWARD_CALL(SetContentLength, aLength)
+  m_contentLength = aLength;
+  return NS_OK;
+}
+
+////////////////////////////////////////
+// nsIChannel and nsNNTPProtocol glue //
+////////////////////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::Open(nsIInputStream **_retval)
+{
+  return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::AsyncOpen(nsIStreamListener *listener,
+                                           nsISupports *ctxt)
+{
+  m_channelState = CHANNEL_OPEN_WITH_ASYNC;
+  m_channelListener = listener;
+  m_context = ctxt;
+  return NS_OK;
+}
+
+nsresult
+nsNntpMockChannel::AttachNNTPConnection(nsNNTPProtocol &protocol)
+{
+  // First things first. Were we canceled? If so, tell the protocol.
+  if (m_channelState == CHANNEL_CLOSED || m_channelState == CHANNEL_UNOPENED)
+    return NS_ERROR_FAILURE;
+
+  // We're going to active the protocol now. Note that if the user has
+  // interacted with us through the nsIChannel API, we need to pass it to the
+  // protocol instance. We also need to initialize it. For best results, we're
+  // going to initialize the code and then set the values.
+  nsresult rv = protocol.Initialize(m_url, m_msgWindow);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Variable fun
+  protocol.SetLoadGroup(m_loadGroup);
+  protocol.SetLoadFlags(m_loadFlags);
+  protocol.SetOriginalURI(m_originalUrl);
+  protocol.SetOwner(m_owner);
+  protocol.SetNotificationCallbacks(m_notificationCallbacks);
+  protocol.SetContentType(m_contentType);
+
+  // Now that we've set up the protocol, attach it to ourselves so that we can
+  // forward all future calls to the protocol instance. We do not refcount this
+  // instance, since the server will be owning all of them: once the server
+  // releases its reference, the protocol instance is no longer usable anyways.
+  m_protocol = &protocol;
+
+  switch (m_channelState)
+  {
+  case CHANNEL_OPEN_WITH_LOAD:
+    rv = protocol.LoadNewsUrl(m_url, m_context);
+    break;
+  case CHANNEL_OPEN_WITH_ASYNC:
+    rv = protocol.AsyncOpen(m_channelListener, m_context);
+    break;
+  default:
+    NS_NOTREACHED("Unknown channel state got us here.");
+    return NS_ERROR_FAILURE;
+  }
+
+  // If we fail, that means that loading the NNTP protocol failed. Since we
+  // essentially promised that we would load (by virtue of returning NS_OK to
+  // AsyncOpen), we must now tell our listener the bad news.
+  if (NS_FAILED(rv) && m_channelListener)
+    m_channelListener->OnStopRequest(this, m_context, rv);
+
+  // Returning a failure code is our way of telling the server that this URL
+  // isn't going to run, so it should give the connection the next URL in the
+  // queue.
+  return rv;
+}
new file mode 100644
--- /dev/null
+++ b/mailnews/news/src/nsNntpMockChannel.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joshua Cranmer <Pidgeot18@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsNntpMockChannel_h___
+#define nsNntpMockChannel_h___
+
+#include "nsIChannel.h"
+#include "nsIMsgWindow.h"
+
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+class nsNNTPProtocol;
+
+class nsNntpMockChannel : public nsIChannel
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICHANNEL
+  NS_DECL_NSIREQUEST
+
+  nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow);
+  nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow,
+                    nsISupports *aConsumer);
+  virtual ~nsNntpMockChannel();
+
+  nsresult AttachNNTPConnection(nsNNTPProtocol &protocol);
+protected:
+  // The URL we will be running
+  nsCOMPtr<nsIURI> m_url;
+
+  // Variables for arguments to pass into the opening phase.
+  nsCOMPtr<nsIStreamListener> m_channelListener;
+  nsCOMPtr<nsISupports> m_context;
+  nsCOMPtr<nsIMsgWindow> m_msgWindow;
+
+  // The state we're in
+  enum
+  {
+    CHANNEL_UNOPENED,        //!< No one bothered to open this yet
+    CHANNEL_OPEN_WITH_LOAD,  //!< We should open with LoadNewsUrl
+    CHANNEL_OPEN_WITH_ASYNC, //!< We should open with AsyncOpen
+    CHANNEL_CLOSED           //!< We were closed and should not open
+  } m_channelState;
+
+  // The protocol instance
+  nsNNTPProtocol *m_protocol;
+
+  // Temporary variables for accessors before we get to the actual instance.
+  nsresult m_cancelStatus;
+  nsCOMPtr<nsILoadGroup> m_loadGroup;
+  nsLoadFlags m_loadFlags;
+
+  nsCOMPtr<nsIURI> m_originalUrl;
+  nsCOMPtr<nsISupports> m_owner;
+  nsCOMPtr<nsIInterfaceRequestor> m_notificationCallbacks;
+  nsCString m_contentType;
+  nsCString m_contentCharset;
+  PRInt32 m_contentLength;
+};
+
+#endif  // nsNntpMockChannel_h___
--- a/mailnews/news/src/nsNntpService.cpp
+++ b/mailnews/news/src/nsNntpService.cpp
@@ -41,17 +41,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "msgCore.h"    // precompiled header...
 #include "nntpCore.h"
 #include "nsISupportsObsolete.h"
 #include "nsMsgNewsCID.h"
 #include "nsINntpUrl.h"
-#include "nsNNTPProtocol.h"
+#include "nsIMsgNewsFolder.h"
 #include "nsNNTPNewsgroupPost.h"
 #include "nsIMsgMailSession.h"
 #include "nsIMsgIdentity.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsNewsUtils.h"
 #include "nsNewsDatabase.h"
 #include "nsMsgDBCID.h"
@@ -1082,17 +1082,17 @@ nsNntpService::CreateNewsAccount(const c
   // Now save the new acct info to pref file.
   rv = accountManager->SaveAccountInfo();
   if (NS_FAILED(rv)) return rv;
 
   return NS_OK;
 }
 
 nsresult
-nsNntpService::GetProtocolForUri(nsIURI *aUri, nsIMsgWindow *aMsgWindow, nsINNTPProtocol **aProtocol)
+nsNntpService::GetServerForUri(nsIURI *aUri, nsINntpIncomingServer **aServer)
 {
   nsCAutoString hostName;
   nsCAutoString scheme;
   nsCAutoString path;
   PRInt32 port = 0;
   nsresult rv;
 
   rv = aUri->GetAsciiHost(hostName);
@@ -1180,16 +1180,18 @@ nsNntpService::GetProtocolForUri(nsIURI 
   if (NS_FAILED(rv)) return rv;
   if (!server) return NS_ERROR_FAILURE;
 
   nntpServer = do_QueryInterface(server, &rv);
 
   if (!nntpServer || NS_FAILED(rv))
     return rv;
 
+  NS_IF_ADDREF(*aServer = nntpServer);
+
   nsCAutoString spec;
   rv = aUri->GetSpec(spec);
   NS_ENSURE_SUCCESS(rv,rv);
 
 #if 0 // this not ready yet.
   nsNewsAction action = nsINntpUrl::ActionUnknown;
   nsCOMPtr <nsINntpUrl> nntpUrl = do_QueryInterface(aUri);
   if (nntpUrl) {
@@ -1219,20 +1221,17 @@ nsNntpService::GetProtocolForUri(nsIURI 
       PRBool hasMsgOffline = PR_FALSE;
       folder->HasMsgOffline(key, &hasMsgOffline);
       nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(aUri));
       if (msgUrl)
         msgUrl->SetMsgIsInLocalCache(hasMsgOffline);
     }
   }
 
-  rv = nntpServer->GetNntpConnection(aUri, aMsgWindow, aProtocol);
-  if (NS_FAILED(rv) || !*aProtocol)
-    return NS_ERROR_OUT_OF_MEMORY;
-  return rv;
+  return NS_OK;
 }
 
 PRBool nsNntpService::WeAreOffline()
 {
   nsresult rv = NS_OK;
   PRBool offline = PR_FALSE;
 
   nsCOMPtr<nsIIOService> netService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
@@ -1246,25 +1245,21 @@ nsresult
 nsNntpService::RunNewsUrl(nsIURI * aUri, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer)
 {
   nsresult rv;
 
   if (WeAreOffline())
     return NS_MSG_ERROR_OFFLINE;
 
   // almost there...now create a nntp protocol instance to run the url in...
-  nsCOMPtr <nsINNTPProtocol> nntpProtocol;
-  rv = GetProtocolForUri(aUri, aMsgWindow, getter_AddRefs(nntpProtocol));
+  nsCOMPtr<nsINntpIncomingServer> server;
+  rv = GetServerForUri(aUri, getter_AddRefs(server));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  if (NS_SUCCEEDED(rv))
-    rv = nntpProtocol->Initialize(aUri, aMsgWindow);
-  if (NS_FAILED(rv)) return rv;
-
-  rv = nntpProtocol->LoadNewsUrl(aUri, aConsumer);
-  return rv;
+  return server->LoadNewsUrl(aUri, aMsgWindow, aConsumer);
 }
 
 NS_IMETHODIMP nsNntpService::GetNewNews(nsINntpIncomingServer *nntpServer, const char *uri, PRBool aGetOld, nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI **_retval)
 {
   NS_ENSURE_ARG_POINTER(uri);
 
   NS_LOCK_INSTANCE();
   nsresult rv = NS_OK;
@@ -1404,23 +1399,20 @@ NS_IMETHODIMP nsNntpService::NewURI(cons
     NS_ADDREF(*_retval = nntpUri);
     return NS_OK;
 }
 
 NS_IMETHODIMP nsNntpService::NewChannel(nsIURI *aURI, nsIChannel **_retval)
 {
   NS_ENSURE_ARG_POINTER(aURI);
   nsresult rv = NS_OK;
-  nsCOMPtr <nsINNTPProtocol> nntpProtocol;
-  rv = GetProtocolForUri(aURI, nsnull, getter_AddRefs(nntpProtocol));
-  if (NS_SUCCEEDED(rv))
-    rv = nntpProtocol->Initialize(aURI, nsnull);
-  if (NS_FAILED(rv)) return rv;
-
-  return CallQueryInterface(nntpProtocol, _retval);
+  nsCOMPtr<nsINntpIncomingServer> server;
+  rv = GetServerForUri(aURI, getter_AddRefs(server));
+  NS_ENSURE_SUCCESS(rv, rv);
+  return server->GetNntpChannel(aURI, nsnull, _retval);
 }
 
 NS_IMETHODIMP
 nsNntpService::SetDefaultLocalPath(nsILocalFile *aPath)
 {
     NS_ENSURE_ARG(aPath);
     return NS_SetPersistentFile(PREF_MAIL_ROOT_NNTP_REL, PREF_MAIL_ROOT_NNTP, aPath);
 }
@@ -1778,24 +1770,30 @@ nsNntpService::HandleContent(const char 
   if (PL_strncasecmp(aContentType, "x-application-newsgroup", 23) == 0)
   {
     nsCOMPtr<nsIURI> uri;
     rv = aChannel->GetURI(getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
     if (uri)
     {
       nsCString uriStr;
+      rv = uri->GetSpec(uriStr);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      PRInt32 cutChar = uriStr.FindChar('?');
+      NS_ASSERTION(cutChar > 0, "No query in the list-ids?");
+      if (cutChar > 0)
+        uriStr.SetLength(cutChar);
+
+      // Try to get a valid folder...
       nsCOMPtr <nsIMsgFolder> msgFolder;
-      nsCOMPtr <nsINNTPProtocol> protocol = do_QueryInterface(aChannel);
-      if (protocol)
-        protocol->GetCurrentFolder(getter_AddRefs(msgFolder));
+      GetFolderFromUri(uriStr.get(), getter_AddRefs(msgFolder));
+
+      // ... and only go on if we have a valid URI (i.e., valid folder)
       if (msgFolder)
-        msgFolder->GetURI(uriStr);
-
-      if (!uriStr.IsEmpty())
       {
         nsCOMPtr <nsIURI> originalUri;
         aChannel->GetOriginalURI(getter_AddRefs(originalUri));
         if (originalUri)
         {
           nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(originalUri);
           if (mailUrl)
           {
--- a/mailnews/news/src/nsNntpService.h
+++ b/mailnews/news/src/nsNntpService.h
@@ -89,17 +89,17 @@ protected:
   nsresult FindHostFromGroup(nsCString &host, nsCString &groupName);
   nsresult FindServerWithNewsgroup(nsCString &host, nsCString &groupName);
 
   nsresult CreateMessageIDURL(nsIMsgFolder *folder, nsMsgKey key, char **url);
   nsresult GetMessageFromUrl(nsIURI *aUrl, nsIMsgWindow *aMsgWindow, nsISupports *aDisplayConsumer);
   // a convience routine used to put together news urls
   nsresult ConstructNntpUrl(const char * urlString, nsIUrlListener *aUrlListener,  nsIMsgWindow * aMsgWindow, const char *originalMessageUri, PRInt32 action, nsIURI ** aUrl);
   nsresult CreateNewsAccount(const char *aHostname, PRBool aIsSecure, PRInt32 aPort, nsIMsgIncomingServer **aServer);
-  nsresult GetProtocolForUri(nsIURI *aUri, nsIMsgWindow *aMsgWindow, nsINNTPProtocol **aProtocol);
+  nsresult GetServerForUri(nsIURI *aUri, nsINntpIncomingServer **aProtocol);
   // a convience routine to run news urls
   nsresult RunNewsUrl (nsIURI * aUrl, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer);
   // a convience routine to go from folder uri to msg folder
   nsresult GetFolderFromUri(const char *uri, nsIMsgFolder **folder);
   static PRBool findNewsServerWithGroup(nsISupports *aElement, void *data);
   nsresult DecomposeNewsMessageURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey);
 
   PRBool            mPrintingOperation; // Flag for printing operations
--- a/mailnews/news/test/unit/head_server_setup.js
+++ b/mailnews/news/test/unit/head_server_setup.js
@@ -91,16 +91,18 @@ function setupLocalServer(port) {
   server.valid = true;
 
   // Subscribe to certain posts
   server.QueryInterface(Ci.nsINntpIncomingServer);
   groups.forEach(function (element) {
       if (element[1])
         server.subscribeToNewsgroup(element[0]);
     });
+  // Only allow one connection
+  server.maximumConnectionsNumber = 1;
 
   _server = server;
   
   return server;
 }
 
 const URLCreator = Cc["@mozilla.org/messenger/messageservice;1?type=news"]
                      .getService(Ci.nsINntpService)
@@ -111,42 +113,36 @@ function setupProtocolTest(port, newsUrl
   var url;
   if (newsUrl instanceof Ci.nsIMsgMailNewsUrl) { 
     url = newsUrl;
   } else {
     url = URLCreator.newURI(newsUrl, null, null);
   }
   server = setupLocalServer(port);
   
-  var connection = {};
-  server.getNntpConnection(url, null, connection);
-  connection = connection.value;
-  
   var listener = {
     onStartRequest : function () {},
     onStopRequest : function ()  {
       if (!this.called) {
         this.called = true;
-        connection.CloseConnection();
+        server.closeCachedConnections();
         this.called = false;
       }
     },
     onDataAvailable : function () {}, 
     QueryInterface : function (iid) {
       if (iid.equals(Ci.nsIStreamListener) ||
           iid.equals(Ci.nsISupports))
         return this;
 
       throw Cr.NS_ERROR_NO_INTERFACE;
     }
   }
   listener.called = false;
-
-  connection.Initialize(url, null);
-  connection.LoadNewsUrl(url, listener);
+  server.loadNewsUrl(url, null, listener);
 }
 
 function create_post(baseURL, file) {
   var url = URLCreator.newURI(baseURL, null, null);
   url.QueryInterface(Ci.nsINntpUrl);
 
   var post = Cc["@mozilla.org/messenger/nntpnewsgrouppost;1"]
                .createInstance(Ci.nsINNTPNewsgroupPost);
--- a/mailnews/news/test/unit/test_server.js
+++ b/mailnews/news/test/unit/test_server.js
@@ -102,11 +102,33 @@ function testRFC977() {
   }
   server.stop();
 
   var thread = gThreadManager.currentThread;
   while (thread.hasPendingEvents())
     thread.processNextEvent(true);
 }
 
+function testConnectionLimit() {
+  var handler = new NNTP_RFC977_handler(daemon);
+  var server = new nsMailServer(handler);
+  server.start(NNTP_PORT);
+
+  var prefix = "news://localhost:"+NNTP_PORT+"/";
+  var transaction;
+
+  // To test make connections limit, we run two URIs simultaneously.
+  setupProtocolTest(NNTP_PORT, prefix+"*");
+  setupProtocolTest(NNTP_PORT, prefix+"TSS1@nntp.test");
+  server.performTest();
+  // We should have length one... which means this must be a transaction object,
+  // containing only us and them
+  do_check_true('us' in server.playTransaction());
+  server.stop();
+
+  var thread = gThreadManager.currentThread;
+  while (thread.hasPendingEvents())
+    thread.processNextEvent(true);
+}
 function run_test() {
   testRFC977();
+  testConnectionLimit();
 }