[System.ServiceModel] Fix "collection was modified" exception in tests
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels.Http / HttpListenerManager.cs
index 3bae51bd5fbfbca49cbeab7060adc0b3fab2166e..5ccee0a2605dfa6d5e968e426f232e0fcf9542fd 100644 (file)
@@ -48,24 +48,29 @@ namespace System.ServiceModel.Channels.Http
                        Entries = new List<HttpChannelListenerEntry> ();
                }
 
-               public List<HttpChannelListenerEntry> Entries { get; private set; }
+               protected List<HttpChannelListenerEntry> Entries { get; private set; }
+               object entries_lock = new object ();
 
-               public abstract void RegisterListener (ChannelDispatcher channel, TimeSpan timeout);
+               public abstract void RegisterListener (ChannelDispatcher channel, HttpTransportBindingElement element, TimeSpan timeout);
                public abstract void UnregisterListener (ChannelDispatcher channel, TimeSpan timeout);
 
                protected void RegisterListenerCommon (ChannelDispatcher channel, TimeSpan timeout)
                {
-                       Entries.Add (new HttpChannelListenerEntry (channel, new AutoResetEvent (false)));
+                       lock (entries_lock) {
+                               Entries.Add (new HttpChannelListenerEntry (channel, new AutoResetEvent (false)));
 
-                       Entries.Sort (HttpChannelListenerEntry.CompareEntries);
+                               Entries.Sort (HttpChannelListenerEntry.CompareEntries);
+                       }
                }
 
                protected void UnregisterListenerCommon (ChannelDispatcher channel, TimeSpan timeout)
                {
-                       var entry = Entries.First (e => e.ChannelDispatcher == channel);
-                       Entries.Remove (entry);
+                       lock (entries_lock) {
+                               var entry = Entries.First (e => e.ChannelDispatcher == channel);
+                               Entries.Remove (entry);
 
-                       entry.WaitHandle.Set (); // make sure to finish pending requests.
+                               entry.WaitHandle.Set (); // make sure to finish pending requests.
+                       }
                }
 
                public void ProcessNewContext (HttpContextInfo ctxi)
@@ -79,9 +84,11 @@ namespace System.ServiceModel.Channels.Http
 
                HttpChannelListenerEntry SelectChannel (HttpContextInfo ctx)
                {
-                       foreach (var e in Entries)
-                               if (e.FilterHttpContext (ctx))
-                                       return e;
+                       lock (entries_lock) {
+                               foreach (var e in Entries)
+                                       if (e.FilterHttpContext (ctx))
+                                               return e;
+                       }
                        return null;
                }
 
@@ -90,12 +97,21 @@ namespace System.ServiceModel.Channels.Http
                        DateTime start = DateTime.Now;
 
                        context = null;
-                       var ce = Entries.First (e => e.ChannelDispatcher == channel);
+                       HttpChannelListenerEntry ce = null;
+                       lock (entries_lock) {
+                               ce = Entries.FirstOrDefault (e => e.ChannelDispatcher == channel);
+                       }
+                       if (ce == null)
+                               return false;
                        lock (ce.RetrieverLock) {
                                var q = ce.ContextQueue;
                                if (q.Count == 0) {
-                                       bool ret = ce.WaitHandle.WaitOne (timeout);
-                                       return ret && TryDequeueRequest (channel, timeout - (DateTime.Now - start), out context); // recurse, am lazy :/
+                                       if (timeout.TotalMilliseconds < 0) return false;
+                                       TimeSpan waitTimeout = timeout;
+                                       if (timeout == TimeSpan.MaxValue)
+                                               waitTimeout = TimeSpan.FromMilliseconds (int.MaxValue);
+                                       bool ret = ce.WaitHandle.WaitOne (waitTimeout);
+                                       return ret && TryDequeueRequest (channel, waitTimeout - (DateTime.Now - start), out context); // recurse, am lazy :/
                                }
                                context = q.Dequeue ();
                                return true;
@@ -105,11 +121,11 @@ namespace System.ServiceModel.Channels.Http
 
        internal class HttpStandaloneListenerManager : HttpListenerManager
        {
-               public HttpStandaloneListenerManager (Uri uri)
+               public HttpStandaloneListenerManager (Uri uri, HttpTransportBindingElement element)
                {
                        var l = new HttpListener ();
 
-                       string uriString = uri.ToString ();
+                       string uriString = element.HostNameComparisonMode == HostNameComparisonMode.Exact ? uri.ToString () : uri.Scheme + "://*" + uri.GetComponents (UriComponents.Port | UriComponents.Path, UriFormat.SafeUnescaped);
                        if (!uriString.EndsWith ("/", StringComparison.Ordinal))
                                uriString += "/"; // HttpListener requires this mess.
 
@@ -123,16 +139,25 @@ namespace System.ServiceModel.Channels.Http
                Thread loop;
 
                // FIXME: use timeout
-               public override void RegisterListener (ChannelDispatcher channel, TimeSpan timeout)
+               public override void RegisterListener (ChannelDispatcher channel, HttpTransportBindingElement element, TimeSpan timeout)
                {
                        RegisterListenerCommon (channel, timeout);
 
                        if (Entries.Count != 1)
                                return;
 
+                       if (element != null) {
+                               var l = listener;
+                               l.AuthenticationSchemeSelectorDelegate = delegate (HttpListenerRequest req) {
+                                       return element.AuthenticationScheme;
+                               };
+                               l.Realm = element.Realm;
+                               l.UnsafeConnectionNtlmAuthentication = element.UnsafeConnectionNtlmAuthentication;
+                       }
+
                        // Start here. It is shared between channel listeners
                        // that share the same listen Uri. So there is no other appropriate place.
-#if true
+#if USE_SEPARATE_LOOP // this cannot be enabled because it causes infinite loop when ChannelDispatcher is not involved.
                        loop = new Thread (new ThreadStart (delegate {
                                listener.Start ();
                                try {
@@ -145,6 +170,7 @@ namespace System.ServiceModel.Channels.Http
                        }));
                        loop.Start ();
 #else
+                       listener.Start ();
                        listener.BeginGetContext (GetContextCompleted, null);
 #endif
                }
@@ -158,7 +184,7 @@ namespace System.ServiceModel.Channels.Http
                        if (Entries.Count > 0)
                                return;
 
-#if true
+#if USE_SEPARATE_LOOP
                        loop.Abort ();
 #else
                        this.listener.Stop ();
@@ -187,7 +213,7 @@ namespace System.ServiceModel.Channels.Http
                {
                }
 
-               public override void RegisterListener (ChannelDispatcher channel, TimeSpan timeout)
+               public override void RegisterListener (ChannelDispatcher channel, HttpTransportBindingElement element, TimeSpan timeout)
                {
                        RegisterListenerCommon (channel, timeout);
                }