mORMot and Open Source friends
Check-in [e2420ad793]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:added TSynLog.Release method and protected the global TSynLog instances list against potential race condition
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: e2420ad79368448c5ecd726d457b76a8ac106636
User & Date: User 2014-03-29 10:48:50
Context
2014-03-29
11:16
fixed Delphi 2009-XE2 compilation issue check-in: fed09df557 user: User tags: trunk
10:48
added TSynLog.Release method and protected the global TSynLog instances list against potential race condition check-in: e2420ad793 user: User tags: trunk
10:48
added TObjectListLocked class check-in: 7918565793 user: User tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/mORMotSelfTests.pas.

184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
{$endif FPC}

procedure SQLite3ConsoleTests;
begin
  AllocConsole;
  TSynLogTestLog := TSQLLog; // share the same log file with whole mORMot
  TSQLLog.Family.Level := LOG_STACKTRACE; // log errors by default
  if false then // "if not false then" will create around 450 MB of log file
  with TSQLLog.Family do begin
    Level := LOG_VERBOSE;
    PerThreadLog := ptIdentifiedInOnFile;
    //HighResolutionTimeStamp := true;
    //RotateFileCount := 5; RotateFileSizeKB := 20*1024; // rotate by 20 MB logs
  end
  else






|







184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
{$endif FPC}

procedure SQLite3ConsoleTests;
begin
  AllocConsole;
  TSynLogTestLog := TSQLLog; // share the same log file with whole mORMot
  TSQLLog.Family.Level := LOG_STACKTRACE; // log errors by default
  if false then // "if not false then" will create around 500 MB of log file
  with TSQLLog.Family do begin
    Level := LOG_VERBOSE;
    PerThreadLog := ptIdentifiedInOnFile;
    //HighResolutionTimeStamp := true;
    //RotateFileCount := 5; RotateFileSizeKB := 20*1024; // rotate by 20 MB logs
  end
  else

Changes to SynCommons.pas.

491
492
493
494
495
496
497
498

499
500
501
502
503
504
505
....
9894
9895
9896
9897
9898
9899
9900





















9901
9902
9903
9904
9905
9906
9907
.....
36799
36800
36801
36802
36803
36804
36805

36806
36807
36808
36809
36810
36811
36812
36813
36814
36815
36816
36817
.....
36886
36887
36888
36889
36890
36891
36892
36893
36894
36895
36896
36897
36898
36899
36900
36901


36902
36903
36904
36905
36906
36907
36908
36909
36910
36911
36912
36913
36914
36915



36916
36917
36918
36919
36920
36921
36922
.....
37192
37193
37194
37195
37196
37197
37198
37199
37200


37201
37202
37203
37204
37205
37206



37207
37208
37209
37210
37211
37212
37213
37214
37215
37216
37217
37218
37219
37220
37221
37222


37223
37224
37225
37226
37227
37228
37229
37230
37231
37232
37233
37234



37235
37236
37237
37238
37239
37240
37241
.....
37306
37307
37308
37309
37310
37311
37312
37313
37314
37315
37316
37317
37318
37319
37320
37321
.....
37488
37489
37490
37491
37492
37493
37494
















37495
37496
37497
37498
37499
37500
37501
  - TSynTests will now write the tests summary with colored console output
  - added TSynTestCase.CleanUp virtual method for proper cleaning before Destroy
  - added TSynTestCase.CheckMatchAny() method for multi-value checks
  - TSynTestCase.TestFailed now triggers a debugger breakpoint when run from IDE
  - TSynLog will now append only the execution time when leaving a method,
    without the class/method name (smaller log file, and less resource use)
  - TSynLog header now contains system environment variables
  - added TSynLog.DebuggerNotify() and TSynLog.CloseLogFile methods

  - introducing TSynLogFamily.StackTraceUse: TSynLogStackTraceUse property
  - introducing TSynLogFamily.EchoToConsole: TSynLogInfos property, able to
    optionally echo the process log to the current console window, using colors
  - if new property TSynLogFamily.PerThreadLog is set to ptIdentifiedInOnFile,
    a new column will be added for each logged row - LogViewer has been updated
    to allow easy and efficient multi-thread process logging
  - introducing TSynLogFamily.RotateFileCount and RotateFileSizeKB properties,
................................................................................
    /// release all memory and internal handles
    destructor Destroy; override;
    /// flush all log content to file
    // - if ForceDiskWrite is TRUE, will wait until written on disk (slow)
    procedure Flush(ForceDiskWrite: boolean);
    /// flush all log content to file and close the file
    procedure CloseLogFile;





















    {/ handle generic method enter / auto-leave tracing
     - this is the main method to be called within a procedure/function to trace:
     ! procedure TMyDB.SQLExecute(const SQL: RawUTF8);
     ! var ILog: ISynLog;
     ! begin
     !   ILog := TSynLogDB.Enter(self,'SQLExecute');
     !   // do some stuff
................................................................................
var
  /// internal list of registered TSynLogFamily
  // - up to MAX_SYNLOGFAMILY+1 families may be defined
  SynLogFamily: TObjectList = nil;

  /// internal list of created TSynLog instance, one per each log file on disk
  // - do not use directly - necessary for inlining TSynLogFamily.SynLog method

  SynLogFile: TObjectList = nil;

threadvar
  /// each thread can access to its own TSynLogFile
  // - TSynLogFile instance is SynLogFile[SynLogFileIndex[TSynLogFamily.Ident]-1]
  SynLogFileIndexThreadVar: TSynLogFileIndex;

{$ifndef NOEXCEPTIONINTERCEPT}

{ ESynException }

function ESynException.CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; 
................................................................................
var Index: ^TSynLogFileIndex;
    i: integer;
    ndx, n: cardinal;
begin
  result := CurrentHandleExceptionSynLog;
  if (result<>nil) and result.fFamily.fHandleExceptions then
    exit;
  if SynLogFile=nil then begin
    // we are here is no previous log content was trigerred
    for i := 0 to SynLogFamily.Count-1 do
      with TSynLogFamily(SynLogFamily.List[i]) do
      if fHandleExceptions then begin
        result := SynLog;
        exit;
      end;
  end else begin


    Index := @SynLogFileIndexThreadVar;
    n := SynLogFile.Count;
    for i := 0 to high(Index^) do begin
      ndx := Index^[i]-1;
      if ndx<=n then begin
        result := TSynLog(SynLogFile.List[ndx]);
        if result.fFamily.fHandleExceptions then
          exit;
      end;
    end;
    for i := 0 to n-1 do begin
      result := TSynLog(SynLogFile.List[i]);
      if result.fFamily.fHandleExceptions then
        exit;



    end;
  end;
  result := nil;
end;

type
  PExceptionRecord = ^TExceptionRecord;
................................................................................
  fLevelStackTrace :=
    [sllError,sllException,sllExceptionOS,sllFail,sllLastError,sllStackTrace];
end;

function TSynLogFamily.CreateSynLog: TSynLog;
var i: integer;
begin
  if SynLogFile=nil then
    GarbageCollectorFreeAndNil(SynLogFile,TObjectList.Create);


  result := fSynLogClass.Create(self);
  i := SynLogFile.Add(result);
  if (fPerThreadLog=ptOneFilePerThread) and
     (fRotateFileCount=0) and (fRotateFileSize=0) then
    SynLogFileIndexThreadVar[fIdent] := i+1 else
    fGlobalLog := result;



end;

var
  AutoFlushThread: THandle = 0;
  AutoFlushSecondElapsed: cardinal;

procedure AutoFlushProc(P: pointer); stdcall;  // TThread not needed here
var i: integer;
begin
  repeat
    Sleep(1000); // thread will awake every second to check of pending data
    if AutoFlushThread=0 then
      break; // avoid GPF
    if SynLogFile=nil then
      continue; // nothing to flush
    inc(AutoFlushSecondElapsed);


    for i := 0 to SynLogFile.Count-1 do
    with TSynLog(SynLogFile.List[i]) do
      if AutoFlushThread=0 then
        break else // avoid GPF
      if (fFamily.fAutoFlush<>0) and (fWriter<>nil) and
         (AutoFlushSecondElapsed mod fFamily.fAutoFlush=0) then
        if fWriter.B-fWriter.fTempBuf>1 then begin
          if not IsMultiThread and
             not fWriterStream.InheritsFrom(TFileStream) then
            IsMultiThread := true; // only TFileStream is thread-safe
          Flush(false); // write pending data
        end;



  until false;
  ExitThread(0);
end;

procedure TSynLogFamily.SetAutoFlush(TimeOut: cardinal);
var ID: cardinal;
begin
................................................................................

function TSynLogFamily.SynLog: TSynLog;
var ndx: integer;
begin
  if (fRotateFileCount=0) and (fRotateFileSize=0) and
     (fPerThreadLog=ptOneFilePerThread) then begin
    ndx := SynLogFileIndexThreadVar[fIdent]-1;
    if ndx>=0 then
      result := SynLogFile.List[ndx] else
      result := CreateSynLog;
  end else // for ptMergedInOneFile and ptIdentifiedInOnFile
    if fGlobalLog<>nil then
      result := fGlobalLog else
      result := CreateSynLog;
{$ifndef NOEXCEPTIONINTERCEPT}
  if fHandleExceptions and (CurrentHandleExceptionSynLog<>result) then
................................................................................
    fWriter.Flush;
    FreeAndNil(fWriterStream);
    FreeAndNil(fWriter);
  finally
    LeaveCriticalSection(fThreadLock);
  end;
end;

















procedure TSynLog.Flush(ForceDiskWrite: boolean);
begin
  if fWriter=nil then
    exit;
  EnterCriticalSection(fThreadLock);
  try






|
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
|



|







 







|








>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>







 







|
|
>
>
|
|
|
|
|
|
>
>
>













|


>
>
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>







 







|
|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
....
9895
9896
9897
9898
9899
9900
9901
9902
9903
9904
9905
9906
9907
9908
9909
9910
9911
9912
9913
9914
9915
9916
9917
9918
9919
9920
9921
9922
9923
9924
9925
9926
9927
9928
9929
.....
36821
36822
36823
36824
36825
36826
36827
36828
36829
36830
36831
36832
36833
36834
36835
36836
36837
36838
36839
36840
.....
36909
36910
36911
36912
36913
36914
36915
36916
36917
36918
36919
36920
36921
36922
36923
36924
36925
36926
36927
36928
36929
36930
36931
36932
36933
36934
36935
36936
36937
36938
36939
36940
36941
36942
36943
36944
36945
36946
36947
36948
36949
36950
.....
37220
37221
37222
37223
37224
37225
37226
37227
37228
37229
37230
37231
37232
37233
37234
37235
37236
37237
37238
37239
37240
37241
37242
37243
37244
37245
37246
37247
37248
37249
37250
37251
37252
37253
37254
37255
37256
37257
37258
37259
37260
37261
37262
37263
37264
37265
37266
37267
37268
37269
37270
37271
37272
37273
37274
37275
37276
37277
37278
37279
.....
37344
37345
37346
37347
37348
37349
37350
37351
37352
37353
37354
37355
37356
37357
37358
37359
.....
37526
37527
37528
37529
37530
37531
37532
37533
37534
37535
37536
37537
37538
37539
37540
37541
37542
37543
37544
37545
37546
37547
37548
37549
37550
37551
37552
37553
37554
37555
  - TSynTests will now write the tests summary with colored console output
  - added TSynTestCase.CleanUp virtual method for proper cleaning before Destroy
  - added TSynTestCase.CheckMatchAny() method for multi-value checks
  - TSynTestCase.TestFailed now triggers a debugger breakpoint when run from IDE
  - TSynLog will now append only the execution time when leaving a method,
    without the class/method name (smaller log file, and less resource use)
  - TSynLog header now contains system environment variables
  - added TSynLog.DebuggerNotify() and TSynLog.CloseLogFile / Release methods
  - protected the global TSynLog instances list against potential race condition
  - introducing TSynLogFamily.StackTraceUse: TSynLogStackTraceUse property
  - introducing TSynLogFamily.EchoToConsole: TSynLogInfos property, able to
    optionally echo the process log to the current console window, using colors
  - if new property TSynLogFamily.PerThreadLog is set to ptIdentifiedInOnFile,
    a new column will be added for each logged row - LogViewer has been updated
    to allow easy and efficient multi-thread process logging
  - introducing TSynLogFamily.RotateFileCount and RotateFileSizeKB properties,
................................................................................
    /// release all memory and internal handles
    destructor Destroy; override;
    /// flush all log content to file
    // - if ForceDiskWrite is TRUE, will wait until written on disk (slow)
    procedure Flush(ForceDiskWrite: boolean);
    /// flush all log content to file and close the file
    procedure CloseLogFile;
    /// flush all log content to file, close the file, and release the instance
    // - you should never call the Free method directly, since the instance
    // is registered in a global TObjectList and an access violation may
    // occur at application closing: you can use this Release method if you
    // are sure that you won't need this TSynLog instance any more
    // - ensure there is no pending Leave element in a stack-allocated ISynLog
    // (see below) 
    // - can be used e.g. to release the instance when finishing a thread when
    // Family.PerThreadLog=ptOneFilePerThread:
    // ! var
    // !   TThreadLogger : TSynLogClass = TSynLog;
    // !
    // ! procedure TMyThread.Execute;
    // ! var log : ISynLog;
    // ! begin
    // !   log := TThreadLogger.Enter(self);
    // ! ...
    // !   log := nil; // to force logging end of method
    // !   TThreadLogger.SynLog.Release;
    // ! end;
    procedure Release;
    {/ handle generic method enter / auto-leave tracing
     - this is the main method to be called within a procedure/function to trace:
     ! procedure TMyDB.SQLExecute(const SQL: RawUTF8);
     ! var ILog: ISynLog;
     ! begin
     !   ILog := TSynLogDB.Enter(self,'SQLExecute');
     !   // do some stuff
................................................................................
var
  /// internal list of registered TSynLogFamily
  // - up to MAX_SYNLOGFAMILY+1 families may be defined
  SynLogFamily: TObjectList = nil;

  /// internal list of created TSynLog instance, one per each log file on disk
  // - do not use directly - necessary for inlining TSynLogFamily.SynLog method
  // - also used by AutoFlushProc() to get a global list of TSynLog instances
  SynLogFileList: TObjectListLocked = nil;

threadvar
  /// each thread can access to its own TSynLogFile
  // - TSynLogFile instance is SynLogFileList[SynLogFileIndex[TSynLogFamily.Ident]-1]
  SynLogFileIndexThreadVar: TSynLogFileIndex;

{$ifndef NOEXCEPTIONINTERCEPT}

{ ESynException }

function ESynException.CustomLog(WR: TTextWriter; const Context: TSynLogExceptionContext): boolean; 
................................................................................
var Index: ^TSynLogFileIndex;
    i: integer;
    ndx, n: cardinal;
begin
  result := CurrentHandleExceptionSynLog;
  if (result<>nil) and result.fFamily.fHandleExceptions then
    exit;
  if SynLogFileList=nil then begin
    // we are here is no previous log content was trigerred
    for i := 0 to SynLogFamily.Count-1 do
      with TSynLogFamily(SynLogFamily.List[i]) do
      if fHandleExceptions then begin
        result := SynLog;
        exit;
      end;
  end else begin
    SynLogFileList.Lock;
    try
      Index := @SynLogFileIndexThreadVar;
      n := SynLogFileList.Count;
      for i := 0 to high(Index^) do begin
        ndx := Index^[i]-1;
        if ndx<=n then begin
          result := TSynLog(SynLogFileList.List[ndx]);
          if result.fFamily.fHandleExceptions then
            exit;
        end;
      end;
      for i := 0 to n-1 do begin
        result := TSynLog(SynLogFileList.List[i]);
        if result.fFamily.fHandleExceptions then
          exit;
      end;
    finally
      SynLogFileList.UnLock;
    end;
  end;
  result := nil;
end;

type
  PExceptionRecord = ^TExceptionRecord;
................................................................................
  fLevelStackTrace :=
    [sllError,sllException,sllExceptionOS,sllFail,sllLastError,sllStackTrace];
end;

function TSynLogFamily.CreateSynLog: TSynLog;
var i: integer;
begin
  if SynLogFileList=nil then
    GarbageCollectorFreeAndNil(SynLogFileList,TObjectListLocked.Create);
  SynLogFileList.Lock;
  try
    result := fSynLogClass.Create(self);
    i := SynLogFileList.Add(result);
    if (fPerThreadLog=ptOneFilePerThread) and
       (fRotateFileCount=0) and (fRotateFileSize=0) then
      SynLogFileIndexThreadVar[fIdent] := i+1 else
      fGlobalLog := result;
  finally
    SynLogFileList.UnLock;
  end;
end;

var
  AutoFlushThread: THandle = 0;
  AutoFlushSecondElapsed: cardinal;

procedure AutoFlushProc(P: pointer); stdcall;  // TThread not needed here
var i: integer;
begin
  repeat
    Sleep(1000); // thread will awake every second to check of pending data
    if AutoFlushThread=0 then
      break; // avoid GPF
    if SynLogFileList=nil then
      continue; // nothing to flush
    inc(AutoFlushSecondElapsed);
    SynLogFileList.Lock;
    try
      for i := 0 to SynLogFileList.Count-1 do
      with TSynLog(SynLogFileList.List[i]) do
        if AutoFlushThread=0 then
          break else // avoid GPF
        if (fFamily.fAutoFlush<>0) and (fWriter<>nil) and
           (AutoFlushSecondElapsed mod fFamily.fAutoFlush=0) then
          if fWriter.B-fWriter.fTempBuf>1 then begin
            if not IsMultiThread and
               not fWriterStream.InheritsFrom(TFileStream) then
              IsMultiThread := true; // only TFileStream is thread-safe
            Flush(false); // write pending data
          end;
     finally
       SynLogFileList.UnLock;
     end;
  until false;
  ExitThread(0);
end;

procedure TSynLogFamily.SetAutoFlush(TimeOut: cardinal);
var ID: cardinal;
begin
................................................................................

function TSynLogFamily.SynLog: TSynLog;
var ndx: integer;
begin
  if (fRotateFileCount=0) and (fRotateFileSize=0) and
     (fPerThreadLog=ptOneFilePerThread) then begin
    ndx := SynLogFileIndexThreadVar[fIdent]-1;
    if ndx>=0 then // SynLogFileList.Lock/Unlock is not mandatory here
      result := SynLogFileList.List[ndx] else
      result := CreateSynLog;
  end else // for ptMergedInOneFile and ptIdentifiedInOnFile
    if fGlobalLog<>nil then
      result := fGlobalLog else
      result := CreateSynLog;
{$ifndef NOEXCEPTIONINTERCEPT}
  if fHandleExceptions and (CurrentHandleExceptionSynLog<>result) then
................................................................................
    fWriter.Flush;
    FreeAndNil(fWriterStream);
    FreeAndNil(fWriter);
  finally
    LeaveCriticalSection(fThreadLock);
  end;
end;

procedure TSynLog.Release;
begin
  SynLogFileList.Lock;
  try
    CloseLogFile;
    SynLogFileList.Remove(self);
    with fFamily do
      if (fPerThreadLog=ptOneFilePerThread) and
         (fRotateFileCount=0) and (fRotateFileSize=0) then
        SynLogFileIndexThreadVar[fIdent] := 0;
  finally
    SynLogFileList.Unlock;
  end;
  Free;
end;

procedure TSynLog.Flush(ForceDiskWrite: boolean);
begin
  if fWriter=nil then
    exit;
  EnterCriticalSection(fThreadLock);
  try