IdFiber.pas 17 KB

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