From cd5942b0d8f714bd3869004f0dba71c312b0d822 Mon Sep 17 00:00:00 2001
From: tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>
Date: Tue, 19 Oct 2010 16:56:06 +0000
Subject: fix console for thread-safe logging

git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2685 b956fd51-792f-4845-bead-9b4dfca2ff2c
---
 mediaplugin/src/base/UCommon.pas  | 129 -----------------------
 mediaplugin/src/base/UConsole.pas | 210 ++++++++++++++++++++++++++++++++++++++
 mediaplugin/src/base/ULog.pas     |   1 +
 mediaplugin/src/ultrastardx.dpr   |   2 +
 4 files changed, 213 insertions(+), 129 deletions(-)
 create mode 100644 mediaplugin/src/base/UConsole.pas

(limited to 'mediaplugin/src')

diff --git a/mediaplugin/src/base/UCommon.pas b/mediaplugin/src/base/UCommon.pas
index 18022337..bf7475a3 100644
--- a/mediaplugin/src/base/UCommon.pas
+++ b/mediaplugin/src/base/UCommon.pas
@@ -68,8 +68,6 @@ type
 
 procedure ShowMessage(const msg: string; msgType: TMessageType = mtInfo);
 
-procedure ConsoleWriteLn(const msg: string);
-
 {$IFDEF FPC}
 function RandomRange(aMin: integer; aMax: integer): integer;
 {$ENDIF}
@@ -297,126 +295,6 @@ begin
 end;
 {$ENDIF}
 
-
-{$IFDEF FPC}
-var
-  MessageList: TStringList;
-  ConsoleHandler: TThreadID;
-  // Note: TRTLCriticalSection is defined in the units System and Libc, use System one
-  ConsoleCriticalSection: System.TRTLCriticalSection;
-  ConsoleEvent: PRTLEvent;
-  ConsoleQuit: boolean;
-{$ENDIF}
-
-(*
- * Write to console if one is available.
- * It checks if a console is available before output so it will not
- * crash on windows if none is available.
- * Do not use this function directly because it is not thread-safe,
- * use ConsoleWriteLn() instead.
- *)
-procedure _ConsoleWriteLn(const aString: string); {$IFDEF HasInline}inline;{$ENDIF}
-begin
-  {$IFDEF MSWINDOWS}
-  // sanity check to avoid crashes with writeln()
-  if (IsConsole) then
-  begin
-  {$ENDIF}
-    Writeln(aString);
-  {$IFDEF MSWINDOWS}
-  end;
-  {$ENDIF}
-end;
-
-{$IFDEF FPC}
-{*
- * The console-handlers main-function.
- * TODO: create a quit-event on closing.
- *}
-function ConsoleHandlerFunc(param: pointer): PtrInt;
-var
-  i: integer;
-  quit: boolean;
-begin
-  quit := false;
-  while (not quit) do
-  begin
-    // wait for new output or quit-request
-    RTLeventWaitFor(ConsoleEvent);
-
-    System.EnterCriticalSection(ConsoleCriticalSection);
-    // output pending messages
-    for i := 0 to MessageList.Count - 1 do
-    begin
-      _ConsoleWriteLn(MessageList[i]);
-    end;
-    MessageList.Clear();
-
-    // use local quit-variable to avoid accessing
-    // ConsoleQuit outside of the critical section
-    if (ConsoleQuit) then
-      quit := true;
-
-    RTLeventResetEvent(ConsoleEvent);
-    System.LeaveCriticalSection(ConsoleCriticalSection);
-  end;
-  result := 0;
-end;
-{$ENDIF}
-
-procedure InitConsoleOutput();
-begin
-  {$IFDEF FPC}
-  // init thread-safe output
-  MessageList := TStringList.Create();
-  System.InitCriticalSection(ConsoleCriticalSection);
-  ConsoleEvent := RTLEventCreate();
-  ConsoleQuit := false;
-  // must be a thread managed by FPC. Otherwise (e.g. SDL-thread)
-  // it will crash when using Writeln.
-  ConsoleHandler := BeginThread(@ConsoleHandlerFunc);
-  {$ENDIF}
-end;
-
-procedure FinalizeConsoleOutput();
-begin
-  {$IFDEF FPC}
-  // terminate console-handler
-  System.EnterCriticalSection(ConsoleCriticalSection);
-  ConsoleQuit := true;
-  RTLeventSetEvent(ConsoleEvent);
-  System.LeaveCriticalSection(ConsoleCriticalSection);
-  WaitForThreadTerminate(ConsoleHandler, 0);
-  // free data
-  System.DoneCriticalsection(ConsoleCriticalSection);
-  RTLeventDestroy(ConsoleEvent);
-  MessageList.Free();
-  {$ENDIF}
-end;
-
-{*
- * FPC uses threadvars (TLS) managed by FPC for console output locking.
- * Using WriteLn() from external threads (like in SDL callbacks)
- * will crash the program as those threadvars have never been initialized.
- * The solution is to create an FPC-managed thread which has the TLS data
- * and use it to handle the console-output (hence it is called Console-Handler)
- *}
-procedure ConsoleWriteLn(const msg: string);
-begin
-{$IFDEF CONSOLE}
-  {$IFDEF FPC}
-  // TODO: check for the main-thread and use a simple _ConsoleWriteLn() then?
-  //GetCurrentThreadThreadId();
-  System.EnterCriticalSection(ConsoleCriticalSection);
-  MessageList.Add(msg);
-  RTLeventSetEvent(ConsoleEvent);
-  System.LeaveCriticalSection(ConsoleCriticalSection);
-  {$ELSE}
-  _ConsoleWriteLn(msg);
-  {$ENDIF}
-{$ENDIF}
-end;
-
 procedure ShowMessage(const msg: String; msgType: TMessageType);
 {$IFDEF MSWINDOWS}
 var Flags: cardinal;
@@ -599,11 +477,4 @@ begin
 end;
 {$WARNINGS ON}
 
-
-initialization
-  InitConsoleOutput();
-
-finalization
-  FinalizeConsoleOutput();
-
 end.
diff --git a/mediaplugin/src/base/UConsole.pas b/mediaplugin/src/base/UConsole.pas
new file mode 100644
index 00000000..102205cc
--- /dev/null
+++ b/mediaplugin/src/base/UConsole.pas
@@ -0,0 +1,210 @@
+{* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *}
+
+unit UConsole;
+
+interface
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$DEFINE SYNCHRONIZE_CONSOLE}
+
+procedure ConsoleWriteLn(const msg: string);
+
+implementation
+
+uses
+  // Do not include the unit 'Libc', it also defines TRTLCriticalSection,
+  // use the TRTLCriticalSection
+  {$IFDEF MSWINDOWS}
+  Windows,
+  {$ENDIF}
+  SysUtils,
+  SyncObjs,
+  Classes;
+
+procedure _ConsoleWriteLn(const aString: string); {$IFDEF HasInline}inline;{$ENDIF} forward;
+
+{$IFDEF SYNCHRONIZE_CONSOLE}
+type
+  {**
+   * FPC uses threadvars (TLS) managed by FPC for console output locking.
+   * Using WriteLn() from external threads (like in SDL callbacks)
+   * will crash the program as those threadvars have never been initialized.
+   * Access to the console by two non-managed threads in Delphi also causes a
+   * crash.
+   *
+   * The solution is to create an FPC/Delphi-managed thread which has the TLS data
+   * and use it to handle the console-output (hence it is called Console-Handler)
+   * This must be a thread managed by FPC/Delphi, otherwise (e.g. SDL-thread)
+   * it will crash when using Writeln.
+   *}
+  TConsoleHandler = class(TThread)
+  private
+    fMessageList: TStringList;
+    fConsoleCriticalSection: TCriticalSection;
+    fConsoleEvent: TEvent;
+
+  public
+    constructor Create();
+    destructor Destroy(); override;
+    procedure WriteLn(const msg: string);
+
+  protected
+    procedure Execute(); override;
+  end;
+
+var
+  ConsoleHandler: TConsoleHandler;
+
+constructor TConsoleHandler.Create();
+begin
+  inherited Create(false);
+
+  // init thread-safe output
+  fMessageList := TStringList.Create();
+  fConsoleCriticalSection := TCriticalSection.Create;
+  fConsoleEvent := TEvent.Create(nil, false, false, 'ConsoleEvent');
+end;
+
+destructor TConsoleHandler.Destroy();
+begin
+  // terminate console-handler
+  fConsoleCriticalSection.Enter;
+  Terminate;
+  fConsoleEvent.SetEvent();
+  fConsoleCriticalSection.Leave();
+
+  WaitFor();
+
+  // free data
+  fConsoleCriticalSection.Free;
+  fConsoleEvent.Free;
+  fMessageList.Free;
+  
+  inherited;
+end;
+
+{*
+ * The console-handlers main-function.
+ *}
+procedure TConsoleHandler.Execute();
+var
+  i: integer;
+begin
+  while (true) do
+  begin
+    // wait for new output or quit-request
+    fConsoleEvent.WaitFor(INFINITE);
+
+    fConsoleCriticalSection.Enter;
+    try
+      // output pending messages
+      for i := 0 to fMessageList.Count - 1 do
+      begin
+        _ConsoleWriteLn(fMessageList[i]);
+      end;
+      fMessageList.Clear();
+
+      // use local quit-variable to avoid accessing
+      // ConsoleQuit outside of the critical section
+      if (Terminated) then
+        Break;
+    finally
+      fConsoleEvent.ResetEvent();
+      fConsoleCriticalSection.Leave;
+    end;
+  end;
+end;
+
+procedure TConsoleHandler.WriteLn(const msg: string);
+begin
+  // TODO: check for the main-thread and use a simple _ConsoleWriteLn() then?
+  //GetCurrentThreadThreadId();
+  fConsoleCriticalSection.Enter;
+  try
+    fMessageList.Add(msg);
+    fConsoleEvent.SetEvent();
+  finally
+    fConsoleCriticalSection.Leave;
+  end;
+end;
+
+{$ENDIF}
+
+(*
+ * Write to console if one is available.
+ * It checks if a console is available before output so it will not
+ * crash on windows if none is available.
+ * Do not use this function directly because it is not thread-safe,
+ * use ConsoleWriteLn() instead.
+ *)
+procedure _ConsoleWriteLn(const aString: string); {$IFDEF HasInline}inline;{$ENDIF}
+begin
+  {$IFDEF MSWINDOWS}
+  // sanity check to avoid crashes with writeln()
+  if (IsConsole) then
+  begin
+  {$ENDIF}
+    Writeln(aString);
+  {$IFDEF MSWINDOWS}
+  end;
+  {$ENDIF}
+end;
+
+procedure ConsoleWriteLn(const msg: string);
+begin
+{$IFDEF CONSOLE}
+  {$IFDEF SYNCHRONIZE_CONSOLE}
+  ConsoleHandler.WriteLn(msg);
+  {$ELSE}
+  _ConsoleWriteLn(msg);
+  {$ENDIF}
+{$ENDIF}
+end;
+
+procedure InitConsoleOutput();
+begin
+  {$IFDEF SYNCHRONIZE_CONSOLE}
+  ConsoleHandler := TConsoleHandler.Create;
+  {$ENDIF}
+end;
+
+procedure FinalizeConsoleOutput();
+begin
+  {$IFDEF SYNCHRONIZE_CONSOLE}
+  ConsoleHandler.Free;
+  {$ENDIF}
+end;
+
+initialization
+  InitConsoleOutput();
+
+finalization
+  FinalizeConsoleOutput();
+
+end.
diff --git a/mediaplugin/src/base/ULog.pas b/mediaplugin/src/base/ULog.pas
index e4ff4862..fcdd32fd 100644
--- a/mediaplugin/src/base/ULog.pas
+++ b/mediaplugin/src/base/ULog.pas
@@ -132,6 +132,7 @@ uses
   URecord,
   UMain,  
   UTime,
+  UConsole,
   UCommon,
   UCommandLine,
   UPathUtils;
diff --git a/mediaplugin/src/ultrastardx.dpr b/mediaplugin/src/ultrastardx.dpr
index f6c9558c..6cdda69d 100644
--- a/mediaplugin/src/ultrastardx.dpr
+++ b/mediaplugin/src/ultrastardx.dpr
@@ -57,6 +57,8 @@ uses
   ctypes                 in 'lib\ctypes\ctypes.pas', // FPC compatibility types for C libs
   {$ENDIF}
 
+  UConsole               in 'base\UConsole.pas',
+
   //------------------------------
   //Includes - 3rd Party Libraries
   //------------------------------
-- 
cgit v1.2.3