Skip to content

Instantly share code, notes, and snippets.

@barik
Forked from jsbattig/DelphiSwitchToFiber.pas
Created July 28, 2023 18:55
Show Gist options
  • Save barik/9ea49cac7d6d7bada8b2bd019e7ed28e to your computer and use it in GitHub Desktop.
Save barik/9ea49cac7d6d7bada8b2bd019e7ed28e to your computer and use it in GitHub Desktop.
Unit to properly SwitchToFiber() in Delphi without breaking exception handling
{
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