From cd5942b0d8f714bd3869004f0dba71c312b0d822 Mon Sep 17 00:00:00 2001 From: tobigun 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 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