Recent

Author Topic: Is working with RTTI thread-safe?  (Read 1458 times)

yus

  • Jr. Member
  • **
  • Posts: 62
Is working with RTTI thread-safe?
« on: September 05, 2025, 11:59:39 pm »
Hello.
Wrote a simple example of working with RTTI.
Work from different threads ends with an Access Violation.
Is it possible to work from different threads?
Code: Pascal  [Select][+][-]
  1. program testRtti;
  2.  
  3. uses
  4.   Classes, SysUtils, Rtti;
  5.  
  6. type
  7.   TBase = class
  8.   private
  9.     FValue: Int64;
  10.   public
  11.     property Value: Int64 read FValue write FValue;
  12.   end;
  13.  
  14.   TMyThread = class(TThread)
  15.   public
  16.     procedure Execute; override;
  17.   end;
  18.  
  19. { TMyThread }
  20.  
  21. procedure TMyThread.Execute;
  22. var
  23.   RttiContext: TRTTIContext;
  24.   RttiType: TRttiType;
  25.   RttiProperties: TRttiPropertyArray;
  26.   Count: Integer;
  27.   Loop: Integer;
  28. begin
  29.   Loop := 0;
  30.   try
  31.     while Loop < 10000 do
  32.     begin
  33.       RttiContext := TRttiContext.Create;
  34.       try
  35.         RttiType := RttiContext.GetType(TBase);
  36.         RttiProperties := RttiType.GetProperties;
  37.         Count := Length(RttiProperties); //
  38.       finally
  39.         RttiContext.Free;
  40.       end;
  41.       Inc(Loop);
  42.     end;
  43.     Writeln('Thread done.');
  44.   except
  45.     on E: Exception do
  46.       begin
  47.         Writeln('Error: ', E.Message);
  48.         raise;
  49.       end;
  50.   end;
  51. end;
  52.  
  53. var
  54.   lThread1, lThread2: TMyThread;
  55. begin
  56.   lThread1 := TMyThread.Create(False);
  57.   lThread2 := TMyThread.Create(False);
  58.   Readln;
  59. end.
  60.  

jamie

  • Hero Member
  • *****
  • Posts: 7317
Re: Is working with RTTI thread-safe?
« Reply #1 on: September 06, 2025, 02:16:58 am »
Most likely your WriteLn calls from first glance.

Create a synchronize procedure to access Writeln.

May not help but it's a start.

Jamie
The only true wisdom is knowing you know nothing

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Is working with RTTI thread-safe?
« Reply #2 on: September 06, 2025, 11:21:56 am »
writeln is threadsafe.(As opposed to text output in GUI programs)
It seems that rtti is indeed not thread safe, because the error disappears with waitfor calls.
Code: Pascal  [Select][+][-]
  1. var
  2.   lThread1, lThread2: TMyThread;
  3. begin
  4.   lThread1 := TMyThread.Create(false);
  5.   lThread1.waitfor;
  6.   lThread2 := TMyThread.Create(false);
  7.   lThread2.Waitfor;
  8.   Readln;
  9. end.
Of course that means the threads do not run in parallel but sequentially.
But they do succeed, which likely means that RTTI is not threadsafe.

« Last Edit: September 06, 2025, 02:43:45 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

yus

  • Jr. Member
  • **
  • Posts: 62
Re: Is working with RTTI thread-safe?
« Reply #3 on: September 06, 2025, 01:45:17 pm »
It is strange that accessing class information is not thread-safe, whereas the contents of the fields remain immutable.

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Is working with RTTI thread-safe?
« Reply #4 on: September 06, 2025, 02:01:34 pm »
It is not that strange, since the information exists only in one place, whereas instances can exist in multiple places.
And RTTI does let you manipulate fields...One example of that is using custom attributes.
See the last example in the wiki here:
https://wiki.freepascal.org/Custom_Attributes#Complete_example.

It looks like the attribute code never gets called.... ;) But it is... (I wrote it, that example)

Ergo, the content of the fields are not immutable.

One remark:
Besides this new feature in trunk, custom attributes, we also have a Monitor, like Delphi has, and that can be trivially used to protect the RTTI information, in some ways better than a TMultiReadExclusiveWriteSynchronizer.(Which you can also use, of course)
To my knowledge, the Monitor feature - not related to screens - was after the custom attributes feature, hence I did not use that. I don't even know it is complete, but support is added to TObject.
« Last Edit: September 06, 2025, 04:44:33 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

yus

  • Jr. Member
  • **
  • Posts: 62
Re: Is working with RTTI thread-safe?
« Reply #5 on: September 06, 2025, 06:23:04 pm »
Thank you for your answer!


jamie

  • Hero Member
  • *****
  • Posts: 7317
Re: Is working with RTTI thread-safe?
« Reply #6 on: September 07, 2025, 01:09:53 am »
of course, there is always a workaround, not perfect but this works.
Code: Pascal  [Select][+][-]
  1. program testRtti;
  2.  
  3. uses
  4.   Classes, SysUtils, Rtti;
  5.  
  6. type
  7.   TBase = class
  8.   private
  9.     FValue: Int64;
  10.   public
  11.     property Value: Int64 read FValue write FValue;
  12.   end;
  13.  TRttiPropertyArray = Specialize TArray<TRttiProperty>;
  14.   TMyThread = class(TThread)
  15.   Public
  16.   Class Var C:TRTLCriticalSection;
  17.   public
  18.     procedure Execute; override;
  19.   end;
  20.  
  21. { TMyThread }
  22.  
  23. procedure TMyThread.Execute;
  24. var
  25.   RttiContext: TRTTIContext;
  26.   RttiType: TRttiType;
  27.   RttiProperties: TRttiPropertyArray;
  28.   Count: Integer;
  29.   Loop: Integer;
  30. begin
  31.   Loop := 0;
  32.   try
  33.     while Loop < 10000 do
  34.     begin
  35.      EnterCriticalSection(C);
  36.       RttiContext := TRttiContext.Create;
  37.       try
  38.         RttiType := RttiContext.GetType(TBase);
  39.         RttiProperties := RttiType.GetProperties;
  40.         Count := Length(RttiProperties); //
  41.       finally
  42.         RttiContext.Free;
  43.       end;
  44.      LeaveCriticalSection(C);
  45.       Inc(Loop);
  46.     end;
  47.     Writeln('Thread done.');
  48.   except
  49.     on E: Exception do
  50.       begin
  51.         Writeln('Error: ', E.Message);
  52.         raise;
  53.       end;
  54.   end;
  55. end;
  56.  
  57. var
  58.   lThread1, lThread2: TMyThread;
  59. begin
  60.   InitCriticalSection(TMyThread.C);//I don't know know any other way atm.
  61.   lThread1 := TMyThread.Create(False);
  62.   lThread2 := TMyThread.Create(False);
  63.   Readln;
  64. end.
  65.  
  66.  
  67.  

I am using 3.2.2 FPC so I had to make a Type for the Properties-Array.
Also, I know there is a way to force the class to initiate the critical variable, maybe in a unit using the initialization section or a direct inline define of a initial critical section.

The only true wisdom is knowing you know nothing

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Is working with RTTI thread-safe?
« Reply #7 on: September 07, 2025, 07:57:25 am »
@Jamie

You forgot  DoneCriticalsection
The code works, though. Also in 3.3.1.
The main code should look like this to avoid memory leaks and (OS) resource leak:
Code: Pascal  [Select][+][-]
  1. var
  2.   lThread1, lThread2: TMyThread;
  3. begin
  4.   InitCriticalSection(TMyThread.C);//I don't know know any other way atm.
  5.   lThread1 := TMyThread.Create(False);
  6.   lThread2 := TMyThread.Create(False);
  7.   // two threads running.
  8.   Readln;
  9.   // work load here
  10.  
  11.   // at the end of the program you still need to clean up.
  12.   // threads are not marked as autofree (which is good!)
  13.   lThread1.Waitfor;
  14.   lThread1.Free;
  15.   lThread2.Waitfor;
  16.   lThread2.Free;
  17.  //=======================
  18.   // to prevent a resource leak in the OS.
  19.   // you must not ignore that
  20.   // otherwise the resource is kept until
  21.   // system  restart.
  22.   // That's even if your app is closed.
  23.   DoneCriticalSection(TMyTHread.C);
  24. end.

(The latter is especially important on both Linux and Windows, where the critical sections are ultimately kernel objects)
« Last Edit: September 07, 2025, 08:30:06 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Is working with RTTI thread-safe?
« Reply #8 on: September 07, 2025, 09:56:47 am »
Also, I know there is a way to force the class to initiate the critical variable, maybe in a unit using the initialization section or a direct inline define of a initial critical section.
A simple class constructor will do since you made it a class var.
A simple class destructor would probably solve the OS resource leak. (Except when the threads are prematurely destroyed without waitfor)

Code: Pascal  [Select][+][-]
  1.   TMyThread = class(TThread)
  2.   Public
  3.   Class
  4.     Var C:TRtlCriticalSection;
  5.   public
  6.     class constructor create;
  7.     class destructor destroy;
  8.     procedure Execute; override;
  9.   end;
  10.  
  11.     class constructor TMyThread.create;
  12.     begin
  13.       initcriticalsection(C)
  14.     end;
  15.    
  16.     class destructor TMyThread.destroy;
  17.     begin
  18.       DoneCriticalSection(C);
  19.     end;

p.s.:
About leaking the criticalsection: that will NOT show up in heaptrc, only in a process monitor.
« Last Edit: September 07, 2025, 10:43:00 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12536
  • FPC developer.
Re: Is working with RTTI thread-safe?
« Reply #9 on: September 07, 2025, 01:20:58 pm »
I think this is worth a bugreport. Traceback  (a few days old FPC 3.3.1):

Code: [Select]

#0  0x77339c66 in ?? ()
#1  0x7736245c in ?? ()
#2  0x77323d93 in ?? ()
#3  0x0042efb4 in GETTYPE (this=0x1814a00, ATYPEINFO=0x455158, USEPUBLISHEDONLY=true)
    at rtl-objpas/src/inc/rtti.pp:2357
#4  0x0043c4ea in GETTYPE (this=Cannot access memory at address 0xfffffffa
) at rtl-objpas/src/inc/rtti.pp:8243
#5  0x0043c527 in GETTYPE (this=..., ACLASS=0x45e7b8) at rtl-objpas/src/inc/rtti.pp:8250
#6  0x004018d8 in Execute (this=0x180e470) at flp.pp:35
#7  0x00417bae in THREADPROC (THREADOBJPTR=0x180e470) at ../objpas/classes/classes.inc:242
#8  0x00402fff in MAIN_WRAPPER (ARG=0x45e7b8, PROC=0xfffffffa) at seh32.inc:453

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Is working with RTTI thread-safe?
« Reply #10 on: September 07, 2025, 01:30:08 pm »
Yes. I think the same. There are good grounds for that.
To the two others in the thread: Shall I do it? It would be nice if the RTTI is threadsafe.
@Marco
There is no difference here between {$M+/-} state, but even if the property is published instead of public it raises an exception.
« Last Edit: September 07, 2025, 01:33:22 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12536
  • FPC developer.
Re: Is working with RTTI thread-safe?
« Reply #11 on: September 07, 2025, 01:36:44 pm »
The exception (rtti.pp:2357is the entry of a critical section), so it is meant to be threadsafe. Definitely a report.

jamie

  • Hero Member
  • *****
  • Posts: 7317
Re: Is working with RTTI thread-safe?
« Reply #12 on: September 07, 2025, 03:16:34 pm »
I just looked at the Rtti file and I can see the Flock:TTRLCriticalSection which is in the instance of the object?

This is why I put my example in as a CLASS Var so that multiple instances will adhere to the same lock.

Jamie
The only true wisdom is knowing you know nothing

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Is working with RTTI thread-safe?
« Reply #13 on: September 07, 2025, 03:20:04 pm »
You were correct. While preparing my bug report I found that your initial hunch is correct:
Code: Pascal  [Select][+][-]
  1. program testRtti;
  2. {$mode objfpc}{$I-}
  3. {$modeswitch functionreferences}
  4. {$modeswitch anonymousfunctions}
  5. uses
  6. {$ifdef unix}cthreads,{$endif}
  7.   Classes, SysUtils, Rtti;
  8.  
  9. type
  10. {$push}{$M+}
  11.   TBase = class
  12.   private
  13.     FValue: Int64;
  14.   published
  15.     property Value: Int64 read FValue write FValue;
  16.   end;
  17. {$pop}
  18.  
  19.   TMyThread = class(TThread)
  20.   strict private
  21.     class constructor create;
  22.     class destructor destroy;
  23.     class var C:TRtlCriticalSection;
  24.   public
  25.     procedure Execute; override;
  26.   end;
  27.  
  28. { TMyThread }
  29. class constructor TMyThread.create;
  30. begin
  31.   InitCriticalSection(C)
  32. end;
  33.  
  34. class destructor TMyThread.destroy;
  35. begin
  36.   DoneCriticalSection(C);
  37. end;
  38.  
  39. procedure TMyThread.Execute;
  40. var
  41.   RttiContext: TRTTIContext;
  42.   RttiType: TRttiType;
  43.   RttiProperties: TRttiPropertyArray;
  44.   Count: Integer;
  45.   Loop: Integer;
  46. begin
  47.   Loop := 0;
  48.   try
  49.     while Loop < 10000 do
  50.     begin
  51.       EnterCriticalSection(C);
  52.       RttiContext := TRttiContext.Create;
  53.       try
  54.         RttiType := RttiContext.GetType(TBase);
  55.         RttiProperties := RttiType.GetProperties;
  56.         Count := Length(RttiProperties);
  57.         writeln(Self.ThreadID)
  58.       finally
  59.         RttiContext.Free;
  60.         Inc(Loop);
  61.         LeaveCriticalSection(C);
  62.       end;
  63.     end;
  64.     Writeln('Thread done.',count);
  65.   except
  66.     on E: Exception do
  67.       begin
  68.         Writeln('Error: ', E.Message);
  69.         raise;
  70.       end;
  71.   end;
  72. end;
  73.  
  74. var
  75.   lThread1, lThread2 : TMyThread;
  76. begin
  77.   lThread1 := TMyThread.Create(False);
  78.   lThread2 := TMyThread.Create(False);
  79.   // two threads running.
  80.   Readln;
  81.   // work load here
  82.  
  83.   // at the end of the program you still need to clean up.
  84.   // threads are not marked as autofree (which is good!)
  85.   with TThread.ExecuteInThread(procedure
  86.      begin
  87.        lThread1.Waitfor;
  88.        lThread1.Free;
  89.        lThread2.Waitfor;
  90.        lThread2.Free;
  91.      end)
  92.      do Waitfor;  { multiple objects }
  93. end.
No 3.2.2, but trunk, but it shows the rtti is not sufficiently protected. With the above code it is...
Tnx Jamie. (even if you are on 3.2.2. this helps, the class approach above will also work in 3.2.2)
 
 
 
 
« Last Edit: September 08, 2025, 06:46:59 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

LV

  • Sr. Member
  • ****
  • Posts: 359
Re: Is working with RTTI thread-safe?
« Reply #14 on: September 07, 2025, 05:23:15 pm »
Maybe cache? Works multithreaded (fpc 3.2.2)  :-[

Code: Pascal  [Select][+][-]
  1. program testRtti;
  2.  
  3. uses
  4.   Classes, SysUtils, Rtti;
  5.  
  6. type
  7.   TBase = class
  8.   private
  9.     FValue: Int64;
  10.   public
  11.     property Value: Int64 read FValue write FValue;
  12.   end;
  13.  
  14.   TRttiPropertyArray = specialize TArray<TRttiProperty>;
  15.  
  16.   TMyThread = class(TThread)
  17.   public
  18.     procedure Execute; override;
  19.   end;
  20.  
  21. var
  22.   // Global cache of properties (initialized once in the main thread)
  23.   CachedProps: TRttiPropertyArray;
  24.  
  25. { TMyThread }
  26.  
  27. procedure TMyThread.Execute;
  28. var
  29.   Count, Loop: Integer;
  30. begin
  31.   Loop := 0;
  32.   try
  33.     while Loop < 10000 do
  34.     begin
  35.       // Work only with the ready-made array of properties
  36.       Count := Length(CachedProps);
  37.       // Here you can do something with the properties
  38.  
  39.       Inc(Loop);
  40.     end;
  41.     Writeln('Thread done.');
  42.   except
  43.     on E: Exception do
  44.     begin
  45.       Writeln('Error: ', E.Message);
  46.       raise;
  47.     end;
  48.   end;
  49. end;
  50.  
  51. var
  52.   RttiContext: TRttiContext;
  53.   RttiType: TRttiType;
  54.   lThread1, lThread2: TMyThread;
  55.  
  56. begin
  57.   // --- RTTI cache initialization ---
  58.   RttiContext := TRttiContext.Create;
  59.   try
  60.     RttiType := RttiContext.GetType(TBase);
  61.     CachedProps := RttiType.GetProperties;
  62.   finally
  63.     RttiContext.Free;
  64.   end;
  65.  
  66.   // --- start threads ---
  67.   lThread1 := TMyThread.Create(False);
  68.   lThread2 := TMyThread.Create(False);
  69.  
  70.   Readln;
  71. end.
  72.  

 

TinyPortal © 2005-2018