{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. } { $Log$ } { Rev 1.3 6/11/2004 8:39:48 AM DSiders Added "Do not Localize" comments. Rev 1.2 2004.04.22 11:45:16 PM czhower Bug fixes Rev 1.1 2004.02.09 9:16:34 PM czhower Updated to compile and match lib changes. Rev 1.0 2004.02.03 12:38:48 AM czhower Move Rev 1.8 2003.10.24 1:00:04 PM czhower Name change Rev 1.7 2003.10.21 12:19:20 AM czhower TIdTask support and fiber bug fixes. Rev 1.6 2003.10.19 2:50:38 PM czhower Fiber cleanup Rev 1.5 2003.10.19 1:04:26 PM czhower Updates Rev 1.3 2003.10.11 5:43:12 PM czhower Chained servers now functional. Rev 1.2 2003.09.19 10:09:38 PM czhower Next stage of fiber support in servers. Rev 1.1 2003.09.19 3:01:34 PM czhower Changed to emulate IdThreads Run behaviour Rev 1.0 8/16/2003 11:09:14 AM JPMugaas Moved from Indy Core dir as part of package reorg Rev 1.25 7/2/2003 2:06:40 PM BGooijen changed IdSupportsFibers to TIdFiberBase.HaveFiberSupport Rev 1.24 7/1/2003 8:34:14 PM BGooijen Added function IdSupportsFibers Fiber-functions are now loaded on runtime Rev 1.23 2003.06.30 7:33:50 PM czhower Fix to exception handling. Rev 1.22 2003.06.30 6:52:20 PM czhower Exposed FiberWeaver has a property. Rev 1.21 2003.06.03 11:05:02 PM czhower Modified ProcessInThisFiber to support error flag return. Rev 1.20 2003.06.03 8:01:38 PM czhower Completed fiber exception handling. Rev 1.19 2003.05.27 10:27:08 AM czhower Put back original exception handling. Rev 1.18 5/16/2003 3:48:24 PM BGooijen Added FreeOnTerminate Rev 1.17 4/17/2003 7:40:00 PM BGooijen Added AAutoStart for fibers Rev 1.16 2003.04.17 7:44:56 PM czhower Rev 1.15 2003.04.14 10:54:08 AM czhower Fiber specific exceptions Rev 1.14 2003.04.12 11:53:56 PM czhower Added DoExecute Rev 1.13 4/11/2003 1:46:58 PM BGooijen added ProcessInThisFiber and WaitForFibers to TIdFiberWeaverBase Rev 1.12 2003.04.10 11:21:42 PM czhower Yield support Rev 1.9 2003.03.27 1:29:14 AM czhower Exception frame swapping. Rev 1.7 3/22/2003 09:45:28 PM JPMugaas Now should compile under D4. Rev 1.6 2003.03.13 1:25:18 PM czhower Moved check for parent fiber to SwitchTo Rev 1.5 3/13/2003 10:18:12 AM BGooijen Server side fibers, bug fixes Rev 1.4 2003.02.18 1:25:04 PM czhower Added exception if user tries to SwitchTo a completed fiber. Rev 1.3 2003.01.17 2:32:12 PM czhower Rev 1.2 1-1-2003 16:25:10 BGooijen The property ParentFiber can now be written to Added class function TIdFiberBase.GetCurrentFiberBase, which returns the current TIdFiber Rev 1.1 12-28-2002 12:01:18 BGooijen Made a public read only property: ParentFiber Rev 1.0 11/13/2002 08:44:18 AM JPMugaas } unit IdFiber; interface uses Classes, IdThreadSafe, IdBaseComponent, IdYarn, IdTask, SyncObjs, SysUtils, Windows; type // TIdFiberBase is the base for both fiber types and contains // methods that are common to both and defines the general interface. All // references to fibers should generally use this base type. TIdFiberBase = class(TObject) protected FHandle: Pointer; FPriorFiber: TIdFiberBase; FName: string; FRaiseList: Pointer; // No descendants should ever call this. Its internal only // and should only be called after destruction or after the RaiseList has // been saved procedure SwitchToMeFrom( AFromFiber: TIdFiberBase ); public constructor Create; reintroduce; virtual; procedure CheckRunnable; virtual; class function HaveFiberSupport: Boolean; procedure SwitchTo(AFiber: TIdFiberBase); // property Name: string read FName write FName; property PriorFiber: TIdFiberBase read FPriorFiber; property Handle: Pointer read FHandle; end; TIdFiber = class; TIdFiberRelinquishEvent = procedure( ASender: TIdFiber; AReschedule: Boolean ) of object; // TIdConvertedFiber is used to represent thread that have been converted to // fibers TIdConvertedFiber = class(TIdFiberBase) public constructor Create; override; end; // TIdFiber is the general purpose fiber. To implement fibers descend from // TIdFiber. TIdFiber = class(TIdFiberBase) protected FFatalException: Exception; FFatalExceptionOccurred: Boolean; FFinished: TIdThreadSafeBoolean; FFreeFatalException: Boolean; FFreeFiber: Boolean; FLoop: Boolean; FOnRelinquish: TIdFiberRelinquishEvent; FParentFiber: TIdFiberBase; FStarted: TIdThreadSafeBoolean; FStopped: TIdThreadSafeBoolean; FYarn: TIdYarn; // procedure AfterRun; virtual; //not abstract - otherwise it is required procedure BeforeRun; virtual; //not abstract - otherwise it is required function GetFinished: Boolean; function GetStarted: Boolean; function GetStopped: Boolean; procedure Execute; procedure Run; virtual; abstract; procedure SwitchToParent; public procedure CheckRunnable; override; constructor Create( AParentFiber: TIdFiberBase = nil; ALoop: Boolean = False; AStackSize: Integer = 0); reintroduce; destructor Destroy; override; procedure RaiseFatalException; // Relinquish is used when the fiber is stuck and cannot usefully do // anything. It will be removed from scheduling until something reschedules // it. This is different than yield. // // Relinquish is used with FiberWeavers to tell them that the fiber is done // or blocked. Something external such as more work, or completion of a task // must reschedule the fiber with the fiber weaver. procedure Relinquish; procedure SetRelinquishHandler(AValue: TIdFiberRelinquishEvent); procedure Stop; virtual; // Gives up execution time and tells scheduler to process next available // fiber. // For manual fibers (no weaver) relinquish is called // For woven fibers, the fiber is rescheduled and relinquished. procedure Yield; // property FatalExceptionOccurred: Boolean read FFatalExceptionOccurred; property Finished: Boolean read GetFinished; property Loop: Boolean read FLoop write FLoop; property Started: Boolean read GetStarted; property Stopped: Boolean read GetStopped; property ParentFiber: TIdFiberBase read FParentFiber write FParentFiber; property Yarn: TIdYarn read FYarn write FYarn; end; TIdFiberWithTask = class(TIdFiber) protected FTask: TIdTask; public procedure AfterRun; override; procedure BeforeRun; override; // Defaults because a bit crazy to create a non looped task constructor Create( AParentFiber: TIdFiberBase = nil; ATask: TIdTask = nil; AName: string = ''; AStackSize: Integer = 0 ); reintroduce; destructor Destroy; override; procedure Run; override; // // Must be writeable because tasks are often created after thread or // thread is pooled property Task: TIdTask read FTask write FTask; end; implementation uses IdGlobal, IdResourceStringsCore, IdExceptionCore, IdException; var SwitchToFiber: function(lpFiber: Pointer): BOOL; stdcall = nil; CreateFiber: function(dwStackSize: DWORD; lpStartAddress: TFNFiberStartRoutine; lpParameter: Pointer): BOOL; stdcall=nil; DeleteFiber: function (lpFiber: Pointer): BOOL; stdcall = nil; ConvertThreadToFiber: function (lpParameter: Pointer): BOOL; stdcall = nil; procedure LoadFiberFunctions; var LKernel32Handle: THandle; begin if TIdFiberBase.HaveFiberSupport then begin LKernel32Handle := GetModuleHandle(kernel32); SwitchToFiber := Getprocaddress(LKernel32Handle,'SwitchToFiber'); {do not localize} CreateFiber := Getprocaddress(LKernel32Handle,'CreateFiber'); {do not localize} DeleteFiber := Getprocaddress(LKernel32Handle,'DeleteFiber'); {do not localize} ConvertThreadToFiber := Getprocaddress(LKernel32Handle,'ConvertThreadToFiber'); {do not localize} if Assigned(@SwitchToFiber) and Assigned(@CreateFiber) and Assigned(@DeleteFiber) and Assigned(@ConvertThreadToFiber) then begin Exit; end else begin SwitchToFiber := nil; CreateFiber := nil; DeleteFiber := nil; ConvertThreadToFiber := nil; end; end; raise EIdFibersNotSupported.Create(RSFibersNotSupported); end; procedure FiberFunc(AFiber: TIdFiber); stdcall; var LParentFiber: TIdFiberBase; begin with AFiber do begin Execute; LParentFiber := ParentFiber; end; // Threads converted from Fibers have no parent. Also use may specify // nil if they want to control exit manually. // // We must do this last because with schedulers fibers get switched away // at this last point and not rescheduled. We do this outside the // execute as the fiber will likely be freed from somewhere else if LParentFiber <> nil then begin LParentFiber.SwitchToMeFrom(AFiber); end; end; { TIdFiber } procedure TIdFiber.AfterRun; begin end; procedure TIdFiber.BeforeRun; begin end; procedure TIdFiber.CheckRunnable; begin inherited; EIdFiberFinished.IfTrue(Finished, 'Fiber is finished.'); {do not localize} EIdFiber.IfTrue((ParentFiber = nil) and (Assigned(FOnRelinquish) = False) , 'No parent fiber or fiber weaver specified.'); {do not localize} end; constructor TIdFiber.Create( AParentFiber: TIdFiberBase; ALoop: Boolean; AStackSize: Integer ); begin inherited Create; FFinished := TIdThreadSafeBoolean.Create; FStarted := TIdThreadSafeBoolean.Create; FStopped := TIdThreadSafeBoolean.Create; FFreeFiber := True; FLoop := ALoop; FParentFiber := AParentFiber; // Create Fiber FHandle := Pointer(CreateFiber(AStackSize, @FiberFunc, Self)); Win32Check(LongBool(FHandle)); end; destructor TIdFiber.Destroy; begin EIdException.IfTrue(Started and (Finished = False), 'Fiber not finished.'); {do not localize} // Threads converted from Fibers will have nil parents and if we call // DeleteFiber it will exit the whole thread. if FFreeFiber then begin // Must never call from self. If so ExitThread is called // Because of this FreeOnTerminate cannot be suported because a fiber // cannot delete itself, and we never know where a fiber will go for sure // when it is done. It can be done that the next fiber deletes it, but // there are catches here too. Because of this I have made it the // responsibility of the user (manual) or the scheduler (optional). Win32Check(DeleteFiber(FHandle)); end; FreeAndNil(FYarn); FreeAndNil(FFinished); FreeAndNil(FStarted); FreeAndNil(FStopped); // Kudzu: // Docs say to call ReleaseException, but its empty. But it appears that since // we are taking the exception and taking it from the raise list, that instead // what we need to do is call .Free on the exception instead and that the docs // are wrong. Need to run through a memory checker to verify the behaviour. // // Normally the except block frees the exception object, but we are stealing // it out fo the list, so it does not free it. // // Ive looked into TThread and this is what it does as well, so big surprise // that the docs are wrong. // // Update: We only free it if we dont reraise the exception. If we reraise it // the fiber may be freed in a finally, and thus when the exception is handled // again an AV or other will occur because the exception has been freed. // When it is reraised, it is added back into the exception list and the // VCL will free it as part of the final except block. // if FFreeFatalException then begin FreeAndNil(FFatalException); end; // inherited; end; procedure TIdFiber.Execute; begin try try BeforeRun; try // This can be combined, but then it checks loop each run and its not // valid to toggle it after run has started and therefore adds an // unnecessary check if Loop then begin while not Stopped do begin Run; // If Weaver, this will let the weaver reschedule. // If manual it will switch back to parent to let it handle it. // If stopped just run through so it can clean up and exit if not Stopped then begin Yield; end; end; end else begin Run; end; finally AfterRun; end; except FFatalException := AcquireExceptionObject; end; if FFatalException <> nil then begin FFatalExceptionOccurred := True; FFreeFatalException := True; end; finally FFinished.Value := True; end; end; function TIdFiber.GetFinished: Boolean; begin Result := FFinished.Value; end; function TIdFiber.GetStarted: Boolean; begin Result := FStarted.Value; end; function TIdFiber.GetStopped: Boolean; begin Result := FStopped.Value; end; procedure TIdFiber.RaiseFatalException; begin if FatalExceptionOccurred then begin FFreeFatalException := False; raise FFatalException; end; end; procedure TIdFiber.Stop; begin FStopped.Value := True; end; procedure TIdFiber.SwitchToParent; begin EIdException.IfNotAssigned(FParentFiber, 'No parent fiber to switch to.'); {do not localize} SwitchTo(FParentFiber); end; procedure TIdFiber.Relinquish; begin if Assigned(FOnRelinquish) then begin FOnRelinquish(Self, False); end else begin SwitchToParent; end; end; procedure TIdFiber.Yield; begin // If manual fiber, yield is same as relinquish if Assigned(FOnRelinquish) then begin FOnRelinquish(Self, True); end else begin SwitchToParent; end; end; procedure TIdFiber.SetRelinquishHandler(AValue: TIdFiberRelinquishEvent); begin FOnRelinquish := AValue; end; { TIdConvertedFiber } constructor TIdConvertedFiber.Create; begin inherited; FHandle := Pointer(ConvertThreadToFiber(Self)); end; { TIdFiberBase } constructor TIdFiberBase.Create; begin inherited; if not Assigned(@CreateFiber) then begin LoadFiberFunctions; end; end; procedure TIdFiberBase.CheckRunnable; begin end; class function TIdFiberBase.HaveFiberSupport:boolean; begin Result := IndyWindowsPlatform = VER_PLATFORM_WIN32_NT; end; procedure TIdFiberBase.SwitchTo(AFiber: TIdFiberBase); begin //Kudzu // Be VERY careful in this section. This section takes care of Delphi's // exception handling mechanism. // // This section swaps out the exception frames for each fiber so that // exceptions are handled properly, preserved between switches, and across // threads. // // Notes: // -Only works on Windows, but we dont support fibers on Kylix right now // anyways // -Developer MUST use our fibers and not call Fiber API calls directly. // -May not work on C++ Builder at this time. // -May not work on older Delphi editions at this time. // -If the user calls this method and the fiber is not the current fiber, will // be problems. Maybe lock against thread ID and check that. // // This could be extended to make ThreadVars "FiberVars" by swaping out the // TLS entry. I may make this an option in the future. // This would also take care of the exception stack by itself and may be // more portable to Linux, CB and older versions of Delphi. Will check later. // // // Save raise list for current fiber FRaiseList := RaiseList; AFiber.SwitchToMeFrom(Self); end; procedure TIdFiberBase.SwitchToMeFrom( AFromFiber: TIdFiberBase ); begin // See if we can run the fiber. If not it will raise an exception. CheckRunnable; FPriorFiber := AFromFiber; // Restore raise list SetRaiseList(FRaiseList); // Switch to the actual fiber SwitchToFiber(Handle); end; { TIdFiberWithTask } procedure TIdFiberWithTask.AfterRun; begin FTask.DoAfterRun; inherited; end; procedure TIdFiberWithTask.BeforeRun; begin inherited; FTask.DoBeforeRun; end; constructor TIdFiberWithTask.Create( AParentFiber: TIdFiberBase = nil; ATask: TIdTask = nil; AName: string = ''; AStackSize: Integer = 0 ); begin inherited Create(AParentFiber, True, AStackSize); FTask := ATask; end; destructor TIdFiberWithTask.Destroy; begin FreeAndNil(FTask); inherited; end; procedure TIdFiberWithTask.Run; begin if not FTask.DoRun then begin Stop; end; end; end.