UThread.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. unit UThread;
  2. {$IFDEF FPC}
  3. {$MODE Delphi}
  4. {$ENDIF}
  5. { Copyright (c) 2016 by Albert Molina
  6. Distributed under the MIT software license, see the accompanying file LICENSE
  7. or visit http://www.opensource.org/licenses/mit-license.php.
  8. This unit is a part of Pascal Coin, a P2P crypto currency without need of
  9. historical operations.
  10. If you like it, consider a donation using BitCoin:
  11. 16K3HCZRhFUtM8GdWRcfKeaa6KsuyxZaYk
  12. }
  13. interface
  14. uses
  15. {$IFnDEF FPC}
  16. Windows,
  17. {$ELSE}
  18. {$IFDEF LINUX}cthreads,{$ENDIF}
  19. {$ENDIF}
  20. Classes, SyncObjs, UBaseTypes;
  21. {$I config.inc}
  22. Type
  23. TPCCriticalSection = Class(TCriticalSection)
  24. private
  25. FCounterLock : TCriticalSection;
  26. FWaitingForCounter : Integer;
  27. FCurrentThread : Cardinal;
  28. FStartedTickCount : TTickCount;
  29. FName : String;
  30. public
  31. Constructor Create(const AName : String);
  32. Destructor Destroy; override;
  33. {$IFDEF HIGHLOG}
  34. procedure Acquire; override;
  35. procedure Release; override;
  36. function TryEnter: Boolean; { HS - had 'override' in development }
  37. {$ENDIF}
  38. Property CurrentThread : Cardinal read FCurrentThread;
  39. Property WaitingForCounter : Integer read FWaitingForCounter;
  40. Property StartedTickCount : TTickCount read FStartedTickCount; // Renamed from StartedTimestamp to StartedTickCount to avoid confusion
  41. Property Name : String read FName;
  42. end;
  43. TPCThread = Class;
  44. TPCThreadClass = Class of TPCThread;
  45. TPCThread = Class(TThread)
  46. private
  47. FDebugStep: String;
  48. FStartTickCount : TTickCount;
  49. protected
  50. procedure DoTerminate; override;
  51. procedure Execute; override;
  52. procedure BCExecute; virtual; abstract;
  53. public
  54. Class function ThreadClassFound(tclass : TPCThreadClass; Exclude : TObject) : Integer;
  55. Class function ThreadCount : Integer;
  56. Class function GetThread(index : Integer) : TPCThread;
  57. Class function GetThreadByClass(tclass : TPCThreadClass; Exclude : TObject) : TPCThread;
  58. Class Procedure ProtectEnterCriticalSection(Const Sender : TObject; var Lock : TPCCriticalSection);
  59. Class Function TryProtectEnterCriticalSection(Const Sender : TObject; MaxWaitMilliseconds : Cardinal; var Lock : TPCCriticalSection) : Boolean;
  60. Class Procedure ThreadsListInfo(list: TStrings);
  61. constructor Create(CreateSuspended: Boolean);
  62. destructor Destroy; override;
  63. Property DebugStep : String read FDebugStep write FDebugStep;
  64. property Terminated;
  65. End;
  66. TPCThreadList = class
  67. private
  68. FList: TList;
  69. FLock: TPCCriticalSection;
  70. public
  71. constructor Create(const AName : String);
  72. destructor Destroy; override;
  73. function Add(Item: Pointer) : Integer;
  74. procedure Clear;
  75. procedure Remove(Item: Pointer); inline;
  76. function LockList: TList;
  77. function TryLockList(MaxWaitMilliseconds : Cardinal; var lockedList : TList) : Boolean;
  78. procedure UnlockList; inline;
  79. end;
  80. implementation
  81. uses
  82. SysUtils, ULog, UConst;
  83. { TPCThread }
  84. Var _threads : TPCThreadList;
  85. constructor TPCThread.Create(CreateSuspended: Boolean);
  86. begin
  87. inherited Create(CreateSuspended);
  88. {$IFDEF HIGHLOG}TLog.NewLog(ltdebug,Classname,'Created Thread '+IntToHex(PtrInt(Self),8));{$ENDIF}
  89. end;
  90. destructor TPCThread.Destroy;
  91. begin
  92. inherited;
  93. end;
  94. procedure TPCThread.DoTerminate;
  95. begin
  96. inherited;
  97. end;
  98. procedure TPCThread.Execute;
  99. Var l : TList;
  100. i : Integer;
  101. begin
  102. FStartTickCount := TPlatform.GetTickCount;
  103. FDebugStep := '';
  104. i := _threads.Add(Self);
  105. try
  106. {$IFDEF HIGHLOG}TLog.NewLog(ltdebug,Classname,'Starting Thread '+IntToHex(PtrInt(Self),8)+' in pos '+inttostr(i+1));{$ENDIF}
  107. Try
  108. Try
  109. BCExecute;
  110. FDebugStep := 'Finalized BCExecute';
  111. Finally
  112. Terminate;
  113. End;
  114. Except
  115. On E:Exception do begin
  116. TLog.NewLog(lterror,Classname,'Exception inside a Thread at step: '+FDebugStep+' ('+E.ClassName+'): '+E.Message);
  117. Raise;
  118. end;
  119. End;
  120. finally
  121. l := _threads.LockList;
  122. Try
  123. i := l.Remove(Self);
  124. {$IFDEF HIGHLOG}TLog.NewLog(ltdebug,Classname,'Finalizing Thread in pos '+inttostr(i+1)+'/'+inttostr(l.Count+1)+' working time: '+FormatFloat('0.000',TPlatform.GetElapsedMilliseconds(FStartTickCount) / 1000)+' sec');{$ENDIF}
  125. Finally
  126. _threads.UnlockList;
  127. End;
  128. end;
  129. end;
  130. class function TPCThread.GetThread(index: Integer): TPCThread;
  131. Var l : TList;
  132. begin
  133. Result := Nil;
  134. l := _threads.LockList;
  135. try
  136. if (index<0) or (index>=l.Count) then exit;
  137. Result := TPCThread(l[index]);
  138. finally
  139. _threads.UnlockList;
  140. end;
  141. end;
  142. class function TPCThread.GetThreadByClass(tclass: TPCThreadClass; Exclude: TObject): TPCThread;
  143. Var l : TList;
  144. i : Integer;
  145. begin
  146. Result := Nil;
  147. if Not Assigned(_threads) then exit;
  148. l := _threads.LockList;
  149. try
  150. for i := 0 to l.Count - 1 do begin
  151. if (TPCThread(l[i]) is tclass) And ((l[i])<>Exclude) then begin
  152. Result := TPCThread(l[i]);
  153. exit;
  154. end;
  155. end;
  156. finally
  157. _threads.UnlockList;
  158. end;
  159. end;
  160. class procedure TPCThread.ProtectEnterCriticalSection(Const Sender : TObject; var Lock: TPCCriticalSection);
  161. begin
  162. {$IFDEF HIGHLOG}
  163. if Not Lock.TryEnter then begin
  164. Lock.Acquire;
  165. end;
  166. {$ELSE}
  167. Lock.Acquire;
  168. {$ENDIF}
  169. end;
  170. class function TPCThread.ThreadClassFound(tclass: TPCThreadClass; Exclude : TObject): Integer;
  171. Var l : TList;
  172. begin
  173. Result := -1;
  174. if Not Assigned(_threads) then exit;
  175. l := _threads.LockList;
  176. try
  177. for Result := 0 to l.Count - 1 do begin
  178. if (TPCThread(l[Result]) is tclass) And ((l[Result])<>Exclude) then exit;
  179. end;
  180. Result := -1;
  181. finally
  182. _threads.UnlockList;
  183. end;
  184. end;
  185. class function TPCThread.ThreadCount: Integer;
  186. Var l : TList;
  187. begin
  188. l := _threads.LockList;
  189. try
  190. Result := l.Count;
  191. finally
  192. _threads.UnlockList;
  193. end;
  194. end;
  195. class procedure TPCThread.ThreadsListInfo(list: TStrings);
  196. Var l : TList;
  197. i : Integer;
  198. begin
  199. l := _threads.LockList;
  200. try
  201. list.BeginUpdate;
  202. list.Clear;
  203. for i := 0 to l.Count - 1 do begin
  204. list.Add(Format('%.2d/%.2d <%s> Time:%s sec - Step: %s',[i+1,l.Count,TPCThread(l[i]).ClassName,FormatFloat('0.000',(TPlatform.GetElapsedMilliseconds(TPCThread(l[i]).FStartTickCount) / 1000)),TPCThread(l[i]).DebugStep] ));
  205. end;
  206. list.EndUpdate;
  207. finally
  208. _threads.UnlockList;
  209. end;
  210. end;
  211. class function TPCThread.TryProtectEnterCriticalSection(const Sender: TObject;
  212. MaxWaitMilliseconds: Cardinal; var Lock: TPCCriticalSection): Boolean;
  213. Var tc : TTickCount;
  214. {$IFDEF HIGHLOG}
  215. tc2,tc3,lockStartedTimestamp : TTickCount;
  216. lockCurrThread : TThreadID;
  217. lockWatingForCounter : Cardinal;
  218. s : String;
  219. {$ENDIF}
  220. begin
  221. tc := TPlatform.GetTickCount;
  222. if MaxWaitMilliseconds>60000 then MaxWaitMilliseconds := 60000;
  223. {$IFDEF HIGHLOG}
  224. lockWatingForCounter := Lock.WaitingForCounter;
  225. lockStartedTimestamp := Lock.StartedTimestamp;
  226. lockCurrThread := Lock.CurrentThread;
  227. {$ENDIF}
  228. Repeat
  229. Result := Lock.TryEnter;
  230. if Not Result then sleep(1);
  231. Until (Result) Or (TPlatform.GetElapsedMilliseconds(tc)>MaxWaitMilliseconds);
  232. {$IFDEF HIGHLOG}
  233. if Not Result then begin
  234. tc2 := TPlatform.GetTickCount;
  235. if lockStartedTimestamp=0 then lockStartedTimestamp := Lock.StartedTimestamp;
  236. if lockStartedTimestamp=0 then tc3 := 0
  237. else tc3 := tc2-lockStartedTimestamp;
  238. s := Format('Cannot Protect a critical section %s %s class %s after %d milis locked by %s waiting %d-%d elapsed milis: %d',
  239. [IntToHex(PtrInt(Lock),8),Lock.Name,
  240. Sender.ClassName,tc2-tc,
  241. IntToHex(lockCurrThread,8)+'-'+IntToHex(Lock.CurrentThread,8),
  242. lockWatingForCounter,Lock.WaitingForCounter,
  243. tc3
  244. ]);
  245. TLog.NewLog(ltdebug,Classname,s);
  246. end;
  247. {$ENDIF}
  248. end;
  249. { TPCThreadList }
  250. function TPCThreadList.Add(Item: Pointer) : Integer;
  251. begin
  252. LockList;
  253. Try
  254. Result := FList.Add(Item);
  255. Finally
  256. UnlockList;
  257. End;
  258. end;
  259. procedure TPCThreadList.Clear;
  260. begin
  261. LockList;
  262. Try
  263. FList.Clear;
  264. Finally
  265. UnlockList;
  266. End;
  267. end;
  268. constructor TPCThreadList.Create(const AName : String);
  269. begin
  270. FLock := TPCCriticalSection.Create(AName);
  271. FList := TList.Create;
  272. end;
  273. destructor TPCThreadList.Destroy;
  274. begin
  275. LockList;
  276. try
  277. FreeAndNil(FList);
  278. inherited Destroy;
  279. finally
  280. UnlockList;
  281. FreeAndNil(FLock);
  282. end;
  283. end;
  284. function TPCThreadList.LockList: TList;
  285. begin
  286. TPCThread.ProtectEnterCriticalSection(Self,FLock);
  287. Result := FList;
  288. end;
  289. procedure TPCThreadList.Remove(Item: Pointer);
  290. begin
  291. LockList;
  292. try
  293. FList.Remove(Item);
  294. finally
  295. UnlockList;
  296. end;
  297. end;
  298. function TPCThreadList.TryLockList(MaxWaitMilliseconds: Cardinal; var lockedList: TList): Boolean;
  299. begin
  300. lockedList := FList;
  301. Result := TPCThread.TryProtectEnterCriticalSection(Self,MaxWaitMilliseconds,FLock);
  302. end;
  303. procedure TPCThreadList.UnlockList;
  304. begin
  305. FLock.Release;
  306. end;
  307. { TPCCriticalSection }
  308. {$IFDEF HIGHLOG}
  309. procedure TPCCriticalSection.Acquire;
  310. Var continue, logged : Boolean;
  311. startTC : TTickCount;
  312. begin
  313. startTC := TPlatform.GetTickCount;
  314. FCounterLock.Acquire;
  315. try
  316. FWaitingForCounter := FWaitingForCounter + 1;
  317. finally
  318. FCounterLock.Release;
  319. end;
  320. logged := false;
  321. Repeat
  322. continue := inherited TryEnter;
  323. if (Not continue) then begin
  324. If (not logged) And ((FStartedTimestamp>0) And ((FStartedTimestamp+1000)<TPlatform.GetTickCount)) then begin
  325. logged := true;
  326. TLog.NewLog(ltdebug,ClassName,'ALERT Critical section '+IntToHex(PtrInt(Self),8)+' '+Name+
  327. ' locked by '+IntToHex(FCurrentThread,8)+' waiting '+
  328. IntToStr(FWaitingForCounter)+' elapsed milis: '+IntToStr(TPlatform.GetTickCount-FStartedTimestamp) );
  329. continue := true;
  330. inherited;
  331. end else sleep(1);
  332. end;
  333. Until continue;
  334. if (logged) then begin
  335. TLog.NewLog(ltdebug,Classname,'ENTER Critical section '+IntToHex(PtrInt(Self),8)+' '+Name+' elapsed milis: '+IntToStr(TPlatform.GetTickCount - startTC) );
  336. end;
  337. FCounterLock.Acquire;
  338. try
  339. FWaitingForCounter := FWaitingForCounter - 1;
  340. finally
  341. FCounterLock.Release;
  342. end;
  343. FCurrentThread := TThread.CurrentThread.ThreadID;
  344. FStartedTimestamp := TPlatform.GetTickCount;
  345. inherited;
  346. end;
  347. {$ENDIF}
  348. constructor TPCCriticalSection.Create(const AName : String);
  349. begin
  350. FCounterLock := TCriticalSection.Create;
  351. FWaitingForCounter := 0;
  352. FCurrentThread := 0;
  353. FStartedTickCount := 0;
  354. FName := AName;
  355. inherited Create;
  356. {$IFDEF HIGHLOG}TLog.NewLog(ltDebug,ClassName,'Created critical section '+IntToHex(PtrInt(Self),8)+' '+AName );{$ENDIF}
  357. end;
  358. destructor TPCCriticalSection.Destroy;
  359. begin
  360. FCounterLock.Free;
  361. inherited;
  362. end;
  363. {$IFDEF HIGHLOG}
  364. procedure TPCCriticalSection.Release;
  365. begin
  366. FCurrentThread := 0;
  367. FStartedTimestamp := 0;
  368. inherited;
  369. end;
  370. function TPCCriticalSection.TryEnter: Boolean;
  371. begin
  372. FCounterLock.Acquire;
  373. try
  374. FWaitingForCounter := FWaitingForCounter + 1;
  375. finally
  376. FCounterLock.Release;
  377. end;
  378. If inherited TryEnter then begin
  379. FCurrentThread := TThread.CurrentThread.ThreadID;
  380. FStartedTimestamp := TPlatform.GetTickCount;
  381. Result := true;
  382. end else Result := false;
  383. FCounterLock.Acquire;
  384. try
  385. FWaitingForCounter := FWaitingForCounter - 1;
  386. finally
  387. FCounterLock.Release;
  388. end;
  389. end;
  390. {$ENDIF}
  391. initialization
  392. _threads := TPCThreadList.Create('GLOBAL_THREADS');
  393. finalization
  394. FreeAndNil(_threads);
  395. end.