浏览代码

Fixed WindowsDriver mem leak

Tigger Kindel 1 年之前
父节点
当前提交
e4f3b97da0

+ 1 - 1
Terminal.Gui/Application.cs

@@ -242,7 +242,7 @@ namespace Terminal.Gui {
 
 			// BUGBUG: OverlappedTop is not cleared here, but it should be?
 
-			MainLoop?.Stop ();
+			MainLoop?.Dispose ();
 			MainLoop = null;
 			Driver?.End ();
 			Driver = null;

+ 8 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Runtime.InteropServices;
+using static Terminal.Gui.NetEvents;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -205,5 +206,12 @@ namespace Terminal.Gui {
 				}
 			}
 		}
+
+		public void TearDown ()
+		{
+			_descriptorWatchers?.Clear ();
+
+			_mainLoop = null;
+		}
 	}
 }

+ 5 - 0
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs

@@ -1,4 +1,5 @@
 using System;
+using static Terminal.Gui.NetEvents;
 
 namespace Terminal.Gui;
 
@@ -33,5 +34,9 @@ internal class FakeMainLoop : IMainLoopDriver {
 			KeyPressed?.Invoke (FakeConsole.MockKeyPresses.Pop ());
 		}
 	}
+
+	public void TearDown ()
+	{
+	}
 }
 

+ 14 - 1
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Text;
+using static Terminal.Gui.NetEvents;
 
 namespace Terminal.Gui;
 class NetWinVTConsole {
@@ -1394,8 +1395,20 @@ internal class NetMainLoop : IMainLoopDriver {
 			ProcessInput?.Invoke (_inputResult.Dequeue ().Value);
 		}
 	}
+	
 	public void TearDown ()
 	{
-		//throw new NotImplementedException ();
+		_inputResult?.Clear ();
+		
+		_tokenSource?.Cancel ();
+		_tokenSource?.Dispose ();
+		
+		_keyReady?.Dispose ();
+
+		_waitForProbe?.Dispose ();
+
+		_netEvents?.Dispose ();
+
+		_mainLoop = null;
 	}
 }

+ 34 - 24
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1739,7 +1739,7 @@ internal class WindowsDriver : ConsoleDriver {
 			//_mainLoop.Dispose ();
 		}
 		_mainLoop = null;
-		
+
 		WinConsole?.Cleanup ();
 		WinConsole = null;
 
@@ -1774,7 +1774,8 @@ internal class WindowsMainLoop : IMainLoopDriver {
 	bool _winChanged;
 	Size _windowSize;
 	CancellationTokenSource _eventReadyTokenSource = new CancellationTokenSource ();
-	
+	CancellationTokenSource _inputHandlerTokenSource = new CancellationTokenSource ();
+
 	// The records that we keep fetching
 	readonly Queue<WindowsConsole.InputRecord []> _resultQueue = new Queue<WindowsConsole.InputRecord []> ();
 
@@ -1790,22 +1791,30 @@ internal class WindowsMainLoop : IMainLoopDriver {
 
 	public WindowsMainLoop (ConsoleDriver consoleDriver = null)
 	{
-		_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof(consoleDriver));
+		_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
 		_winConsole = ((WindowsDriver)consoleDriver).WinConsole;
 	}
 
 	void IMainLoopDriver.Setup (MainLoop mainLoop)
 	{
 		_mainLoop = mainLoop;
-		Task.Run (WindowsInputHandler);
+		Task.Run (WindowsInputHandler, _inputHandlerTokenSource.Token);
 		Task.Run (CheckWinChange);
 	}
-	
+
 	void WindowsInputHandler ()
 	{
 		while (_mainLoop != null) {
-			_waitForProbe.Wait ();
-			_waitForProbe.Reset ();
+			try {
+				if (!_inputHandlerTokenSource.IsCancellationRequested) {
+					_waitForProbe.Wait (_inputHandlerTokenSource.Token);
+				}
+
+			} catch (OperationCanceledException) {
+				return;
+			} finally {
+				_waitForProbe.Reset ();
+			}
 
 			if (_resultQueue?.Count == 0) {
 				_resultQueue.Enqueue (_winConsole.ReadConsoleInput ());
@@ -1820,7 +1829,7 @@ internal class WindowsMainLoop : IMainLoopDriver {
 		while (_mainLoop != null) {
 			_winChange.Wait ();
 			_winChange.Reset ();
-			
+
 			while (_mainLoop != null) {
 				Task.Delay (500).Wait ();
 				_windowSize = _winConsole.GetConsoleBufferWindow (out _);
@@ -1829,18 +1838,17 @@ internal class WindowsMainLoop : IMainLoopDriver {
 					break;
 				}
 			}
-			
+
 			_winChanged = true;
 			_eventReady.Set ();
 		}
 	}
-	
+
 	void IMainLoopDriver.Wakeup ()
 	{
-		//tokenSource.Cancel ();
 		_eventReady.Set ();
 	}
-	
+
 	bool IMainLoopDriver.EventsPending ()
 	{
 		_waitForProbe.Set ();
@@ -1869,8 +1877,6 @@ internal class WindowsMainLoop : IMainLoopDriver {
 		_eventReadyTokenSource = new CancellationTokenSource ();
 		return true;
 	}
-	
-
 
 	void IMainLoopDriver.Iteration ()
 	{
@@ -1886,17 +1892,21 @@ internal class WindowsMainLoop : IMainLoopDriver {
 			WinChanged?.Invoke (this, new SizeChangedEventArgs (_windowSize));
 		}
 	}
-	
-	//public void Dispose ()
-	//{
-	//	_eventReadyTokenSource?.Cancel ();
-	//	_eventReadyTokenSource?.Dispose ();
-	//	_eventReady?.Dispose ();
 
-	//	_mainLoop = null;
-	//	_winChange?.Dispose ();
-	//	_waitForProbe?.Dispose ();
-	//}
+	void IMainLoopDriver.TearDown ()
+	{
+		_inputHandlerTokenSource?.Cancel ();
+		_inputHandlerTokenSource?.Dispose ();
+
+		_eventReadyTokenSource?.Cancel ();
+		_eventReadyTokenSource?.Dispose ();
+		_eventReady?.Dispose ();
+
+		_winChange?.Dispose ();
+		_waitForProbe?.Dispose ();
+
+		_mainLoop = null;
+	}
 }
 
 class WindowsClipboard : ClipboardBase {

+ 35 - 16
Terminal.Gui/MainLoop.cs

@@ -16,6 +16,9 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Initializes the <see cref="MainLoop"/>, gets the calling main loop for the initialization.
 		/// </summary>
+		/// <remarks>
+		/// Call <see cref="TearDown"/> to release resources.
+		/// </remarks>
 		/// <param name="mainLoop">Main loop.</param>
 		void Setup (MainLoop mainLoop);
 
@@ -34,6 +37,11 @@ namespace Terminal.Gui {
 		/// The iteration function.
 		/// </summary>
 		void Iteration ();
+
+		/// <summary>
+		/// Tears down the <see cref="MainLoop"/> driver. Releases resources created in <see cref="Setup"/>.
+		/// </summary>
+		void TearDown ();
 	}
 
 	/// <summary>
@@ -43,7 +51,7 @@ namespace Terminal.Gui {
 	///   Monitoring of file descriptors is only available on Unix, there
 	///   does not seem to be a way of supporting this on Windows.
 	/// </remarks>
-	public class MainLoop {
+	public class MainLoop : IDisposable {
 
 		internal SortedList<long, Timeout> _timeouts = new SortedList<long, Timeout> ();
 		readonly object _timeoutsLockToken = new object ();
@@ -76,7 +84,7 @@ namespace Terminal.Gui {
 		/// The current <see cref="IMainLoopDriver"/> in use.
 		/// </summary>
 		/// <value>The main loop driver.</value>
-		public IMainLoopDriver MainLoopDriver { get; }
+		public IMainLoopDriver MainLoopDriver { get; private set; }
 
 		/// <summary>
 		/// Invoked when a new timeout is added. To be used in the case
@@ -87,6 +95,9 @@ namespace Terminal.Gui {
 		/// <summary>
 		///  Creates a new MainLoop. 
 		/// </summary>
+		/// <remarks>
+		/// Use <see cref="Dispose"/> to release resources.
+		/// </remarks>
 		/// <param name="driver">The <see cref="ConsoleDriver"/> instance
 		/// (one of the implementations FakeMainLoop, UnixMainLoop, NetMainLoop or WindowsMainLoop).</param>
 		public MainLoop (IMainLoopDriver driver)
@@ -294,17 +305,6 @@ namespace Terminal.Gui {
 
 		bool _running;
 
-		// BUGBUG: Stop is only called from MainLoopUnitTests.cs. As a result, the mainloop
-		// will never exit during other unit tests or normal execution.
-		/// <summary>
-		///   Stops the mainloop.
-		/// </summary>
-		public void Stop ()
-		{
-			_running = false;
-			MainLoopDriver.Wakeup ();
-		}
-
 		/// <summary>
 		///   Determines whether there are pending events to be processed.
 		/// </summary>
@@ -325,7 +325,7 @@ namespace Terminal.Gui {
 		///   Use this to process all pending events (timers, idle handlers and file watches).
 		///
 		///   <code>
-		///     while (main.EvenstPending ()) RunIteration ();
+		///     while (main.EventsPending ()) RunIteration ();
 		///   </code>
 		/// </remarks>
 		public void RunIteration ()
@@ -335,10 +335,10 @@ namespace Terminal.Gui {
 					RunTimers ();
 				}
 			}
-
+			
 			MainLoopDriver.Iteration ();
 
-			bool runIdle = false;
+			var runIdle = false;
 			lock (_idleHandlersLock) {
 				runIdle = _idleHandlers.Count > 0;
 			}
@@ -361,5 +361,24 @@ namespace Terminal.Gui {
 			}
 			_running = prev;
 		}
+
+		/// <summary>
+		/// Stops the main loop driver and calls <see cref="IMainLoopDriver.Wakeup"/>.
+		/// </summary>
+		public void Stop ()
+		{
+			_running = false;
+			MainLoopDriver.Wakeup ();
+		}
+		
+		/// <inheritdoc/>
+		public void Dispose ()
+		{
+			GC.SuppressFinalize (this);
+			Stop ();
+			_running = false;
+			MainLoopDriver?.TearDown ();
+			MainLoopDriver = null;
+		}
 	}
 }

+ 3 - 1
UnitTests/Application/MainLoopTests.cs

@@ -613,8 +613,10 @@ namespace Terminal.Gui.ApplicationTests {
 				Application.MainLoop.Invoke (() => {
 					tf.Text = $"index{r.Next ()}";
 					Interlocked.Increment (ref tbCounter);
-					if (target == tbCounter)                                                // On last increment wake up the check
+					if (target == tbCounter) {
+						// On last increment wake up the check
 						_wakeUp.Set ();
+					}
 				});
 			});
 		}