Recent

Author Topic: Runtime(0) Error  (Read 2133 times)

JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #15 on: July 02, 2020, 06:52:24 pm »
Ok so I did this:

Code: Pascal  [Select][+][-]
  1.  
  2.           if (Train = nil) and LccMessage.TractionSearchIsForceAllocate then
  3.           begin
  4.             try
  5.               CanNode := CreateTrainNode;
  6.   >> ADDED            Train := TLccTrain.Create;
  7.               Train := TrainDatabase.AddTrain('New Train', SearchStr, SearchDccAddress, ForceLongAddress, SpeedStep, CanNode);
  8.               ListViewTrains.AddItem(CanNode.NodeIDStr + ' : ' + CanNode.AliasIDStr, nil);
  9.                                              
  10.  

And it created a Train object no problem.  Here are the objects.... all the parameters passed look fine as well.


rvk

  • Hero Member
  • *****
  • Posts: 4334
Re: Runtime(0) Error
« Reply #16 on: July 02, 2020, 06:59:09 pm »
Yes, but now instead of Train := TrainDatabase.AddTrain()
remove that line and put in
TrainDatabase.TrainList.Add(Train);

Does your code run through in that case?

If it does not, remove the TrainDatabase lines entirely and see then.

JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #17 on: July 02, 2020, 07:06:22 pm »
ok, for testing purposes, kill the ethernet traffic and simulate a message to the same code in the same manner.. Use a Timer or something...

Can call it 1000 times without issue.

rvk

  • Hero Member
  • *****
  • Posts: 4334
Re: Runtime(0) Error
« Reply #18 on: July 02, 2020, 07:15:34 pm »
If it works when it's not called from synchronize then you're not doing the thread directly. Maybe you access a global or form variable outside synchronize. But we see too little code to determine that.

JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #19 on: July 02, 2020, 07:16:58 pm »
Yes, but now instead of Train := TrainDatabase.AddTrain()
remove that line and put in
TrainDatabase.TrainList.Add(Train);

Does your code run through in that case?

If it does not, remove the TrainDatabase lines entirely and see then.

Getting more bizarre... see the screen shot... failed on the assignment to DccAddress......


JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #20 on: July 02, 2020, 07:22:20 pm »
If it works when it's not called from synchronize then you're not doing the thread directly. Maybe you access a global or form variable outside synchronize. But we see too little code to determine that.

Do you mean from within the context of the thread?  I have a hard time seeing how.  The thread is a self contained unit I wrote that has no knowledge of what program I use it in.  I create the thread object in my form and the only connection is through assigning an event (OnReceiveMessage) to hook into when it receives a message (through this Synchronize call) and then it has a thread safe string list that the form has access to to place messages in the queue to be sent.  The thread picks up these messages without needing access to anything but that list.  I designed it to be very very separated from the application code to minimize these sorts of issues.
« Last Edit: July 02, 2020, 07:29:56 pm by JimKueneman »

JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #21 on: July 02, 2020, 07:28:53 pm »
This SHOULD work.... I am at the point where I think it is time to throw in another thread safe list and queue the incoming messages to and pick them up from within the context of the main thread....  In a way this is better since I can get into trouble the way I am doing it as I can get a message that I have to wait for another message before continuing and the only way to do that is with the dreaded Application.ProcessMessages and that little trick can get you into trouble really fast....

I really hate not understanding this before I move on so if anyone has any other ideas I would love to solve this.....

Jim

rvk

  • Hero Member
  • *****
  • Posts: 4334
Re: Runtime(0) Error
« Reply #22 on: July 02, 2020, 07:32:23 pm »
I really hate not understanding this before I move on so if anyone has any other ideas I would love to solve this.....
Not from the code you've shown so far.
It's still probably, like already mentioned, a stack corruption problem.

jamie

  • Hero Member
  • *****
  • Posts: 3669
Re: Runtime(0) Error
« Reply #23 on: July 02, 2020, 07:34:31 pm »
and where at what time does the List get accessed via the thread ?

LCL classes for the most part are not thread safe so any access to items out side the thread must be done via a sync call or the values must be placed in some sort of circular buffer that has been reallocated and is fixed in memory. This buffer should have a leading and trailing index it so that it does not collide across threads

 You can have multiple sync calls, one for each purpose but personally I would use a circular buffer scheme with dual indexes where there is no reordering of memory.

The only true wisdom is knowing you know nothing

JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #24 on: July 02, 2020, 07:47:33 pm »
and where at what time does the List get accessed via the thread ?

LCL classes for the most part are not thread safe so any access to items out side the thread must be done via a sync call or the values must be placed in some sort of circular buffer that has been reallocated and is fixed in memory. This buffer should have a leading and trailing index it so that it does not collide across threads

 You can have multiple sync calls, one for each purpose but personally I would use a circular buffer scheme with dual indexes where there is no reordering of memory.

I use a wrapper around a string list using critical sections for this application.  Nothing in the code above has any access to or from the thread.  The only place where the thread and the main thread can access the same data is during a "SendMessage" call (the later code in this method) and that thread safe TStringList wrapper.
« Last Edit: July 02, 2020, 07:50:30 pm by JimKueneman »

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 8800
  • FPC developer.
Re: Runtime(0) Error
« Reply #25 on: July 02, 2020, 07:53:59 pm »
Mixing interfaces and class references can be dangerous, and hard to debug.

JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #26 on: July 02, 2020, 07:54:10 pm »
Hold the phone.... is the thread blocked when Synchronize is called?  If not I may know what is going on.....

JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #27 on: July 02, 2020, 07:57:23 pm »
Mixing interfaces and class references can be dangerous, and hard to debug.

That was an earlier question, since I am not implementing IUnknown and deriving from only TPersistent there should be no reference counting going on.  I am only using them as away keep units separate.

JimKueneman

  • Full Member
  • ***
  • Posts: 178
Re: Runtime(0) Error
« Reply #28 on: July 02, 2020, 08:16:36 pm »
Maybe by documenting the path through the code it may show something obvious....

In the thread the data comes in as a string and I read it one byte at a time and put it through a state machine looking for a complete valid message sequence (CAN GridConnect).

Code: Pascal  [Select][+][-]
  1.  
  2.               RcvByte := Socket.RecvByte(1);
  3.               case Socket.LastError of
  4.                 0 :
  5.                   begin
  6.                     GridConnectStrPtr := nil;
  7.                     if GridConnectHelper.GridConnect_DecodeMachine(RcvByte, GridConnectStrPtr) then
  8.                     begin
  9.                       FEthernetRec.MessageStr := GridConnectBufferToString(GridConnectStrPtr^);
  10.                       FEthernetRec.LccMessage.LoadByGridConnectStr(FEthernetRec.MessageStr);
  11.                       Synchronize({$IFDEF FPC}@{$ENDIF}DoReceiveMessage);
  12.                     end;
  13.                   end;
  14.                 WSAETIMEDOUT :
  15.                   begin
  16.  
  17.                   end;
  18.                 WSAECONNRESET   :
  19.                   begin                          
  20.  


The variable that contains the data lives in the thread object:

Code: Pascal  [Select][+][-]
  1. TLccEthernetServerThread =  class(TLccConnectionThread)
  2.     private
  3.       FEthernetRec: TLccEthernetRec;     <<<<<<<<<<<<<<<<<<<
  4.       FOnClientDisconnect: TOnEthernetRecFunc;
  5.       FOnErrorMessage: TOnEthernetRecFunc;
  6.       FOnConnectionStateChange: TOnEthernetRecFunc;
  7.       FOnReceiveMessage: TOnEthernetReceiveFunc;
  8.       FOnSendMessage: TOnMessageEvent;
  9.       FOwner: TLccEthernetServer;
  10.       {$IFDEF ULTIBO}
  11.       {$ELSE}
  12.       FSocket: TTCPBlockSocket;
  13.       FSocketHandleForListener: TSocket;
  14.       {$ENDIF}
  15.       FTcpDecodeStateMachine: TOPStackcoreTcpDecodeState
  16.  

So here is the Synchronize method from the thread object:

Code: Pascal  [Select][+][-]
  1. procedure TLccEthernetServerThread.DoReceiveMessage;
  2. var
  3.   L: TList;
  4.   i: Integer;
  5. begin
  6.   if not IsTerminated then
  7.   begin
  8.     // Called in the content of the main thread through Syncronize
  9.     if Assigned(OnReceiveMessage) then    // Do first so we get notified before any response is sent in ProcessMessage
  10.       OnReceiveMessage(Self, FEthernetRec);
  11.  
  12.     if Gridconnect then
  13.     begin
  14.       if Owner.NodeManager <> nil then
  15.         Owner.NodeManager.ProcessMessage(EthernetRec.LccMessage);   << Here is where it is sent to basically the message parser to decode and send it to the right place, it is passing the field local to the thread object but the thread is blocked so it does not matter
  16.          
  17.  

The NodeManager carries a list of nodes and just passes the message on to each node for processing.

Code: Pascal  [Select][+][-]
  1. procedure TLccNodeManager.ProcessMessage(LccMessage: TLccMessage);
  2. var
  3.   i: Integer;
  4. begin
  5.   DoLccMessageReceive(LccMessage);
  6.   for i := 0 to Nodes.Count - 1 do
  7.     TLccNode( Nodes[i]).ProcessMessage(LccMessage);
  8. end;  
  9.  

The nodes have huge case statements to route the message

Code: Pascal  [Select][+][-]
  1. function TLccNode.ProcessMessage(SourceLccMessage: TLccMessage): Boolean;
  2. var
  3.   TestNodeID: TNodeID;
  4.   Temp: TEventID;
  5.   AddressSpace, OperationType, TractionCode: Byte;
  6.   DoDefault: Boolean;
  7. begin
  8.  
  9.   // By the time a messages drops into this method it is a fully qualified OpenLCB
  10.   // message.  Any CAN messages that are sent as multi frames have been combined
  11.   // into a full OpenLCB message.
  12.  
  13.   Result := False;
  14.  
  15.   TestNodeID[0] := 0;
  16.   TestNodeID[1] := 0;
  17.  
  18.   // First look for a duplicate NodeID
  19.   if EqualNodeID(NodeID, SourceLccMessage.SourceID, False) then
  20.   begin
  21.     Logout;
  22.     Exit;
  23.   end;
  24.  
  25.  
  26.   // Next look to see if it is an addressed message and if not for use just exit
  27.  
  28.  
  29.   if SourceLccMessage.HasDestination then
  30.   begin
  31.     if not IsDestinationEqual(SourceLccMessage) then
  32.       Exit;
  33.   end;
  34.  
  35.   case SourceLccMessage.MTI of
  36.     MTI_OPTIONAL_INTERACTION_REJECTED :
  37.         begin
  38.           // TODO need a call back handler
  39.         end;
  40.  
  41.     // *************************************************************************
  42.     // *************************************************************************
  43.     MTI_VERIFY_NODE_ID_NUMBER      :
  44.         begin
  45.           if SourceLccMessage.DataCount = 6 then
  46.           begin
  47.             SourceLccMessage.ExtractDataBytesAsNodeID(0, TestNodeID);
  48.             if EqualNodeID(TestNodeID, NodeID, False) then
  49.             begin
  50.               WorkerMessage.LoadVerifiedNodeID(NodeID, GetAlias);
  51.               SendMessageFunc(WorkerMessage);
  52.             end
  53.           end else
  54.           begin
  55.             WorkerMessage.LoadVerifiedNodeID(NodeID, GetAlias);
  56.             SendMessageFunc(WorkerMessage);
  57.           end;
  58.           Result := True;
  59.         end;
  60.     MTI_VERIFY_NODE_ID_NUMBER_DEST :
  61.         begin
  62.           WorkerMessage.LoadVerifiedNodeID(NodeID, GetAlias);
  63.           SendMessageFunc(WorkerMessage);
  64.           Result := True;
  65.         end;
  66.     MTI_VERIFIED_NODE_ID_NUMBER :
  67.         begin
  68.            // TODO need a call back handler
  69.         end;
  70.  
  71.     // *************************************************************************
  72.     // *************************************************************************
  73.     MTI_SIMPLE_NODE_INFO_REQUEST :
  74.         begin
  75.           WorkerMessage.LoadSimpleNodeIdentInfoReply(NodeID, GetAlias, SourceLccMessage.SourceID, SourceLccMessage.CAN.SourceAlias, ProtocolSimpleNodeInfo.PackedFormat(StreamManufacturerData, StreamConfig));
  76.           SendMessageFunc(WorkerMessage);
  77.           Result := True;
  78.         end;
  79.     MTI_SIMPLE_NODE_INFO_REPLY :
  80.         begin  // Called if I send a SNIP Request and the other node replies
  81.           // TODO need a call back handler
  82.           Result := True;
  83.         end;
  84.  
  85.     // *************************************************************************
  86.     // *************************************************************************
  87.     MTI_PROTOCOL_SUPPORT_INQUIRY :
  88.         begin
  89.           WorkerMessage.LoadProtocolIdentifyReply(NodeID, GetAlias, SourceLccMessage.SourceID, SourceLccMessage.CAN.SourceAlias, ProtocolSupportedProtocols.EncodeFlags);
  90.           SendMessageFunc(WorkerMessage);
  91.           Result := True;
  92.         end;
  93.     MTI_PROTOCOL_SUPPORT_REPLY :
  94.         begin   // Called if I send a Protocol Support and loads the ProtocolSupportedProtocols with the data
  95.           // TODO need a call back handler
  96.           Result := True;
  97.         end;
  98.  
  99.     // *************************************************************************
  100.     // Producer/Consumer tell me what events do you care about (for routers, getting mass
  101.     // results for the state of the layout
  102.     // *************************************************************************
  103.     MTI_EVENTS_IDENTIFY :
  104.         begin
  105.           SendConsumedEvents;
  106.           SendProducedEvents;
  107.           Result := True;
  108.         end;
  109.     MTI_EVENTS_IDENTIFY_DEST :
  110.         begin
  111.           SendConsumedEvents;  // already known the destination is us
  112.           SendProducedEvents;
  113.           Result := True;
  114.         end;
  115.  
  116.     // *************************************************************************
  117.     // General Producer/Consumer Queries
  118.     // *************************************************************************
  119.     MTI_PRODUCER_IDENDIFY :
  120.         begin
  121.           // First see if we have any built in producers we can to reply automatically
  122.           // Note that the expectation is the app is maintaining the state of the event
  123.           // objects in parallel through the TProtocolEventsProduced object (Clear/Set/Unkown)
  124.  
  125.           // Let the application have a crack
  126.           DoDefault := True;
  127.            (NodeManager as INodeManagerCallbacks).DoProducerIdentify(Self, SourceLccMessage, DoDefault);
  128.           if DoDefault then
  129.           begin
  130.             Temp := SourceLccMessage.ExtractDataBytesAsEventID(0);
  131.             SendProducerIdentify(Temp);         // Compatible with Smart Pascal
  132.           end;
  133.           Result := True;
  134.         end;
  135.     MTI_CONSUMER_IDENTIFY :
  136.         begin
  137.           // First see if we have any preregistred consumers that we use that
  138.           // we can to reply automatically, we are not the producers so we
  139.           // don't need to keep the state upto date
  140.  
  141.           // Let the application have a crack
  142.           DoDefault := True;
  143.            (NodeManager as INodeManagerCallbacks).DoProducerIdentify(Self, SourceLccMessage, DoDefault);
  144.           if DoDefault then
  145.           begin
  146.             Temp := SourceLccMessage.ExtractDataBytesAsEventID(0);
  147.             SendConsumerIdentify(Temp);        // Compatible with Smart Pascal
  148.           end;
  149.           Result := True;
  150.         end;
  151.  
  152.     // *************************************************************************
  153.      // This block of messages is if we sent at "Producer" or "Consumer" Identify
  154.      // and these are the results coming back... I am not sure what "Consumer" Identify
  155.      // needs different states as the replying node is not in control of the state only
  156.      // the "Producer" is in control
  157.      // *************************************************************************
  158.      MTI_CONSUMER_IDENTIFIED_CLEAR :
  159.         begin
  160.       ..........
  161.  

Here is the message we are handling:

Code: Pascal  [Select][+][-]
  1.  // *************************************************************************
  2.     // General Producer/Consumer Queries
  3.     // *************************************************************************
  4.     MTI_PRODUCER_IDENDIFY :
  5.         begin
  6.           // First see if we have any built in producers we can to reply automatically
  7.           // Note that the expectation is the app is maintaining the state of the event
  8.           // objects in parallel through the TProtocolEventsProduced object (Clear/Set/Unkown)
  9.  
  10.           // Let the application have a crack
  11.           DoDefault := True;
  12.            (NodeManager as INodeManagerCallbacks).DoProducerIdentify(Self, SourceLccMessage, DoDefault);
  13.           if DoDefault then
  14.           begin
  15.             Temp := SourceLccMessage.ExtractDataBytesAsEventID(0);
  16.             SendProducerIdentify(Temp);         // Compatible with Smart Pascal
  17.           end;
  18.           Result := True;
  19.         end;
  20.     MTI_CONSUMER_IDENTIFY :
  21.         begin
  22.           // First see if we h
  23.  

The node calls back to the NodeManager to its event "Do" method where the application can assign an event handler to access it.

Code: Pascal  [Select][+][-]
  1. procedure TLccNodeManager.DoProducerIdentify(LccNode: TLccNode;
  2.   LccMessage: TLccMessage; var DoDefault: Boolean);
  3. begin
  4.   if Assigned(OnLccNodeProducerIdentify) then
  5.     OnLccNodeProducerIdentify(Self, LccNode, LccMessage, DoDefault);
  6. end;  
  7.  

And finally into the main Forms handler for this:

Code: Pascal  [Select][+][-]
  1. procedure TFormTrainCommander.OnNodeIdentifyProducers(Sender: TObject;
  2.   LccSourceNode: TLccNode; LccMessage: TLccMessage; var DoDefault: Boolean);
  3. var
  4.   NMRA_SpeedStep: TLccDccSpeedStep;
  5.   NMRA_ForceLongAddress: Boolean;
  6.   SearchStr: string;
  7.   SearchDccAddress: LongInt;
  8.   ForceLongAddress: Boolean;
  9.   Train: TLccTrain;
  10.   SpeedStep: TLccDccSpeedStep;
  11.   ListIndex: Integer;
  12.   AnEvent: TEventID;
  13.   CanNode: TLccCanNode;
  14. begin
  15.   // Don't allow reentrant calls
  16.   if InitializationWait then Exit;
  17.  
  18.  
  19.   // Only the CommandStation replies to this Event
  20.   if LccSourceNode = CommandStationNode then
  21.   begin
  22.     if LccMessage.TractionSearchIsEvent then
  23.     begin
  24.       NMRA_ForceLongAddress := False;
  25.       NMRA_SpeedStep := ldssDefault;
  26.  
  27.       SearchStr := LccMessage.TractionSearchDecodeSearchString;
  28.  
  29.       if TryStrToInt(SearchStr, SearchDccAddress) then                       // Gaurd against an empty string
  30.       begin
  31.         ForceLongAddress := False;                          // Setup up what we call defaults
  32.         SpeedStep := ldss14;                                // Setup up what we call defaults
  33.  
  34.         if LccMessage.TractionSearchIsProtocolAny then
  35.         begin
  36.  
  37.         end else
  38.         if LccMessage.TractionSearchIsProtocolDCC(NMRA_ForceLongAddress, NMRA_SpeedStep) then
  39.         begin
  40.           // Was a NMRA DCC message so look for the DCC specific information that overrides our defaults
  41.           LccMessage.TractionSearchIsProtocolDCC(ForceLongAddress, SpeedStep);
  42.           // Look for an existing Train
  43.           ListIndex := -1;
  44.           Train := TrainDatabase.FindByDccAddress(SearchDccAddress, ForceLongAddress, ListIndex);
  45.  
  46.           if (Train = nil) and LccMessage.TractionSearchIsForceAllocate then
  47.           begin
  48.             try
  49.               CanNode := CreateTrainNode;
  50.               Train := TLccTrain.Create;
  51.               Train.LccNode := CanNode;
  52.               Train.DccAddress := SearchDccAddress;
  53.               TrainDatabase.TrainList.Add(Train);
  54.          //     Train := TrainDatabase.AddTrain('New Train', SearchStr, SearchDccAddress, ForceLongAddress, SpeedStep, CanNode);
  55.               ListViewTrains.AddItem(CanNode.NodeIDStr + ' : ' + CanNode.AliasIDStr, nil);
  56.                                        
  57.  

This is not a visual component, it is all assigned and handled in code.

The only new thing that I have added recently would be using the interfaces.  This way the Nodes don't need to know about the manager that owns them, they just need to know about the Interface functions to call back to the manager.

Jim

jamie

  • Hero Member
  • *****
  • Posts: 3669
Re: Runtime(0) Error
« Reply #29 on: July 02, 2020, 09:02:33 pm »
Is the "DoReceiveMessage" being called via  Sync code ? if not it should be..

The only true wisdom is knowing you know nothing

 

TinyPortal © 2005-2018