Last active
July 28, 2023 18:55
-
-
Save jsbattig/02372990ab3831e8c5be to your computer and use it in GitHub Desktop.
Unit to properly SwitchToFiber() in Delphi without breaking exception handling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
The idea of DelphiSwitchToFiber() function is to backup on a local variable in stack the | |
state of the Exception stack right before calling SwitchToFiber() and then restoring its state | |
right atfer returns from call to SwitchToFiber(). | |
If SwitchToFiber() is used directly from within an Except or Finally block, and if there's an exception | |
raised after switching to another fiber, upon coming back the results will be unpredictable because | |
the exception stack will be completely unwinded and all raise exceptions destroyed. | |
In order to prevent this issue we must backup the Exception stack before the call to SwitchToFiber() | |
and restore it right after the call. | |
Details of 64 bits exception stack persistance in unit uWin64ExceptionStack.pas | |
It's important to notice that when getting back from SwitchToFiber() we can't even assume that | |
the call was done within the context of Thread where the call was initiated. That's why on | |
64 bits implementation of this function, we need to obtain TLS pointer to ThreadVar storage | |
area right after the call to SwitchToThread(). | |
Notice also that 32 bits implementation is dramatically simpler. On 32 bits versions of Delphi | |
when compiling under Windows, the system unit simply kept a linked list of stacked exceptions | |
so backing up our Exception stack was as simple as storing a pointer to this list on a local | |
variable and then restoring this pointer with the provided system API calls. | |
Win64 is an entirely different story, system unit hides completely all of the exception management | |
details, so we have to essently do a hack by accessing the area of the TLS where threadvars are stored | |
to persist the three key elements that participate on Exception management stack. These are: | |
RaiseFrames : TRaiseFrames; | |
ExceptionObjectCount : Integer; | |
RaiseListPtr : PRaiseFrame; | |
Notice that when calculating offset into TLS area to backup and restore RaiseListPtr we consider | |
ExceptionObjectCount size as NativeUInt (64 bits) rather than a regular integer (32 bits). This is | |
because Delphi compiler aligns to 64 bits the elements stored in the ThreadVar storage area. | |
} | |
{$IFDEF WIN64} | |
// Details to implement this function pulled from System.pas unit | |
procedure DelphiSwitchToFiber(AFiber : Pointer); | |
var | |
Win64ExceptionStack : TWin64ExceptionStack; | |
begin | |
Win64ExceptionStack.LoadFromThreadExceptionStack; | |
SwitchToFiber(AFiber); | |
Win64ExceptionStack.SaveToThreadExceptionStack; | |
end; | |
{$ELSE} | |
{$IFDEF VER130} | |
// SetRaiseList is totally broken in Delphi 5. Needs to be replaced by this function | |
function SetRaiseList(NewPtr: Pointer): Pointer; | |
asm | |
PUSH EAX | |
CALL SysInit.@GetTLS | |
MOV EDX, [EAX].$0 // Offset of threadvar RaiseListPtr in TLS memory block | |
POP ECX | |
MOV [EAX], ECX | |
MOV EAX, EDX | |
end; | |
{$ENDIF} | |
procedure DelphiSwitchToFiber(AFiber : Pointer); | |
var | |
SavedRaiseList : Pointer; | |
begin | |
SavedRaiseList := RaiseList; | |
SwitchToFiber(AFiber); | |
SetRaiseList(SavedRaiseList); | |
end; | |
{$ENDIF} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment