Jump to content

fastbike

Members
  • Content Count

    25
  • Joined

  • Last visited

Posts posted by fastbike


  1. 23 hours ago, fastbike said:

    Thanks Uwe, I'll take a look later today.
    I see they have a 64 bit DLL on the vendor website so I will update the pas import file to match.

    Now that was useful, many thanks.

    I spent about an hour getting my head around the whole 32 bit app running on a 64 bit OS, and unicode thing but after changing declarations using PChar to PAnsiChar and also figuring out that the safest way of calling some of the functions that were looking for a PChar to populate, was to declare an array of Byte and pass in the address - it all worked out ok.

    The CANUSB driver installs the header files so you can check what is required but as far as I could tell the Delphi import unit had got everything correct.
    I've created a simple monitor with some decoding of the Velbus protocol so I can figure out what is going on at the bus level.


  2. 7 hours ago, Uwe Raabe said:

    It made use of canusbdrv.dll and a Delphi wrapper (see attachment). This was sometimes around 2008, so most likely that interface may have changed since then.

    uCanUsbDll.pas

    Thanks Uwe, I'll take a look later today.
    I see they have a 64 bit DLL on the vendor website so I will update the pas import file to match.


  3. On 11/11/2023 at 12:07 PM, Uwe Raabe said:

    It has been quite a while, but I had a good experience with CANUSB.

    I've bought the CANUSB dongle but they seem to only offer support for dot Net.
    I've found an old project CanDe that uses an older version of the ComPortDriver but I can't get it to connect to the bus.

     

    Do you remember what was required ?


  4. 11 hours ago, David Heffernan said:

    Anyway it's kinda hard to analyse your code with it spread over loads of different posts. 

    Blame the scattered posts on the forum software consistently refusing to allow me to post anything here two days ago - each time posts with codeblocks were tagged as spam and after editing the attempted post I received notification that I had exceeded the number of posts - which is just way beyond frustrating.
    I will start another thread on Stackoverflow since this one has become very hard to follow.


  5. 16 hours ago, David Heffernan said:

    If the methods here can all be called arbitrarily from multiple threads then you seem to have at the very least a race on FOperationsLoaded. I don't understand what that call to Wait is for either. 

    There is one global object.

    It has a method that loads the data into it (Reload). There is an internal flag to say if the data has been loaded.

    There is another method to access that data (GetOperation) - client threads (from IIS so I do not create these consumer threads) call this method which needs to block until the data has been loaded which is signified by the internal flag being set.

    Is TMonitor the correct synchronisation structure here ?

     


  6. And finally a much improved version of the method that should not return until the Reload method above has done its thing.

    function TFHIROperationFactory.GetOperation(Id: string): IFHIROperationDefintion;
    begin
      Log.Debug('GetOperation for %s', [Id], DefaultLogTag);
    
      if not FOperationsLoaded then
      begin
        Log.Debug('Waiting for Operations to be loaded', DefaultLogTag);
        TMonitor.Enter(Self);
        try
          for var I := 0 to Settings.ReadInteger('Global', 'RetryOperationsReload', 15) do // prevent run away loop
          begin
            while not FOperationsLoaded do
              TMonitor.Wait(Self, 250);
          end;
          if not FOperationsLoaded then
            raise EFHIRExceptionServer.Create('Could not load FHIR Operations, check settings Global|RetryOperationsReload');
        finally
          TMonitor.Exit(Self);
        end;
      end;
    
      if not FRegisteredOperations.TryGetValue(Id, Result) then
      begin
        raise EFHIRExceptionServer.CreateFmt('FHIR Operation with id %s not registered', [Id]);
      end;
    end;

     


  7. An improved version of the Reload method (called either independently via an URL endpoint) or as part of the lazy creation of the object (see above)

    This one does a pulse all to advise any waiting threads that the global objects is now unlocked.

    /// <summary>Load (or reload) the OperationDefinition resources from *.xml files, and populate the type mapper</summary>
    procedure TFHIROperationFactory.Reload;
    begin
      TMonitor.Enter(Self);
      try
        FOperationsLoaded := false;
        LoadOperationDefinitions;
        FOperationsLoaded := true;
        TMonitor.PulseAll(Self);
      finally
        TMonitor.Exit(Self);
      end;
    end;

     


  8. And the code that gets called by other worker threads when they need a IFHIROperationDefintion instance. I think the TMonitor code is of no effect - in hindsight it should actually be sitting there and spinning (via  TMonitor.Wait ? ) until the Reload method has set the lock variable (FOperationsLoaded). I'll provide an improved implementation of these two methods in the next post for inspection and comment.

     

    function TFHIROperationFactory.GetOperation(Id: string): IFHIROperationDefintion;
    begin
      if not FOperationsLoaded then
      begin
        Log.Debug('Waiting for Operations to be loaded', DefaultLogTag);
        TMonitor.Enter(Self);
        try
          if not FOperationsLoaded then
          begin
            Reload;
          end;
        finally
          TMonitor.Exit(Self);
        end;
      end;
    
      if not FRegisteredOperations.TryGetValue(Id, Result) then
      begin
        raise EFHIRExceptionServer.CreateFmt('FHIR Operation with id %s not registered', [Id]);
      end;
    end;

     


  9. And the code that does the initial loading / reloading of the xml files and creation of the implementation objects

    ///<summary>Load (or reload) the OperationDefinition resources from *.xml files</summary>
    procedure TFHIROperationFactory.Reload;
    begin
      FOperationsLoaded := false;
      TMonitor.Enter(Self);
      try
        LoadOperationDefinitions; // a helper method that reads files from a directory, creates objects and adds to the dictionary
        FOperationsLoaded := true;
      finally
        TMonitor.Exit(Self);
      end;
    end;

     


  10. My implementation, very similar but with a function that @Remy Lebeau helped me write.

     

    The singleton can only be accessed via the global function pointer as all of this code is in the implementation section.

     

    // global singleton variables/methods
    var
      _FHIROperationFactory: IFHIROperationFactory;
    
    function GetDefaultFHIROperationFactory: IFHIROperationFactory;
    var
      newFHIRFactory: IFHIROperationFactory;
    begin
      if _FHIROperationFactory = nil then
      begin
        { The object does not exist yet. Create one. }
        newFHIRFactory := TFHIROperationFactory.Create;
        { It is possible another thread also created one, so get the first}
        InterlockedCompareExchangeIntf(IInterface(_FHIROperationFactory), newFHIRFactory, nil);
        _FHIROperationFactory.Reload;
      end;
      Result := _FHIROperationFactory;
    end;
    
    initialization
    // assign an implemntation method to the global function pointer
    FHIROperationFactory := GetDefaultFHIROperationFactory;
    
    end.

     


  11. Next up there is a default implementation which can read xml files from  disk and create a list of IFHIROperationDefintion (worker) objects.

    // concrete class declared in the implementation part of the unit, so only accessible via the global function
    type
    /// <summary>Concrete class for FHIR Operations </summary>
      TFHIROperationFactory = class(TInterfacedObject, IFHIROperationFactory)
      private
        FOperationsLoaded: Boolean;
        FRegisteredOperations: TDictionary<string, IFHIROperationDefintion>;
        procedure LoadOperationDefinitions;
      protected
        { IFHIROperationFactory }
        function GetOperation(Id: string): IFHIROperationDefintion;
        procedure Reload;
      public
        constructor Create;
        destructor Destroy; override;
      end;

     


  12. Here's the declaration of the type and a global function to an instance of that type

    // interface to provide functionality that will be implemented by a global object
    type
      /// <summary>Register of supported FHIR Operations</summary>
      IFHIROperationFactory = interface
        ['{6E207FA9-CBB8-4F7A-A54F-D90621B601C8}']
        function GetOperation(Id: string): IFHIROperationDefintion;
        procedure Reload;
      end;
    
    // a global pointer to a function that returns an instance (allows mocking for testing other parts of the code)
    var
      FHIROperationFactory: function: IFHIROperationFactory;

     


  13. Thanks for the comments and suggestions. I'll copy in some simplified code snippets - I think I have misunderstood how TMonitor would protect multiple threads from accessing methods of  a global object.

     

    I'm trying to post an example here and keep getting a message saying my post appears to be spam !


    I'll try posting each separately so apologies in advance for having to read through it all.


  14. 1 hour ago, pyscripter said:

    Indeed.  This is the whole purpose of locking.

    I think I worded that part of my question poorly in retrospect.

     

    If one thread is calling a method to initialize/load the objects, what is required to prevent a second thread from accessing the global object via another method that tries to read from the list of items.

    I'm thinking I need a Tmonitor.wait loop in that second method that waits for the class level flag to be set by the initialize method after it has finished loading the items.

    I will post some code snippets tomorrow.


  15. We have a global object to manage a list of long lived objects that are created by reading an XML definition file from disk and parsing it to create an in memory structure.

     

    The global object itself uses a lazy creation pattern as it is possible that the functionality contained within may not be accessed during the lifetime of the application (web server hosted under IIS with a 2 hour recycle time).

     

    The lazy creation pattern checks to see if the object exists, if not it is created, then it initializes the items from the xml definition files.  While it is being initialised we want to prevent any other thread from accessing the methods.

    Is the TMonitor  record going to be useful here - i.e. if the first thread called TMonitor.Enter(Self) on the object would that prevent another thread from making the same call to the object before TMonitor.Exit(Self) had been called by the first thread.

     

    The compiler is Delphi 11.3 Enterprise.  I can post some code but I'm trying to understand in more general terms  how we can lock the object so only one thread can access it first.

×