Jump to content

Dalija Prasnikar

Members
  • Content Count

    1058
  • Joined

  • Last visited

  • Days Won

    91

Posts posted by Dalija Prasnikar


  1. 25 minutes ago, A.M. Hoornweg said:

    My question is about threads literally running simultaneously on different CPU cores, does each core always have an independent FPU + FPCR so one thread cannot jeopardize another ?

    Each core has its own FPU. It is possible that some older multicore processors shared single FPU, but even in such case each thread would have access to its own "FPU data copy". If those values would be shared among threads, then different threads could trample upon results of other thread calculations.


  2. 54 minutes ago, A.M. Hoornweg said:

    Since threads run concurrently on multi-core systems, isn't the fp control state global to all threads? Or does every cpu core have its own fpu core?

    FPCR is part of the FPU and its state is preserved during context switch. So each thread works with its own state that is independent of others. If you only handle floating point state directly through FPCR it will be thread-safe. Problem with Delphi is that it throws global variable into the equation and then handles FPCR with the help of that global in thread-unsafe manner, which can then leak "wrong" state into different thread.


  3. 42 minutes ago, msohn said:

    How can you make this work in a thread-safe manner?

    FPCR can be set in a thread safe manner. it is just that Delphi RTL doesn't do that on Windows platform. David has written about it see 

    and https://stackoverflow.com/a/27125657

     

    I have also written about it in my book Delphi Thread Safety Patterns.

     

    Working with masked FPU exceptions is the norm across various languages. Also even before Delphi 12, all other Delphi platforms except Windows VCL has masked FPU exceptions. So if you had written FMX application it would have been working the same way it is working now across the board. 

    So the best option is moving to the masked FPU exceptions as this will create the least amount of trouble for the future. Of course, if you are dealing with Delphi built DLLs which don't honor that, you will have to adapt. Another option is to unmask exceptions and proceed as usual, but doing that in multithreaded application is not advisable unless you also patch the RTL as it is not thread-safe. Even having masked FPU exceptions is not thread-safe, but it is much harder to trigger the issue and much easier to avoid RTL API that uses unsafe parts than with unmasked exceptions.

    • Like 1
    • Thanks 1

  4. 7 minutes ago, FreeDelphiPascal said:

    So, I would correct myself and say that the error is between the hardware and the language, in the LLVM. Do you agree with this "technicality"? 

    Well, the problem is technically in the LLVM, so blaming the LLVM is fine 😉

     

    But again, also it is not technically a bug in LLVM, but merely lack of certain feature, which is not so easy to add at this time.


  5. 55 minutes ago, FreeDelphiPascal said:

    I don't think you read Dalija's article. It clearly states that it is hardware related, not language related.

    It is language related, more precisely compiler related (LLVM backend). The hardware part is related to exceptions that happen in hardware which cannot be caught by LLVM backend. Old Delphi compiler (Windows one which has custom frontend and backend) can catch those because it was designed that way. LLVM origins are in C++ which on language level does expect catching such exceptions, so there was a "design mistake" in there that is now very hard to change, to accommodate slightly different languages.

     

    Most common hardware exceptions are access violations (accessing nil or invalid memory) and floating point exceptions. See: https://dalijap.blogspot.com/2023/12/catch-me-if-you-can-part-ii.html

     

    Plenty of languages are designed around notion that if they encounter certain types of issues they should just crash the whole application instead of trying to recover. Now, such behavior reduces the chances of certain vulnerabilities, but on the other hand makes very hard to deal with certain scenarios, especially gracefully detecting and handling bad input data, and they put a lot of more pressure to the developer to imagine all kinds of potential failures and prevent them explicitly in code. 

     

    So in Delphi (Windows platform) accessing nil pointer will result with exception which you can catch and handle, so you can write code that will not check for nil instance and just try to access it and if it fails you can have exception handler that will handle such situation without anything bad happening. On the other hand, if you have such code in Swift (which also uses LLVM) your application will crash and burn and you cannot recover from such error at all. You need to pre-emptively check whether instance is nil when you expect it might be to avoid accessing nil instance. 

     

    In Delphi compilers with LLVM backend, you can still catch nil instance access and recover, you just cannot catch it in the immediate code block, but it must be in separate procedure or method and only outside exception handler can catch and gracefully handle such exception. 

     

     


  6. I would use TypeInfo instead of RTTI as it will be faster. This requires System.TypInfo 

     

    procedure TMyThingy.SetEnumPropertyValue(const AValue: string);
    begin
      PByte(@FEnumProp)^ := GetEnumValue(TypeInfo(TMyEnum), AValue);
    end;

     

    You should pay attention on enum type size and use appropriate sized pointer when casting PByte(@EnumProp)^. This will also raise out of range exception if passed string does not match to any value in TMyEnum. You can catch that and set it to some default value if needed.


  7. 23 minutes ago, David Schwartz said:

    That's what I'd like to hear more about. 

    If you really need to have multiple references to an object, then use interfaces and ARC to manage lifetime. If not, then you will have to invent similar mechanism for managing the lifetime.

    • Like 3

  8. 37 minutes ago, Anders Melander said:

    It's still a good read and even though some techniques are obsolete, knowing them can help solving other similar problems simply because they can make you think about problems differently.

    This is always the greatest benefit.

     

    42 minutes ago, Anders Melander said:

    Um... Always? There used to be a time, not that long ago, when integer math was far superior to floating point math (see: Hackers Delight). Not so anymore.

    Integer math is still faster. Just not that much. And division is still more expensive than multiplication.

     

    Anyway, when it comes to any optimizations, it always goes hand in hand with measuring. and making sure that you really need optimization in the first place and are not making a bad trade-off by complicating the code.


  9. 4 hours ago, Brian Evans said:

    Looks like it. There was another post by the same account in a different topic that also looked auto generated but had an extra sentence not really on topic with a link to a commercial software/service website. 

    This is classic AI spammer tactic. Post AI on random posts to make it look like user is actively participating, and then separately they also post blatant spam.

    • Like 1

  10. 2 hours ago, Hans♫ said:

    I used the option "Migrate settings to a newer product version". Do you suggest to use the other option "Export settings to a migration file"?

    I never used that setting so there might be issue with that one. But, when you run the wizard at one point you will also have Three buttons under the settings tree: Update Migration, Version Migration, and Computer Migration. You should choose Version Migration when migrating form one version to another. It is probably better to export settings to file and then import after the installation. But even if you do that you need to make sure that you choose Version Migration. 

     

    The fact that IDE was trying to load packages from older version is a signal that you have imported wrong settings.


  11. Just now, EugeneK said:

    I think anonymous methods will still work without ability to explicitly use them as interfaces. I could not find it being used in this way anywhere in Delphi RTL.

    You don't understand. They are implemented as interface based class. Without that implementation they wouldn't work at all. It is just that compiler hides that fact from us and automatically creates everything needed.


  12. Just now, Attila Kovacs said:

    I don't believe their concern is memory leaks when they recommend using memory-safe languages, so I'm unsure what the focus of this conversation is.

    Memory leaks as such are indeed not relevant for this discussion, but accessing invalid memory is the focus of this conversation and manual memory management opens up such possibility. 


  13. 3 hours ago, EugeneK said:

    All classes that take TComponent owner already need to be manually freed when passed nil as a parameter. There could be bugs because of double free/memory leak because of this too.

    I think that you don't fully grasp the difference between forgetting to free an object instance which will cause a memory leak and accidentally triggering reference counting mechanism which will trigger premature destruction of an object which is still in use.

     

    3 hours ago, EugeneK said:

    Exactly! That's the whole point, it also has other advantages, like ability to provide different implementation for classes.

    And TComponent seems to already implement interface reference counting, so maybe all that is needed to add interface implementing public methods.

    For providing different implementation for classes by using interfaces, it is not enough to add interface to the class, you need to change all signatures from class to interface. This is not backward compatible. 

     

    Also, TComponent does not implement reference counting, it is disabled for almost all use cases, and it works under very specific scenarios. Not to mention that TComponent is one of the main reasons why full ARC compiler was removed. It is completely incompatible with ARC. TXmlDocument is an outlier.

    3 hours ago, EugeneK said:

    You never really need automatically manage memory, you can always free it manually it just a big source of bugs. I think goal of compiler developers should be to give people options to avoid them and write simpler code. Just now I found 10 year old bug where TStrings object was assigned to already created object causing memory leak, this kind of bug is almost impossible to find and it would not be a problem if it was IStrings instead.

    I beg to differ. Original code had a memory leak because of wrong assignment, but such leak is detectable and if you follow the leak you can find issue in the code. If you had IStrings, you would not have a leak, but you would still have a bug in your code because there is still a wrong assignment somewhere. Anything can be source of bugs, just the bugs and issues you have will be different. 

     

     

    3 hours ago, EugeneK said:

    For smart pointers I don't really see a way to use them as elegantly as interfaces, at least with current Delphi language.

     

    https://github.com/dalijap/code-delphi-mm/blob/master/Part5/SmartPointer/Smart.dpr

     

    procedure UseStrings(Strings: TStrings);
    begin
      Writeln(Trim(Strings.Text));
    end;
    
    procedure SmartPtr;
    var
      sl: ISmartPointer<TStringList>;
    begin
      sl := TSmartPointer<TStringList>.Create();
      sl.Add('I am inside automanaged StringList');
    
      UseStrings(sl);
    end; 

    I think the above code is simple enough. The name SmartPointer is rather long and it can be easily shortened.

     

    If you would want to have the same functionality as the above code with interfaces while preserving backward compatibility then you would have to write something like:

     

    procedure UseStrings(Strings: TStrings);
    begin
      Writeln(Trim(Strings.Text));
    end;
    
    procedure SmartPtr;
    var
      sl: IStrings;
    begin
      sl := TStringList.Create(auto);
      sl.Add('I am inside automanaged StringList');
    
      UseStrings(TStrings(sl));
    end; 

    Which also shows why you cannot insert different implementation as you need to typecast back to TStrings if you want to preserve backward compatibility.

     

    4 hours ago, EugeneK said:

    I don't agree with this, but Embarcadero devs seem to agree, since they closed my ticket about it.

     

    I assume this is the ticket https://quality.embarcadero.com/browse/RSP-36609 I fully agree with the closure. Adding interfaces to TStrings for the sole purpose of having automatic memory management would only add burden to that class hierarchy while achieving nothing that cannot be done by smart pointers. What Bruneau Babet suggests is the right course of action, adding memory management from the within opens possibility for wrong usage and accidental errors. Using smart pointers (the examples from C++ and Rust are in line with above smart pointer example) is the way to go.

     

    Now, there is no universal smart pointer support built in the RTL, adding that would be more meaningful and more versatile solution than adding interfaces to handpicked classes.

     


  14. 11 hours ago, EugeneK said:

    I was thinking something like TXmlDocument/IXmlDocument, if you use TXmlDocument you continue using regular Create/Free or component ownership and if you use IXmlDocument it is reference counted.

    First, that is not proper description how TXmlDocument memory management works - I understand it is possible that you just wanted to convey what you meant by adding interfaces to RTL classes (and that served the purpose), but I need to add precise explanation of how it works because that class is one of the most incorrectly used ones when it comes to memory management.

     

    The deciding factor in how the TXmlDocument memory is managed is not in how it is stored (reference type, although it does have a play in how the code will behave), but whether you have constructed the instance with nil owner or not. This will set FOwnerIsComponent field and depending on that field the instance will be automatically managed with reference counting or not. If the owner is nil, you must store created instance in interface reference and if the owner is not nil, then you may store it in either interface reference or object reference, but the memory will not be managed by interface reference if you do that and you must make sure that interface reference does not outlive owner.

     

    Following console application demonstrates this. XmlDocInterafce and XmlDocComponent show correct usage, the other two are incorrect ones that can cause trouble depending on the code (you can get lucky and avoid issues, but that does not mean that such usage is correct and will not blow when you least expect it)

     

    program XMLDocMM;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils,
      System.Classes,
      Winapi.ActiveX,
      Xml.Win.msxmldom,
      Xml.XMLDoc,
      Xml.XMLIntf;
    
    var
      Owner: TComponent;
    
    procedure UseDoc(Doc: IXMLDocument);
    begin
      Doc.LoadFromXML('<abc>aaa</abc>');
    end;
    
    procedure XmlDocInterface;
    var
      x: IXMLDocument;
    begin
      x := TXMLDocument.Create(nil);
      UseDoc(x);
      Writeln(x.IsEmptyDoc);
    end;
    
    procedure XmlDocInterfaceLeak1;
    var
      // this will create leak as reference counting is not properly triggered
      x: TXMLDocument;
    begin
      x := TXMLDocument.Create(nil);
      x.LoadFromXML('<abc>aaa</abc>');
      Writeln(x.IsEmptyDoc);
    end;
    
    procedure XmlDocInterfaceLeak2;
    var
      x: IXMLDocument;
    begin
      x := TXMLDocument.Create(Owner);
      UseDoc(x);
      Writeln(x.IsEmptyDoc);
      // this will create memory leak as memory is not managed by interface reference, but owner
      Owner.RemoveComponent(TComponent(x));
    end;
    
    procedure XmlDocComponent;
    var
      x: TXMLDocument;
    begin
      x := TXMLDocument.Create(Owner);
      try
        UseDoc(x);
        Writeln(x.IsEmptyDoc);
      finally
        x.Free;
      end;
    end;
    
    procedure XmlDocComponentSelfDestruct;
    var
      x: TXMLDocument;
    begin
      x := TXMLDocument.Create(nil);
      try
        UseDoc(x);
        // x will be destroyed here
        Writeln(x.IsEmptyDoc); // accessing destroyed instance
      finally
        // Invalid Pointer Operation
        x.Free;
      end;
    end;
    
    begin
      ReportMemoryLeaksOnShutdown := True;
      CoInitialize(nil);
      try
        Owner := TComponent.Create(nil);
        try
          XmlDocInterface;
          XmlDocInterfaceLeak1;
          XmlDocInterfaceLeak2;
          XmlDocComponent;
          XmlDocComponentSelfDestruct;
        finally
          Owner.Free;
        end;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      CoUninitialize;
    end.

     

    So if you want to add interfaces to some RTL class and change its memory management behavior, you cannot do that just by adding interface to its declaration and required reference counting methods and let the reference type decide the memory management. I mean in simple scenario you could, but such code would be very fragile. Following code works correctly when it comes to memory management, but if the reference counting is in any way triggered by second example (Test2) it would auto destroy the object instance.

     

    procedure Test1;
    var
      i: IInterface;
    begin
      i := TInterfacedObject.Create;
    end;
    
    procedure Test2;
    var
      o: TInterfacedObject;
    begin
      o := TInterfacedObject.Create;
      o.Free;
    end;

     

    So, if you want to have such class that can have its memory managed in dual way, you need to handle that through some additional parameter during instance construction (or through some special value of existing parameter, like it was done with TXmlDocument) that would prevent accidental reference counting from destroying the object prematurely. And then you would also have to pay attention on how you store such instance and that reference type matches passed parameter and associated memory management.

     

    On top of that, you would need to declare interface for the instance public API because otherwise you would not be able to use any of the object's intended functionality. With TXmlDocument the whole functionality is already interface based, so exposing that through outer wrapper is trivial.

     

    In real life scenarios, if you need to automatically manage some object instance, there are better ways to do that (through some kind of smart pointer implementations or similar) than by forcing interfaces on those base classes. I had such base class in my library as part of some base class hierarchy but I don't use that ability except in few rare cases. I though I would use it more, but adding it on such low level was a mistake as I almost never used it across span of 20 years I had it. Classes that had interfaces defined I used through interface references anyway, there was never a need to switch to manual memory management for those, and occasional usage of automatic memory management for other classes that didn't have declared interfaces was not enough to justify the implementation in the base class. Main reason was that using such class with dual memory management without having its specific interface was more complicated than using smart pointers or just writing few extra lines of code and doing it manually.

     

    I am not saying that sometimes such dual memory management is not useful, but that such instances are rare and adding such thing on majority of RTL classes would be waste of time and effort.

     


  15. 6 minutes ago, EugeneK said:

    I wish they added interfaces to existing RTL classes, this would be nice backwards compatible way to improve memory management.

    How would that be backward compatible? Classes that use reference counting have completely different memory management and require slightly different handling than ones that are not reference counted. Every line of code that would use such changed classes would have to be inspected and changed.

     

    This would be like having ARC compiler all over again, only without half of required changes being automatically implemented in the compiler. You cannot force ARC on existing code that is not written with ARC in mind. That does not work. It may work in some simple scenarios, but then when you get to slightly more complex code is all falls apart.


  16. 9 minutes ago, David Heffernan said:

    I guess ARC per-se wasn't the big problem, it was more the attempt to support ARC compilers and non-ARC compilers with the same codebases??

    Yes, that was the core problem. In other words, the ARC compiler alone on the existing codebases and Delphi component system which required DisposeOf would not be any more memory safe than code using non ARC compiler. Some loopholes that would be plugged by having automatic instance initialization and lifetime management, but DisposeOf opens other different loopholes.

     

    So, if we would go that route, then all Delphi code (including RTL, VCL and FMX) would have to be written from scratch, which makes ARC compiler not really viable solution after all.


  17. 46 minutes ago, Sherlock said:

    Now there's something the folks over at Embarcadero can aspire to. Imagine built in memory safety in Delphi.

    I hate to be the one saying this... but ARC compiler was a step in that direction. You cannot have memory safety without automatic memory management.

    • Like 2

  18. It was my comment about the server creation which seemed unnecessary. This was a wrong assumption, and the server is needed for the authorization process.

     

    However, there are still some things in your code that could be improved. 

     

    I will start with simple things. 

     

    You are have plenty of unnecessary nil checks. If you want to return a nil or some field instance if it is assigned, then you can just return the value in the field as if the field is not assigned its default value will be nil. (Note, when it comes to local variables, they must be explicitly initialized to nil, as their default value will be random, unlike for object fields)

     

    So following code can be replaced

    function TServer.ServerLog: TStringList;
    begin
      Result := Nil;
      if Assigned(FServerLog) then
      begin
        Result := FServerLog;
      end;
    end;

    with

     

    function TServer.ServerLog: TStringList;
    begin
      Result := FServerLog;
    end;

    Next, you are constructing FServerLog in the constructor, so the whole nil check is pointless anyway because during the server instance lifetime it will be valid, assigned instance. In cases where it would not be a valid instance. If you would have a case where some function could return nil instance, you would have to always check for nil, before using such object.

     

    Free method can be safely called on nil instances, so you don't have to check if instance is assigned before calling Free. Following would be fine:

     

    destructor TServer.Destroy;
    begin
      FServer.Free;
      FServerLog.Free;
      inherited;
    end;

    Because server uses server log, I would also reverse order of construction and destruction and create server log first, and then the server, also server would be destroyed first, and then server log.

     

    You have similar code in other places and it can be simplified, too.

     

    Now, back to the actual problem you have been asking. 

     

    First of all, TShopeeAuthorizator is inherently connected with TCatcherServer. It would make sense to create server in TShopeeAuthorizator constructor and destroy it in its destructor. This would also prevent memory leak you have if the FieldsReadyHandler is never called.

     

    This would also remove rather ugly Sender.Free code. It is not that it cannot be used in that way, but commonly such code can be replaced with better constructs. 

     

    Next it would be better to move parts of the TShopeeContext.Authorize inside the TShopeeAuthorizator. This would also remove the need to have global AuhorizationDone event. Make it a field in TShopeeAuthorizator and create it in its constructor, and destroy in the destructor.

     

    The whole waiting for even loop will then be moved inside TShopeeAuthorizator.AuthorizationRequest which would be converted to function returning Boolean to detect successful authorization.

     

    procedure TShopeeContext.Authorize;
    var
      Authorizator: TShopeeAuthorizator;
    begin
      // Request Authorization;
      Authorizator := TShopeeAuthorizator.Create(FDataHolder, FHost, FAPI_Key, FPartnerID, 8342);
      try
        if Authorizator.AuthorizationRequest(45000) then
        begin
          ShowMessage('Autenticado com Sucesso');
          ShowMessage(FDataHolder.Code + ' ' + FDataHolder.EntityID);
        end
        else
          raise Exception.Create('Authorization Timed Out');
      finally
        Authorizator.Free;
      end;
    end;

    Additionally, you can also make TShopeeContext.Auhorize a Boolean function that would merely return the value returned by AuthorizationRequest. This would also move error handling to the higher level code which can then have better control of how to handle success and failure.

     

    You can also add event handlers FOnSuccess and FOnFailure to TShopeeContext so the outside code can hook to them. In such case Authorize does not need to be a function (but you can still leave it like that, so your code would have more flexibility.

     

      TShopeeContext = class
      private
        FOnAuhorizeSucess, FOnAuthorizeFailure: TNotifyEvent;
      ...
      published
        property OnAuthorizeSucess: TNotifyEvent read FOnAuhorizeSucess write FOnAuhorizeSucess;
        property OnAuthorizeFailure: TNotifyEvent read FOnAuthorizeFailure write FOnAuthorizeFailure;
      end;
    
    function TShopeeContext.Authorize: Boolean;
    var
      Authorizator: TShopeeAuthorizator;
    begin
      Authorizator := TShopeeAuthorizator.Create(FDataHolder, FHost, FAPI_Key, FPartnerID, 8342);
      try
        Result := Authorizator.AuthorizationRequest(45000);
        if Result then
          begin
            if Assigned(FOnAuthorizeSucess) then FOnAuthorizeSucess(Self);
          end
        else
          begin
            if Assigned(FOnAuthorizeFailure) then FOnAuthorizeFailure(Self);
          end;
      finally
        Authorizator.Free;
      end;
    end;

    Of course, you can move both success and failure into single event handler, but then you would need to define handler that would have additional parameter.

     


  19. 2 hours ago, Stefan Glienke said:

    But due to their currently super old LLVM version (hopefully they upgrade that once they are done on the C++ side which personally I could not care less about), they apparently had to turn off some important optimization steps which causes the binary produced by the Linux compiler to be approx as good as -O0 :classic_wacko:

    Imagine how slow would compilation be with those optimizations turned on. 

     

    I hope than newer LLVM could be faster, but I wouldn't keep my hopes up https://discourse.llvm.org/t/if-llvm-is-so-slow-is-anything-being-done-about-it/75389

×