Преглед изворни кода

Fix for memory leak in SystemClock.

Bart De Smet пре 10 година
родитељ
комит
a3b774ac7c

+ 4 - 4
Rx.NET/Source/Common.targets

@@ -115,7 +115,7 @@
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(BuildTarget)' == 'PLLITE' ">
-    <DefineConstants>$(DefineConstants);NO_RXINTERFACES;NO_SERIALIZABLE;NO_REMOTING;NO_SEMAPHORE;NO_STOPWATCH;NO_CDS;PLIB;PLIB_LITE;NO_THREAD;USE_TASKEX;CRIPPLED_REFLECTION</DefineConstants>    
+    <DefineConstants>$(DefineConstants);NO_RXINTERFACES;NO_SERIALIZABLE;NO_REMOTING;NO_SEMAPHORE;NO_STOPWATCH;NO_CDS;PLIB;PLIB_LITE;NO_THREAD;USE_TASKEX;CRIPPLED_REFLECTION;NO_WEAKREFOFT</DefineConstants>    
     <TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
       <!-- 
@@ -167,14 +167,14 @@
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(BuildTarget)' == '40' ">
-    <DefineConstants>$(DefineConstants);NO_TASK_DELAY;HAS_APTCA;HAS_WINFORMS;USE_TIMER_SELF_ROOT</DefineConstants>
+    <DefineConstants>$(DefineConstants);NO_TASK_DELAY;HAS_APTCA;HAS_WINFORMS;USE_TIMER_SELF_ROOT;NO_WEAKREFOFT</DefineConstants>
     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
     <BuildPlatform>DESKTOPCLR</BuildPlatform>
     <BuildFlavor>DESKTOPCLR40</BuildFlavor>
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(BuildTarget)' == 'SL5' ">
-    <DefineConstants>$(DefineConstants);NO_RXINTERFACES;USE_SL_DISPATCHER;NO_SERIALIZABLE;NO_REMOTING;NO_SEMAPHORE;NO_STOPWATCH;NO_CDS;NO_TASK_DELAY</DefineConstants>
+    <DefineConstants>$(DefineConstants);NO_RXINTERFACES;USE_SL_DISPATCHER;NO_SERIALIZABLE;NO_REMOTING;NO_SEMAPHORE;NO_STOPWATCH;NO_CDS;NO_TASK_DELAY;NO_WEAKREFOFT</DefineConstants>
     <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
     <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
     <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>
@@ -184,7 +184,7 @@
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(BuildTarget)' == 'WP7' ">
-    <DefineConstants>$(DefineConstants);USE_SL_DISPATCHER;NO_SERIALIZABLE;NO_REMOTING;NO_CDS;NO_TLS;NO_VARIANCE;NO_TPL;NO_HASHSET;NO_SEMAPHORE;NO_LARGEARITY;NO_EXPRESSIONVISITOR;NO_LAZY;NO_WEAKTABLE;NO_INTERLOCKED_64;WINDOWSPHONE7</DefineConstants>
+    <DefineConstants>$(DefineConstants);USE_SL_DISPATCHER;NO_SERIALIZABLE;NO_REMOTING;NO_CDS;NO_TLS;NO_VARIANCE;NO_TPL;NO_HASHSET;NO_SEMAPHORE;NO_LARGEARITY;NO_EXPRESSIONVISITOR;NO_LAZY;NO_WEAKTABLE;NO_INTERLOCKED_64;WINDOWSPHONE7;NO_WEAKREFOFT</DefineConstants>
     <TargetFrameworkProfile>WindowsPhone71</TargetFrameworkProfile>
     <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>

+ 2 - 2
Rx.NET/Source/System.Reactive.Core/Reactive/Concurrency/LocalScheduler.TimerQueue.cs

@@ -113,7 +113,7 @@ namespace System.Reactive.Concurrency
             // Hook up for system clock change notifications. This doesn't do anything until the
             // AddRef method is called (which can throw).
             //
-            SystemClock.SystemClockChanged += SystemClockChanged;
+            SystemClock.Register(this);
         }
 
         /// <summary>
@@ -373,7 +373,7 @@ namespace System.Reactive.Concurrency
         /// </summary>
         /// <param name="args">Currently not used.</param>
         /// <param name="sender">Currently not used.</param>
-        private void SystemClockChanged(object sender, SystemClockChangedEventArgs args)
+        internal void SystemClockChanged(object sender, SystemClockChangedEventArgs args)
         {
             lock (_gate)
             {

+ 140 - 8
Rx.NET/Source/System.Reactive.Core/Reactive/Internal/SystemClock.cs

@@ -1,6 +1,9 @@
 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
 
+using System.Collections;
+using System.Collections.Generic;
 using System.ComponentModel;
+using System.Reactive.Concurrency;
 using System.Threading;
 
 namespace System.Reactive.PlatformServices
@@ -17,6 +20,8 @@ namespace System.Reactive.PlatformServices
     {
         private static Lazy<ISystemClock> s_serviceSystemClock = new Lazy<ISystemClock>(InitializeSystemClock);
         private static Lazy<INotifySystemClockChanged> s_serviceSystemClockChanged = new Lazy<INotifySystemClockChanged>(InitializeSystemClockChanged);
+        private static readonly HashSet<WeakReference<LocalScheduler>> s_systemClockChanged = new HashSet<WeakReference<LocalScheduler>>();
+        private static IDisposable s_systemClockChangedHandlerCollector;
 
         private static int _refCount;
 
@@ -28,11 +33,6 @@ namespace System.Reactive.PlatformServices
             get { return s_serviceSystemClock.Value.UtcNow; }
         }
 
-        /// <summary>
-        /// Event that gets raised when a system clock change is detected, if there's any interest as indicated by AddRef calls.
-        /// </summary>
-        public static event EventHandler<SystemClockChangedEventArgs> SystemClockChanged;
-
         /// <summary>
         /// Adds a reference to the system clock monitor, causing it to be sending notifications.
         /// </summary>
@@ -59,9 +59,18 @@ namespace System.Reactive.PlatformServices
 
         private static void OnSystemClockChanged(object sender, SystemClockChangedEventArgs e)
         {
-            var scc = SystemClockChanged;
-            if (scc != null)
-                scc(sender, e);
+            lock (s_systemClockChanged)
+            {
+                foreach (var entry in s_systemClockChanged)
+                {
+                    var scheduler = default(LocalScheduler);
+
+                    if (entry.TryGetTarget(out scheduler))
+                    {
+                        scheduler.SystemClockChanged(sender, e);
+                    }
+                }
+            }
         }
 
         private static ISystemClock InitializeSystemClock()
@@ -73,6 +82,75 @@ namespace System.Reactive.PlatformServices
         {
             return PlatformEnlightenmentProvider.Current.GetService<INotifySystemClockChanged>() ?? new DefaultSystemClockMonitor();
         }
+
+        internal static void Register(LocalScheduler scheduler)
+        {
+            //
+            // LocalScheduler maintains per-instance work queues that need revisiting
+            // upon system clock changes. We need to be careful to avoid keeping those
+            // scheduler instances alive by the system clock monitor, so we use weak
+            // references here. In particular, AsyncLockScheduler in ImmediateScheduler
+            // can have a lot of instances, so we need to collect spurious handlers
+            // at regular times.
+            //
+            lock (s_systemClockChanged)
+            {
+                s_systemClockChanged.Add(new WeakReference<LocalScheduler>(scheduler));
+
+                if (s_systemClockChanged.Count == 1)
+                {
+                    s_systemClockChangedHandlerCollector = ConcurrencyAbstractionLayer.Current.StartPeriodicTimer(CollectHandlers, TimeSpan.FromSeconds(30));
+                }
+                else if (s_systemClockChanged.Count % 64 == 0)
+                {
+                    CollectHandlers();
+                }
+            }
+        }
+
+        private static void CollectHandlers()
+        {
+            //
+            // The handler collector merely collects the WeakReference<T> instances
+            // that are kept in the hash set. The underlying scheduler itself will
+            // be collected due to the weak reference. Unfortunately, we can't use
+            // the ConditionalWeakTable<TKey, TValue> type here because we need to
+            // be able to enumerate the keys.
+            //
+            lock (s_systemClockChanged)
+            {
+                var remove = default(HashSet<WeakReference<LocalScheduler>>);
+
+                foreach (var handler in s_systemClockChanged)
+                {
+                    var scheduler = default(LocalScheduler);
+
+                    if (!handler.TryGetTarget(out scheduler))
+                    {
+                        if (remove == null)
+                        {
+                            remove = new HashSet<WeakReference<LocalScheduler>>();
+                        }
+
+                        remove.Add(handler);
+                    }
+                }
+
+                if (remove != null)
+                {
+                    foreach (var handler in remove)
+                    {
+                        s_systemClockChanged.Remove(handler);
+                    }
+                }
+
+                if (s_systemClockChanged.Count == 0)
+                {
+                    s_systemClockChangedHandlerCollector.Dispose();
+                    s_systemClockChangedHandlerCollector = null;
+                }
+            }
+        }
     }
 
     /// <summary>
@@ -146,4 +224,58 @@ namespace System.Reactive.PlatformServices
         /// </summary>
         public DateTimeOffset NewTime { get; private set; }
     }
+
+#if NO_WEAKREFOFT
+    class WeakReference<T>
+        where T : class
+    {
+        private readonly WeakReference _weakReference;
+
+        public WeakReference(T value)
+        {
+            _weakReference = new WeakReference(value);
+        }
+
+        public bool TryGetTarget(out T value)
+        {
+            value = (T)_weakReference.Target;
+            return value != null;
+        }
+    }
+#endif
+
+#if NO_HASHSET
+    class HashSet<T> : IEnumerable<T>
+    {
+        private readonly Dictionary<T, object> _dictionary = new Dictionary<T, object>();
+
+        public int Count
+        {
+            get
+            {
+                return _dictionary.Count;
+            }
+        }
+
+        public IEnumerator<T> GetEnumerator()
+        {
+            return _dictionary.Keys.GetEnumerator();
+        }
+
+        public void Add(T value)
+        {
+            _dictionary.Add(value, null);
+        }
+
+        public void Remove(T value)
+        {
+            _dictionary.Remove(value);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+#endif
 }

+ 1 - 1
Rx.NET/Source/Tests.System.Reactive/Tests/SystemClockTest.cs

@@ -1002,7 +1002,7 @@ namespace ReactiveTests.Tests
 
             public IDisposable StartPeriodicTimer(Action action, TimeSpan period)
             {
-                throw new NotImplementedException();
+                return Disposable.Empty;
             }
 
             public IDisposable QueueUserWorkItem(Action<object> action, object state)