IdSync.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.13 03/16/05 11:15:42 AM JSouthwell
  18. Named the IdNotify thread for simpler debugging.
  19. Rev 1.12 2004.04.13 10:22:52 PM czhower
  20. Changed procedure to class method.
  21. Rev 1.11 4/12/2004 11:44:36 AM BGooijen
  22. fix
  23. Rev 1.10 4/12/2004 11:36:56 AM BGooijen
  24. NotifyThread can be cleaned up with procedure now
  25. Rev 1.9 2004.03.11 10:14:46 AM czhower
  26. Improper cast fixed.
  27. Rev 1.8 2004.02.29 8:23:16 PM czhower
  28. Fixed visibility mismatch.
  29. Rev 1.7 2004.02.25 10:11:42 AM czhower
  30. Fixed visibility in notify
  31. Rev 1.6 2004.02.03 4:16:54 PM czhower
  32. For unit name changes.
  33. Rev 1.5 1/1/2004 11:56:10 PM PIonescu
  34. Fix for TIdNotifyMethod's constructor
  35. Rev 1.4 2003.12.31 7:33:20 PM czhower
  36. Constructor bug fix.
  37. Rev 1.3 5/12/2003 9:17:42 AM GGrieve
  38. compile fix
  39. Rev 1.2 2003.09.18 5:42:14 PM czhower
  40. Removed TIdThreadBase
  41. Rev 1.1 05.6.2003 ã. 11:30:12 DBondzhev
  42. Mem leak fix for notifiers created in main thread. Also WaitFor for waiting
  43. notification to be executed.
  44. Rev 1.0 11/13/2002 09:00:10 AM JPMugaas
  45. }
  46. unit IdSync;
  47. // Author: Chad Z. Hower - a.k.a. Kudzu
  48. interface
  49. {$i IdCompilerDefines.inc}
  50. {$IF DEFINED(HAS_STATIC_TThread_ForceQueue) AND DEFINED(BROKEN_TThread_ForceQueue)}
  51. {$UNDEF HAS_STATIC_TThread_ForceQueue}
  52. {$ENDIF}
  53. {$IFNDEF HAS_STATIC_TThread_ForceQueue}
  54. {$DEFINE NotifyThreadNeeded}
  55. {$ELSE}
  56. {$UNDEF NotifyThreadNeeded}
  57. {$ENDIF}
  58. uses
  59. Classes,
  60. IdGlobal
  61. {$IFDEF NotifyThreadNeeded}
  62. , IdThread
  63. {$ENDIF}
  64. ;
  65. type
  66. TIdSync = class(TObject)
  67. protected
  68. procedure DoSynchronize; virtual; abstract;
  69. public
  70. constructor Create; virtual;
  71. procedure Synchronize;
  72. class procedure SynchronizeMethod(AMethod: TThreadMethod);
  73. //
  74. end
  75. // TODO: deprecate TIdSync only if anonymous procedures are supported?
  76. // Delphi's TThread.Synchronize() supports them, but FreePascal's does not...
  77. {.$IFDEF HAS_STATIC_TThread_Synchronize_AnonProc}
  78. //deprecated 'Use static TThread.Synchronize() with an anonymous procedure'
  79. deprecated 'Use static TThread.Synchronize()'
  80. {.$ENDIF}
  81. ;
  82. TIdNotify = class(TObject)
  83. protected
  84. FMainThreadUsesNotify: Boolean;
  85. //
  86. procedure DoNotify; virtual; abstract;
  87. {$IFNDEF USE_OBJECT_ARC}
  88. procedure InternalDoNotify;
  89. {$ENDIF}
  90. public
  91. constructor Create; virtual; // here to make virtual
  92. procedure Notify;
  93. class procedure NotifyMethod(AMethod: TThreadMethod; AForceQueue: Boolean = False);
  94. //
  95. property MainThreadUsesNotify: Boolean read FMainThreadUsesNotify write FMainThreadUsesNotify; // deprecated
  96. end
  97. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  98. // TODO: deprecate TIdNotify only if anonymous procedures are available?
  99. // Delphi's TThread.(Force)Queue() supports them, but FreePascal's does not...
  100. {.$IFDEF HAS_STATIC_TThread_Queue_AnonProc}
  101. //deprecated 'Use static TThread.Queue() or TThread.ForceQueue() with an anonymous procedure'
  102. deprecated 'Use static TThread.Queue() or TThread.ForceQueue()'
  103. {.$ENDIF}
  104. {$ENDIF}
  105. ;
  106. {$I IdSymbolDeprecatedOff.inc}
  107. TIdNotifyMethod = class(TIdNotify)
  108. protected
  109. FMethod: TThreadMethod;
  110. //
  111. procedure DoNotify; override;
  112. public
  113. constructor Create(AMethod: TThreadMethod); reintroduce; virtual;
  114. end deprecated {$IFDEF HAS_STATIC_TThread_ForceQueue}'Use static TThread.Queue() or TThread.ForceQueue()'{$ELSE}'Use static TThread.Queue()'{$ENDIF};
  115. {$I IdSymbolDeprecatedOn.inc}
  116. implementation
  117. uses
  118. //facilitate inlining only.
  119. {$IF DEFINED(NotifyThreadNeeded) AND DEFINED(HAS_UNIT_Generics_Collections)}
  120. System.Generics.Collections,
  121. {$IFEND}
  122. {$IF DEFINED(WINDOWS) AND DEFINED(DCC_2010_OR_ABOVE)}
  123. Windows,
  124. {$ELSEIF DEFINED(USE_VCL_POSIX)}
  125. Posix.SysSelect,
  126. Posix.SysTime,
  127. {$IFEND}
  128. SysUtils
  129. {$IFNDEF NotifyThreadNeeded}
  130. , IdThread
  131. {$ENDIF}
  132. ;
  133. // TODO: there is a bug in FireMonkey prior to XE7 where FMX.TApplication does
  134. // not assign a handler to the Classes.WakeMainThread callback (see QC #123579).
  135. // Without that, TThread.Synchronize() and TThread.Queue() will not do anything
  136. // if the main message queue is idle at the moment they are called!!! If the
  137. // main thread *happens* to receive a message at a later time, say from UI
  138. // activity, then they will be processed. But for a background process, we
  139. // cannot rely on that. Need an alternative solution for those versions of
  140. // FireMonkey...
  141. {$IFDEF NotifyThreadNeeded}
  142. type
  143. // This is done with a NotifyThread instead of PostMessage because starting
  144. // with D6/Kylix Borland radically modified the mechanisms for .Synchronize.
  145. // This is a bit more code in the end, but its source compatible and does not
  146. // rely on Indy directly accessing any OS APIs and performance is still more
  147. // than acceptable, especially considering Notifications are low priority.
  148. {$IFDEF HAS_GENERICS_TThreadList}
  149. TIdNotifyThreadList = TThreadList<TIdNotify>;
  150. TIdNotifyList = TList<TIdNotify>;
  151. {$ELSE}
  152. // TODO: flesh out to match TThreadList<TIdNotify> and TList<TIdNotify> for non-Generics compilers...
  153. TIdNotifyThreadList = TThreadList;
  154. TIdNotifyList = TList;
  155. {$ENDIF}
  156. TIdNotifyThread = class(TIdThread)
  157. protected
  158. FEvent: TIdLocalEvent;
  159. FNotifications: TIdNotifyThreadList;
  160. public
  161. procedure AddNotification(ASync: TIdNotify);
  162. constructor Create; reintroduce;
  163. destructor Destroy; override;
  164. class procedure FreeThread;
  165. procedure Run; override;
  166. end;
  167. var
  168. GNotifyThread: TIdNotifyThread = nil;
  169. procedure CreateNotifyThread;
  170. begin
  171. // TODO: this function has a race condition if it is called by multiple
  172. // threads at the same time and GNotifyThread has not been assigned yet!
  173. // Need to use something like InterlockedCompareExchangeObj() so any
  174. // duplicate threads can be freed...
  175. {
  176. Thread := TIdNotifyThread.Create(True);
  177. if InterlockedCompareExchangeObj(GNotifyThread, Thread, nil) <> nil then begin
  178. Thread.Free;
  179. end else begin
  180. Thread.Start;
  181. end;
  182. }
  183. if GNotifyThread = nil then begin
  184. GNotifyThread := TIdNotifyThread.Create;
  185. end;
  186. end;
  187. {$ENDIF}
  188. { TIdSync }
  189. constructor TIdSync.Create;
  190. begin
  191. inherited Create;
  192. end;
  193. procedure DoThreadSync(SyncProc: TThreadMethod);
  194. begin
  195. {
  196. if not Assigned(Classes.WakeMainThread) then
  197. begin
  198. // TODO: if WakeMainThread is not assigned, need to force a message into
  199. // the main message queue so TApplication.Idle() will be called so it can
  200. // call CheckSynchronize():
  201. //
  202. // on Windows, call PostMessage() to post a WM_NULL message to the TApplication window...
  203. //
  204. // on OSX (and iOS?), call NSApp.sendEvent(???), but with what kind of event?
  205. //
  206. // on Android, what to do???
  207. // We can't put the message in the queue before calling TThread.Synchronize(),
  208. // as it might get processed before Synchronize() can queue the procedure.
  209. // Might have to use TThread.Queue() instead and wait on a manual TEvent...
  210. end else
  211. begin
  212. }
  213. // RLebeau 6/7/2016: there are race conditions if multiple threads call
  214. // TThread.Synchronize() on the same TThread object at the same time
  215. // (such as this unit's GNotifyThread object)...
  216. // Fortunately, the static versions of TThread.Synchronize() can skip the
  217. // race conditions when the AThread parameter is nil, so we are safe here...
  218. //·RS-78837
  219. TThread.Synchronize(nil, SyncProc);
  220. // end;
  221. end;
  222. procedure TIdSync.Synchronize;
  223. begin
  224. DoThreadSync(DoSynchronize);
  225. end;
  226. class procedure TIdSync.SynchronizeMethod(AMethod: TThreadMethod);
  227. begin
  228. DoThreadSync(AMethod);
  229. end;
  230. { TIdNotify }
  231. constructor TIdNotify.Create;
  232. begin
  233. inherited Create;
  234. end;
  235. procedure DoThreadQueue(QueueProc: TThreadMethod
  236. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  237. ; AForceQueue: Boolean = False
  238. {$ENDIF}
  239. );
  240. begin
  241. {
  242. if not Assigned(Classes.WakeMainThread) then
  243. begin
  244. // TODO: if WakeMainThread is not assigned, need to force a message into
  245. // the main message queue so TApplication.Idle() will be called so it can
  246. // call CheckSynchronize():
  247. //
  248. // on Windows, call PostMessage() to post a WM_NULL message to the TApplication window...
  249. //
  250. // on OSX (and iOS?), call NSApp.sendEvent(???), but with what kind of event?
  251. //
  252. // on Android, what to do???
  253. // We can't put the message in the queue before calling TThread.Queue(),
  254. // as it might get processed before Queue() can queue the procedure.
  255. // Might have to wait on a manual TEvent...
  256. end else
  257. begin
  258. }
  259. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  260. if AForceQueue then begin
  261. TThread.ForceQueue(nil, QueueProc);
  262. end else begin
  263. TThread.Queue(nil, QueueProc);
  264. end;
  265. {$ELSE}
  266. // TODO: FreePascal/Lazarus has Application.QueueAsyncCall(), but it is in the Forms unit!
  267. {
  268. if AForceQueue then begin
  269. Application.QueueAsyncCall(NotifyAsync, @QueueProc);
  270. else
  271. TThread.Queue(nil, QueueProc);
  272. }
  273. TThread.Queue(nil, QueueProc);
  274. {$ENDIF}
  275. // end;
  276. end;
  277. procedure TIdNotify.Notify;
  278. begin
  279. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  280. DoThreadQueue(
  281. {$IFNDEF USE_OBJECT_ARC}
  282. InternalDoNotify
  283. {$ELSE}
  284. DoNotify
  285. {$ENDIF}
  286. , MainThreadUsesNotify
  287. );
  288. {$ELSE}
  289. if InMainThread then
  290. begin
  291. // RLebeau 9/4/2010: MainThreadUsesNotify only has meaning now when
  292. // TThread.Queue() is not available, as it calls the specified method
  293. // immediately if invoked in the main thread! To go back to the old
  294. // behavior, we would have to re-enable use of TIdNotifyThread, which is
  295. // another interface change...
  296. // RLebeau 6/21/2017: Delphi 10.2 Tokyo added TThread.ForceQueue() to let
  297. // the specified method be queued even if invoked by the main thread! So
  298. // lets re-enable use of TIdNotifyThread in earlier versions, to maintain
  299. // consistent notification behavior...
  300. if not MainThreadUsesNotify then
  301. begin
  302. {$IFNDEF USE_OBJECT_ARC}
  303. InternalDoNotify;
  304. {$ELSE}
  305. DoNotify;
  306. {$ENDIF}
  307. end else
  308. begin
  309. // TODO: if available, use TThread.CreateAnonymousThread() to call TThread.Queue()?
  310. //TThread.CreateAnonymousThread(Notify).Start;
  311. // TODO: FreePascal/Lazarus has Application.QueueAsyncCall(), but it is in the Forms unit!
  312. {
  313. uses Forms;
  314. procedure TIdNotify.NotifyAsync(Data: PtrInt);
  315. begin
  316. ($IFNDEF USE_OBJECT_ARC)
  317. InternalDoNotify;
  318. ($ELSE)
  319. DoNotify;
  320. ($ENDIF)
  321. end;
  322. Application.QueueAsyncCall(@NotifyAsync, 0);
  323. }
  324. {$IFNDEF USE_OBJECT_ARC}
  325. try
  326. {$ENDIF}
  327. CreateNotifyThread;
  328. GNotifyThread.AddNotification(Self);
  329. {$IFNDEF USE_OBJECT_ARC}
  330. except
  331. Free;
  332. raise;
  333. end;
  334. {$ENDIF}
  335. end;
  336. end else begin
  337. {$IFNDEF USE_OBJECT_ARC}
  338. try
  339. {$ENDIF}
  340. DoThreadQueue(
  341. {$IFNDEF USE_OBJECT_ARC}
  342. InternalDoNotify
  343. {$ELSE}
  344. DoNotify
  345. {$ENDIF}
  346. );
  347. {$IFNDEF USE_OBJECT_ARC}
  348. except
  349. Free;
  350. raise;
  351. end;
  352. {$ENDIF}
  353. end;
  354. {$ENDIF}
  355. end;
  356. {$IFNDEF USE_OBJECT_ARC}
  357. procedure TIdNotify.InternalDoNotify;
  358. begin
  359. try
  360. DoNotify;
  361. finally
  362. Free;
  363. end;
  364. end;
  365. {$ENDIF}
  366. class procedure TIdNotify.NotifyMethod(AMethod: TThreadMethod; AForceQueue: Boolean = False);
  367. begin
  368. {$IFDEF HAS_STATIC_TThread_ForceQueue}
  369. DoThreadQueue(AMethod, AForceQueue);
  370. {$ELSE}
  371. if InMainThread then begin
  372. if not AForceQueue then begin
  373. AMethod;
  374. end else begin
  375. {$I IdSymbolDeprecatedOff.inc}
  376. with TIdNotifyMethod.Create(AMethod) do begin
  377. MainThreadUsesNotify := True;
  378. Notify;
  379. end;
  380. {$I IdSymbolDeprecatedOn.inc}
  381. end;
  382. end else begin
  383. DoThreadQueue(AMethod);
  384. end;
  385. {$ENDIF}
  386. end;
  387. {$IFDEF NotifyThreadNeeded}
  388. { TIdNotifyThread }
  389. procedure TIdNotifyThread.AddNotification(ASync: TIdNotify);
  390. begin
  391. FNotifications.Add(ASync);
  392. FEvent.SetEvent;
  393. end;
  394. constructor TIdNotifyThread.Create;
  395. begin
  396. FEvent := TIdLocalEvent.Create;
  397. FNotifications := TIdNotifyThreadList.Create;
  398. // Must be before - Thread starts running when we call inherited
  399. inherited Create(False, False, 'IdNotify'); {do not localize}
  400. end;
  401. destructor TIdNotifyThread.Destroy;
  402. var
  403. {$IFNDEF USE_OBJECT_ARC}
  404. LNotify: TIdNotify;
  405. {$ENDIF}
  406. LList: TIdNotifyList;
  407. begin
  408. // Free remaining Notifications if there is something that is still in
  409. // the queue after thread was terminated
  410. LList := FNotifications.LockList;
  411. try
  412. {$IFDEF USE_OBJECT_ARC}
  413. LList.Clear; // Items are auto-freed
  414. {$ELSE}
  415. while LList.Count > 0 do begin
  416. LNotify := {$IFDEF HAS_GENERICS_TList}LList.Items[0]{$ELSE}TIdNotify(LList.Items[0]){$ENDIF};
  417. LList.Delete(0);
  418. LNotify.Free;
  419. end;
  420. {$ENDIF}
  421. finally
  422. FNotifications.UnlockList;
  423. end;
  424. FNotifications.Free;
  425. FEvent.Free;
  426. inherited Destroy;
  427. end;
  428. class procedure TIdNotifyThread.FreeThread;
  429. begin
  430. if GNotifyThread <> nil then begin
  431. GNotifyThread.Stop;
  432. GNotifyThread.FEvent.SetEvent;
  433. GNotifyThread.WaitFor;
  434. // Instead of FreeOnTerminate so we can set the reference to nil
  435. FreeAndNil(GNotifyThread);
  436. end;
  437. end;
  438. procedure TIdNotifyThread.Run;
  439. // NOTE: Be VERY careful with making changes to this proc. It is VERY delicate and the order
  440. // of execution is very important. Small changes can have drastic effects
  441. var
  442. LNotifications: TIdNotifyList;
  443. LNotify: TIdNotify;
  444. begin
  445. FEvent.WaitForEver;
  446. // TODO: If TThread.Queue() is available, just queue the entire
  447. // FNotifications list to the main thread and exit. No sense in
  448. // locking and unlocking the list on every notification since we
  449. // will not be waiting on them here.
  450. //
  451. // Unlocking and relocking the list should only be needed if we
  452. // have to resort to using TThread.Synchronize(), so we don't block
  453. // other threads from queuing new notifications while a notification
  454. // is running...
  455. {
  456. if not Stopped then begin
  457. try
  458. LNotifications := FNotifications.LockList;
  459. try
  460. while (LNotifications.Count > 0) and (not Stopped) do
  461. begin
  462. LNotify := ($IFDEF HAS_GENERICS_TList)LNotifications.Items[0]($ELSE)TIdNotify(LNotifications.Items[0])($ENDIF);
  463. LNotifications.Delete(0);
  464. ($IFNDEF USE_OBJECT_ARC)
  465. try
  466. DoThreadQueue(LNotify.InternalDoNotify);
  467. except
  468. LNotify.Free;
  469. raise;
  470. end;
  471. ($ELSE)
  472. try
  473. DoThreadQueue(LNotify.DoNotify);
  474. finally
  475. LNotify := nil;
  476. end;
  477. ($ENDIF)
  478. end;
  479. finally
  480. FNotifications.UnlockList;
  481. end;
  482. except // Catch all exceptions especially these which are raised during the application close
  483. end;
  484. end;
  485. }
  486. // If terminated while waiting on the event or during the loop
  487. while not Stopped do begin
  488. try
  489. LNotifications := FNotifications.LockList;
  490. try
  491. if LNotifications.Count = 0 then begin
  492. Break;
  493. end;
  494. LNotify := {$IFDEF HAS_GENERICS_TList}LNotifications.Items[0]{$ELSE}TIdNotify(LNotifications.Items[0]){$ENDIF};
  495. LNotifications.Delete(0);
  496. finally
  497. FNotifications.UnlockList;
  498. end;
  499. {$IFNDEF USE_OBJECT_ARC}
  500. try
  501. DoThreadQueue(LNotify.InternalDoNotify);
  502. except
  503. LNotify.Free;
  504. raise;
  505. end;
  506. {$ELSE}
  507. try
  508. DoThreadQueue(LNotify.DoNotify);
  509. finally
  510. LNotify := nil;
  511. end;
  512. {$ENDIF}
  513. except // Catch all exceptions especially these which are raised during the application close
  514. end;
  515. end;
  516. end;
  517. {$ENDIF} // NotifyThreadNeeded
  518. { TIdNotifyMethod }
  519. constructor TIdNotifyMethod.Create(AMethod: TThreadMethod);
  520. begin
  521. inherited Create;
  522. FMethod := AMethod;
  523. end;
  524. procedure TIdNotifyMethod.DoNotify;
  525. begin
  526. FMethod;
  527. end;
  528. {$IFDEF NotifyThreadNeeded}
  529. initialization
  530. //CreateNotifyThread; // created on demand
  531. finalization
  532. TIdNotifyThread.FreeThread;
  533. {$ENDIF}
  534. end.