IdFiber.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. { $HDR$}
  2. {**********************************************************************}
  3. { Unit archived using Team Coherence }
  4. { Team Coherence is Copyright 2002 by Quality Software Components }
  5. { }
  6. { For further information / comments, visit our WEB site at }
  7. { http://www.TeamCoherence.com }
  8. {**********************************************************************}
  9. {}
  10. { $Log: 56066: IdFiber.pas
  11. {
  12. Rev 1.3 6/11/2004 8:39:48 AM DSiders
  13. Added "Do not Localize" comments.
  14. }
  15. {
  16. { Rev 1.2 2004.04.22 11:45:16 PM czhower
  17. { Bug fixes
  18. }
  19. {
  20. { Rev 1.1 2004.02.09 9:16:34 PM czhower
  21. { Updated to compile and match lib changes.
  22. }
  23. {
  24. { Rev 1.0 2004.02.03 12:38:48 AM czhower
  25. { Move
  26. }
  27. {
  28. { Rev 1.8 2003.10.24 1:00:04 PM czhower
  29. { Name change
  30. }
  31. {
  32. { Rev 1.7 2003.10.21 12:19:20 AM czhower
  33. { TIdTask support and fiber bug fixes.
  34. }
  35. {
  36. { Rev 1.6 2003.10.19 2:50:38 PM czhower
  37. { Fiber cleanup
  38. }
  39. {
  40. { Rev 1.5 2003.10.19 1:04:26 PM czhower
  41. { Updates
  42. }
  43. {
  44. { Rev 1.3 2003.10.11 5:43:12 PM czhower
  45. { Chained servers now functional.
  46. }
  47. {
  48. { Rev 1.2 2003.09.19 10:09:38 PM czhower
  49. { Next stage of fiber support in servers.
  50. }
  51. {
  52. { Rev 1.1 2003.09.19 3:01:34 PM czhower
  53. { Changed to emulate IdThreads Run behaviour
  54. }
  55. {
  56. { Rev 1.0 8/16/2003 11:09:14 AM JPMugaas
  57. { Moved from Indy Core dir as part of package reorg
  58. }
  59. {
  60. Rev 1.25 7/2/2003 2:06:40 PM BGooijen
  61. changed IdSupportsFibers to TIdFiberBase.HaveFiberSupport
  62. }
  63. {
  64. Rev 1.24 7/1/2003 8:34:14 PM BGooijen
  65. Added function IdSupportsFibers
  66. Fiber-functions are now loaded on runtime
  67. }
  68. {
  69. { Rev 1.23 2003.06.30 7:33:50 PM czhower
  70. { Fix to exception handling.
  71. }
  72. {
  73. { Rev 1.22 2003.06.30 6:52:20 PM czhower
  74. { Exposed FiberWeaver has a property.
  75. }
  76. {
  77. { Rev 1.21 2003.06.03 11:05:02 PM czhower
  78. { Modified ProcessInThisFiber to support error flag return.
  79. }
  80. {
  81. { Rev 1.20 2003.06.03 8:01:38 PM czhower
  82. { Completed fiber exception handling.
  83. }
  84. {
  85. { Rev 1.19 2003.05.27 10:27:08 AM czhower
  86. { Put back original exception handling.
  87. }
  88. {
  89. Rev 1.18 5/16/2003 3:48:24 PM BGooijen
  90. Added FreeOnTerminate
  91. }
  92. {
  93. Rev 1.17 4/17/2003 7:40:00 PM BGooijen
  94. Added AAutoStart for fibers
  95. }
  96. {
  97. { Rev 1.16 2003.04.17 7:44:56 PM czhower
  98. }
  99. {
  100. { Rev 1.15 2003.04.14 10:54:08 AM czhower
  101. { Fiber specific exceptions
  102. }
  103. {
  104. { Rev 1.14 2003.04.12 11:53:56 PM czhower
  105. { Added DoExecute
  106. }
  107. {
  108. Rev 1.13 4/11/2003 1:46:58 PM BGooijen
  109. added ProcessInThisFiber and WaitForFibers to TIdFiberWeaverBase
  110. }
  111. {
  112. { Rev 1.12 2003.04.10 11:21:42 PM czhower
  113. { Yield support
  114. }
  115. {
  116. { Rev 1.9 2003.03.27 1:29:14 AM czhower
  117. { Exception frame swapping.
  118. }
  119. {
  120. { Rev 1.7 3/22/2003 09:45:28 PM JPMugaas
  121. { Now should compile under D4.
  122. }
  123. {
  124. { Rev 1.6 2003.03.13 1:25:18 PM czhower
  125. { Moved check for parent fiber to SwitchTo
  126. }
  127. {
  128. Rev 1.5 3/13/2003 10:18:12 AM BGooijen
  129. Server side fibers, bug fixes
  130. }
  131. {
  132. { Rev 1.4 2003.02.18 1:25:04 PM czhower
  133. { Added exception if user tries to SwitchTo a completed fiber.
  134. }
  135. {
  136. { Rev 1.3 2003.01.17 2:32:12 PM czhower
  137. }
  138. {
  139. { Rev 1.2 1-1-2003 16:25:10 BGooijen
  140. { The property ParentFiber can now be written to
  141. { Added class function TIdFiberBase.GetCurrentFiberBase, which returns the
  142. { current TIdFiber
  143. }
  144. {
  145. { Rev 1.1 12-28-2002 12:01:18 BGooijen
  146. { Made a public read only property: ParentFiber
  147. }
  148. {
  149. { Rev 1.0 11/13/2002 08:44:18 AM JPMugaas
  150. }
  151. unit IdFiber;
  152. interface
  153. uses
  154. Classes,
  155. IdThreadSafe, IdBaseComponent, IdYarn, IdTask,
  156. SyncObjs, SysUtils,
  157. Windows;
  158. type
  159. // TIdFiberBase is the base for both fiber types and contains
  160. // methods that are common to both and defines the general interface. All
  161. // references to fibers should generally use this base type.
  162. TIdFiberBase = class(TObject)
  163. protected
  164. FHandle: Pointer;
  165. FPriorFiber: TIdFiberBase;
  166. FName: string;
  167. FRaiseList: Pointer;
  168. // No descendants should ever call this. Its internal only
  169. // and should only be called after destruction or after the RaiseList has
  170. // been saved
  171. procedure SwitchToMeFrom(
  172. AFromFiber: TIdFiberBase
  173. );
  174. public
  175. constructor Create; reintroduce; virtual;
  176. procedure CheckRunnable; virtual;
  177. class function HaveFiberSupport: Boolean;
  178. procedure SwitchTo(AFiber: TIdFiberBase);
  179. //
  180. property Name: string read FName write FName;
  181. property PriorFiber: TIdFiberBase read FPriorFiber;
  182. property Handle: Pointer read FHandle;
  183. end;
  184. TIdFiber = class;
  185. TIdFiberRelinquishEvent = procedure(
  186. ASender: TIdFiber;
  187. AReschedule: Boolean
  188. ) of object;
  189. // TIdConvertedFiber is used to represent thread that have been converted to
  190. // fibers
  191. TIdConvertedFiber = class(TIdFiberBase)
  192. public
  193. constructor Create; override;
  194. end;
  195. // TIdFiber is the general purpose fiber. To implement fibers descend from
  196. // TIdFiber.
  197. TIdFiber = class(TIdFiberBase)
  198. protected
  199. FFatalException: Exception;
  200. FFatalExceptionOccurred: Boolean;
  201. FFinished: TIdThreadSafeBoolean;
  202. FFreeFatalException: Boolean;
  203. FFreeFiber: Boolean;
  204. FLoop: Boolean;
  205. FOnRelinquish: TIdFiberRelinquishEvent;
  206. FParentFiber: TIdFiberBase;
  207. FStarted: TIdThreadSafeBoolean;
  208. FStopped: TIdThreadSafeBoolean;
  209. FYarn: TIdYarn;
  210. //
  211. procedure AfterRun; virtual; //not abstract - otherwise it is required
  212. procedure BeforeRun; virtual; //not abstract - otherwise it is required
  213. function GetFinished: Boolean;
  214. function GetStarted: Boolean;
  215. function GetStopped: Boolean;
  216. procedure Execute;
  217. procedure Run; virtual; abstract;
  218. procedure SwitchToParent;
  219. public
  220. procedure CheckRunnable; override;
  221. constructor Create(
  222. AParentFiber: TIdFiberBase = nil;
  223. ALoop: Boolean = False;
  224. AStackSize: Integer = 0);
  225. reintroduce;
  226. destructor Destroy;
  227. override;
  228. procedure RaiseFatalException;
  229. // Relinquish is used when the fiber is stuck and cannot usefully do
  230. // anything. It will be removed from scheduling until something reschedules
  231. // it. This is different than yield.
  232. //
  233. // Relinquish is used with FiberWeavers to tell them that the fiber is done
  234. // or blocked. Something external such as more work, or completion of a task
  235. // must reschedule the fiber with the fiber weaver.
  236. procedure Relinquish;
  237. procedure SetRelinquishHandler(AValue: TIdFiberRelinquishEvent);
  238. procedure Stop; virtual;
  239. // Gives up execution time and tells scheduler to process next available
  240. // fiber.
  241. // For manual fibers (no weaver) relinquish is called
  242. // For woven fibers, the fiber is rescheduled and relinquished.
  243. procedure Yield;
  244. //
  245. property FatalExceptionOccurred: Boolean read FFatalExceptionOccurred;
  246. property Finished: Boolean read GetFinished;
  247. property Loop: Boolean read FLoop write FLoop;
  248. property Started: Boolean read GetStarted;
  249. property Stopped: Boolean read GetStopped;
  250. property ParentFiber: TIdFiberBase read FParentFiber write FParentFiber;
  251. property Yarn: TIdYarn read FYarn write FYarn;
  252. end;
  253. TIdFiberWithTask = class(TIdFiber)
  254. protected
  255. FTask: TIdTask;
  256. public
  257. procedure AfterRun; override;
  258. procedure BeforeRun; override;
  259. // Defaults because a bit crazy to create a non looped task
  260. constructor Create(
  261. AParentFiber: TIdFiberBase = nil;
  262. ATask: TIdTask = nil;
  263. AName: string = '';
  264. AStackSize: Integer = 0
  265. ); reintroduce;
  266. destructor Destroy;
  267. override;
  268. procedure Run;
  269. override;
  270. //
  271. // Must be writeable because tasks are often created after thread or
  272. // thread is pooled
  273. property Task: TIdTask read FTask write FTask;
  274. end;
  275. implementation
  276. uses
  277. IdGlobal, IdResourceStringsCore, IdExceptionCore, IdException;
  278. var
  279. SwitchToFiber: function(lpFiber: Pointer): BOOL; stdcall = nil;
  280. CreateFiber: function(dwStackSize: DWORD; lpStartAddress: TFNFiberStartRoutine;
  281. lpParameter: Pointer): BOOL; stdcall=nil;
  282. DeleteFiber: function (lpFiber: Pointer): BOOL; stdcall = nil;
  283. ConvertThreadToFiber: function (lpParameter: Pointer): BOOL; stdcall = nil;
  284. procedure LoadFiberFunctions;
  285. var
  286. LKernel32Handle: THandle;
  287. begin
  288. if TIdFiberBase.HaveFiberSupport then begin
  289. LKernel32Handle := GetModuleHandle(kernel32);
  290. SwitchToFiber := Getprocaddress(LKernel32Handle,'SwitchToFiber'); {do not localize}
  291. CreateFiber := Getprocaddress(LKernel32Handle,'CreateFiber'); {do not localize}
  292. DeleteFiber := Getprocaddress(LKernel32Handle,'DeleteFiber'); {do not localize}
  293. ConvertThreadToFiber := Getprocaddress(LKernel32Handle,'ConvertThreadToFiber'); {do not localize}
  294. if Assigned(@SwitchToFiber) and
  295. Assigned(@CreateFiber) and
  296. Assigned(@DeleteFiber) and
  297. Assigned(@ConvertThreadToFiber) then begin
  298. Exit;
  299. end else begin
  300. SwitchToFiber := nil;
  301. CreateFiber := nil;
  302. DeleteFiber := nil;
  303. ConvertThreadToFiber := nil;
  304. end;
  305. end;
  306. EIdFibersNotSupported.Toss(RSFibersNotSupported);
  307. end;
  308. procedure FiberFunc(AFiber: TIdFiber); stdcall;
  309. var
  310. LParentFiber: TIdFiberBase;
  311. begin
  312. with AFiber do begin
  313. Execute;
  314. LParentFiber := ParentFiber;
  315. end;
  316. // Threads converted from Fibers have no parent. Also use may specify
  317. // nil if they want to control exit manually.
  318. //
  319. // We must do this last because with schedulers fibers get switched away
  320. // at this last point and not rescheduled. We do this outside the
  321. // execute as the fiber will likely be freed from somewhere else
  322. if LParentFiber <> nil then begin
  323. LParentFiber.SwitchToMeFrom(AFiber);
  324. end;
  325. end;
  326. { TIdFiber }
  327. procedure TIdFiber.AfterRun;
  328. begin
  329. end;
  330. procedure TIdFiber.BeforeRun;
  331. begin
  332. end;
  333. procedure TIdFiber.CheckRunnable;
  334. begin
  335. inherited;
  336. EIdFiberFinished.IfTrue(Finished, 'Fiber is finished.'); {do not localize}
  337. EIdFiber.IfTrue((ParentFiber = nil) and (Assigned(FOnRelinquish) = False)
  338. , 'No parent fiber or fiber weaver specified.'); {do not localize}
  339. end;
  340. constructor TIdFiber.Create(
  341. AParentFiber: TIdFiberBase;
  342. ALoop: Boolean;
  343. AStackSize: Integer
  344. );
  345. begin
  346. inherited Create;
  347. FFinished := TIdThreadSafeBoolean.Create;
  348. FStarted := TIdThreadSafeBoolean.Create;
  349. FStopped := TIdThreadSafeBoolean.Create;
  350. FFreeFiber := True;
  351. FLoop := ALoop;
  352. FParentFiber := AParentFiber;
  353. // Create Fiber
  354. FHandle := Pointer(CreateFiber(AStackSize, @FiberFunc, Self));
  355. Win32Check(LongBool(FHandle));
  356. end;
  357. destructor TIdFiber.Destroy;
  358. begin
  359. EIdException.IfTrue(Started and (Finished = False), 'Fiber not finished.'); {do not localize}
  360. // Threads converted from Fibers will have nil parents and if we call
  361. // DeleteFiber it will exit the whole thread.
  362. if FFreeFiber then begin
  363. // Must never call from self. If so ExitThread is called
  364. // Because of this FreeOnTerminate cannot be suported because a fiber
  365. // cannot delete itself, and we never know where a fiber will go for sure
  366. // when it is done. It can be done that the next fiber deletes it, but
  367. // there are catches here too. Because of this I have made it the
  368. // responsibility of the user (manual) or the scheduler (optional).
  369. Win32Check(DeleteFiber(FHandle));
  370. end;
  371. FreeAndNil(FYarn);
  372. FreeAndNil(FFinished);
  373. FreeAndNil(FStarted);
  374. FreeAndNil(FStopped);
  375. // Kudzu:
  376. // Docs say to call ReleaseException, but its empty. But it appears that since
  377. // we are taking the exception and taking it from the raise list, that instead
  378. // what we need to do is call .Free on the exception instead and that the docs
  379. // are wrong. Need to run through a memory checker to verify the behaviour.
  380. //
  381. // Normally the except block frees the exception object, but we are stealing
  382. // it out fo the list, so it does not free it.
  383. //
  384. // Ive looked into TThread and this is what it does as well, so big surprise
  385. // that the docs are wrong.
  386. //
  387. // Update: We only free it if we dont reraise the exception. If we reraise it
  388. // the fiber may be freed in a finally, and thus when the exception is handled
  389. // again an AV or other will occur because the exception has been freed.
  390. // When it is reraised, it is added back into the exception list and the
  391. // VCL will free it as part of the final except block.
  392. //
  393. if FFreeFatalException then begin
  394. FreeAndNil(FFatalException);
  395. end;
  396. //
  397. inherited;
  398. end;
  399. procedure TIdFiber.Execute;
  400. begin
  401. try
  402. try
  403. BeforeRun; try
  404. // This can be combined, but then it checks loop each run and its not
  405. // valid to toggle it after run has started and therefore adds an
  406. // unnecessary check
  407. if Loop then begin
  408. while not Stopped do begin
  409. Run;
  410. // If Weaver, this will let the weaver reschedule.
  411. // If manual it will switch back to parent to let it handle it.
  412. // If stopped just run through so it can clean up and exit
  413. if not Stopped then begin
  414. Yield;
  415. end;
  416. end;
  417. end else begin
  418. Run;
  419. end;
  420. finally AfterRun; end;
  421. except FFatalException := AcquireExceptionObject; end;
  422. if FFatalException <> nil then begin
  423. FFatalExceptionOccurred := True;
  424. FFreeFatalException := True;
  425. end;
  426. finally FFinished.Value := True; end;
  427. end;
  428. function TIdFiber.GetFinished: Boolean;
  429. begin
  430. Result := FFinished.Value;
  431. end;
  432. function TIdFiber.GetStarted: Boolean;
  433. begin
  434. Result := FStarted.Value;
  435. end;
  436. function TIdFiber.GetStopped: Boolean;
  437. begin
  438. Result := FStopped.Value;
  439. end;
  440. procedure TIdFiber.RaiseFatalException;
  441. begin
  442. if FatalExceptionOccurred then begin
  443. FFreeFatalException := False;
  444. raise FFatalException;
  445. end;
  446. end;
  447. procedure TIdFiber.Stop;
  448. begin
  449. FStopped.Value := True;
  450. end;
  451. procedure TIdFiber.SwitchToParent;
  452. begin
  453. EIdException.IfNotAssigned(FParentFiber, 'No parent fiber to switch to.'); {do not localize}
  454. SwitchTo(FParentFiber);
  455. end;
  456. procedure TIdFiber.Relinquish;
  457. begin
  458. if Assigned(FOnRelinquish) then begin
  459. FOnRelinquish(Self, False);
  460. end else begin
  461. SwitchToParent;
  462. end;
  463. end;
  464. procedure TIdFiber.Yield;
  465. begin
  466. // If manual fiber, yield is same as relinquish
  467. if Assigned(FOnRelinquish) then begin
  468. FOnRelinquish(Self, True);
  469. end else begin
  470. SwitchToParent;
  471. end;
  472. end;
  473. procedure TIdFiber.SetRelinquishHandler(AValue: TIdFiberRelinquishEvent);
  474. begin
  475. FOnRelinquish := AValue;
  476. end;
  477. { TIdConvertedFiber }
  478. constructor TIdConvertedFiber.Create;
  479. begin
  480. inherited;
  481. FHandle := Pointer(ConvertThreadToFiber(Self));
  482. end;
  483. { TIdFiberBase }
  484. constructor TIdFiberBase.Create;
  485. begin
  486. inherited;
  487. if not Assigned(@CreateFiber) then begin
  488. LoadFiberFunctions;
  489. end;
  490. end;
  491. procedure TIdFiberBase.CheckRunnable;
  492. begin
  493. end;
  494. class function TIdFiberBase.HaveFiberSupport:boolean;
  495. begin
  496. Result := Win32Platform = VER_PLATFORM_WIN32_NT;
  497. end;
  498. procedure TIdFiberBase.SwitchTo(AFiber: TIdFiberBase);
  499. begin
  500. //Kudzu
  501. // Be VERY careful in this section. This section takes care of Delphi's
  502. // exception handling mechanism.
  503. //
  504. // This section swaps out the exception frames for each fiber so that
  505. // exceptions are handled properly, preserved between switches, and across
  506. // threads.
  507. //
  508. // Notes:
  509. // -Only works on Windows, but we dont support fibers on Kylix right now
  510. // anyways
  511. // -Developer MUST use our fibers and not call Fiber API calls directly.
  512. // -May not work on C++ Builder at this time.
  513. // -May not work on older Delphi editions at this time.
  514. // -If the user calls this method and the fiber is not the current fiber, will
  515. // be problems. Maybe lock against thread ID and check that.
  516. //
  517. // This could be extended to make ThreadVars "FiberVars" by swaping out the
  518. // TLS entry. I may make this an option in the future.
  519. // This would also take care of the exception stack by itself and may be
  520. // more portable to Linux, CB and older versions of Delphi. Will check later.
  521. //
  522. //
  523. // Save raise list for current fiber
  524. FRaiseList := RaiseList;
  525. AFiber.SwitchToMeFrom(Self);
  526. end;
  527. procedure TIdFiberBase.SwitchToMeFrom(
  528. AFromFiber: TIdFiberBase
  529. );
  530. begin
  531. // See if we can run the fiber. If not it will raise an exception.
  532. CheckRunnable;
  533. FPriorFiber := AFromFiber;
  534. // Restore raise list
  535. SetRaiseList(FRaiseList);
  536. // Switch to the actual fiber
  537. SwitchToFiber(Handle);
  538. end;
  539. { TIdFiberWithTask }
  540. procedure TIdFiberWithTask.AfterRun;
  541. begin
  542. FTask.DoAfterRun;
  543. inherited;
  544. end;
  545. procedure TIdFiberWithTask.BeforeRun;
  546. begin
  547. inherited;
  548. FTask.DoBeforeRun;
  549. end;
  550. constructor TIdFiberWithTask.Create(
  551. AParentFiber: TIdFiberBase = nil;
  552. ATask: TIdTask = nil;
  553. AName: string = '';
  554. AStackSize: Integer = 0
  555. );
  556. begin
  557. inherited Create(AParentFiber, True, AStackSize);
  558. FTask := ATask;
  559. end;
  560. destructor TIdFiberWithTask.Destroy;
  561. begin
  562. FreeAndNil(FTask);
  563. inherited;
  564. end;
  565. procedure TIdFiberWithTask.Run;
  566. begin
  567. if not FTask.DoRun then begin
  568. Stop;
  569. end;
  570. end;
  571. end.