Lazarus

Programming => General => Topic started by: JimKueneman on July 02, 2020, 02:56:11 pm

Title: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 02:56:11 pm
I am at my wits end on this one....  My application does this on Mac and Windows so it seems like the compiler should be eliminated as the problem.... 

After about 3-8 calls to this function the app stops and throws a "raised exception class 'RunError(0)'" then it throws an "unknown" exception at the entrance to a method call.  It did the same thing on a previous method call within the same object but I refactored and pulled that code into this method but it now fails on NEW method call that use to be in the previous method that I eliminated...   I don't know what to do.  If I break it just sits at the method, looking at that stack tells me nothing as it stops at this place it is.  Looking at the CPU it just shows a bunch of question marks. 

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.  
  16.   // Only the CommandStation replies to this Event
  17.   if LccSourceNode = CommandStationNode then
  18.   begin
  19.     if LccMessage.TractionSearchIsEvent then
  20.     begin
  21.       NMRA_ForceLongAddress := False;
  22.       NMRA_SpeedStep := ldssDefault;
  23.  
  24.       SearchStr := LccMessage.TractionSearchDecodeSearchString;
  25.  
  26.       if TryStrToInt(SearchStr, SearchDccAddress) then                       // Gaurd against an empty string
  27.       begin
  28.         ForceLongAddress := False;                          // Setup up what we call defaults
  29.         SpeedStep := ldss14;                                // Setup up what we call defaults
  30.  
  31.         if LccMessage.TractionSearchIsProtocolAny then
  32.         begin
  33.  
  34.         end else
  35.         if LccMessage.TractionSearchIsProtocolDCC(NMRA_ForceLongAddress, NMRA_SpeedStep) then
  36.         begin
  37.           // Was a NMRA DCC message so look for the DCC specific information that overrides our defaults
  38.           LccMessage.TractionSearchIsProtocolDCC(ForceLongAddress, SpeedStep);
  39.           // Look for an existing Train
  40.           ListIndex := -1;
  41.           Train := TrainDatabase.FindByDccAddress(SearchDccAddress, ForceLongAddress, ListIndex);
  42.  
  43.           if (Train = nil) and LccMessage.TractionSearchIsForceAllocate then
  44.           begin
  45.             CanNode := CreateTrainNode;
  46.  
  47. // Dies on this call and yes TrainDatabase is created, also the previous code the function call WAS WITHIN THIS SAME OBJECT and it did the same thing.
  48.           Train := TrainDatabase.AddTrain('New Train', SearchStr, SearchDccAddress, ForceLongAddress, SpeedStep, CanNode);
  49.  
  50.  
  51.             ListViewTrains.AddItem(CanNode.NodeIDStr + ' : ' + CanNode.AliasIDStr, nil);
  52.                                                                                      
  53.  

The method is nothing fancy in parameter passing:

Code: Pascal  [Select][+][-]
  1. function TLccTrainDatabase.AddTrain(ARoadName, ARoadNumber: string;
  2.   ADccAddress: Word; ALongAddress: Boolean; ASpeedStep: TLccDccSpeedStep;
  3.   ALccNode: TLccNode): TLccTrain;
  4. var
  5.   ATrain: TLccTrain;
  6. begin
  7.   Result := TLccTrain.Create;
  8.   Result.DccAddress := ADccAddress;
  9.   Result.RoadName := ARoadName;
  10.   Result.RoadNumber := ARoadNumber;
  11.   Result.FLongAddress := ALongAddress;
  12.   Result.FSpeedSteps := ASpeedStep;
  13.   Result.LccNode := ALccNode;
  14.   TrainList.Add(Result);
  15. end;
  16.  

I am at a loss to even understand what to look for since I don't know what that error code means "0".  I have the "Verify Method Calls" -CR option on but it does it with it off as well and the SEEMS like it acts up more with it on but it could just be coincidence.   

How do I debug this?

Thanks
Jim 
Title: Re: Runtime(0) Error
Post by: rvk on July 02, 2020, 03:39:36 pm
Can you view the Call Stack when you get this exception.

View > Debug Windows > Call Stack
(or CTRL + ALT + S)

What linenumber does it say for the unit containing TLccTrainDatabase.AddTrain()

(You can also right click there and choose copy all and post it)
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 03:48:18 pm
#0 fpc_raiseexception at :0
#1 SYSUTILS_$$_RUNERRORTOEXCEPT$LONGINT$POINTER$POINTER at :0

// Green arrow points here to line #2

#2 ONNODEIDENTIFYPRODUCERS(0x2819e04, 0xd21624, 0xd15e24, 0x20e7404, true) at traincommanderunit.pas:486
#3 DOPRODUCERIDENTIFY(0xd21624, 0xd15e24, 0x20e7404, true) at ../../Target-Common/lcc_node_manager.pas:428
#4 PROCESSMESSAGE(0xd15e24, 0x20e7404) at ../../Target-Common/lcc_node.pas:1137
#5 PROCESSMESSAGE(0xd15e24, 0x20e7404) at ../../Target-Common/lcc_node.pas:662
#6 PROCESSMESSAGE(0xd21624, 0x20e7404) at ../../Target-Common/lcc_node_manager.pas:616
#7 DORECEIVEMESSAGE(0xf669b4) at ../../Target-PC/HardwareConnection/lcc_ethernet_server.pas:940
#8 CLASSES_$$_EXECUTETHREADQUEUEENTRY$TThread.PTHREADQUEUEENTRY at :0
#9 CARBONAPP_LAZWAKE(0xbfffeb70, 0xd191d0, 0x0) at carbonobject.inc:375
#10 _InvokeEventHandlerUPP at :0
#11 DispatchEventToHandlers at :0
#12 SendEventToEventTargetInternal at :0
#13 SendEventToEventTargetWithOptions at :0
#14 ToolboxEventDispatcherHandler at :0
#15 DispatchEventToHandlers at :0
#16 SendEventToEventTargetInternal at :0
#17 SendEventToEventTarget at :0
#18 APPPROCESSMESSAGES(0xe04274) at carbonobject.inc:693
#19 HANDLEMESSAGE(0x2009c04) at application.inc:1282
#20 RUNLOOP(0x2009c04) at application.inc:1419
#21 EVENTLOOPEVENTHANDLER(0xbffff510, 0xd21930, 0xe04274) at carbonobject.inc:173
#22 _InvokeEventHandlerUPP at :0
#23 DispatchEventToHandlers at :0
#24 SendEventToEventTargetInternal at :0
#25 SendEventToEventTargetWithOptions at :0
#26 ToolboxEventDispatcherHandler at :0
#27 DispatchEventToHandlers at :0
#28 SendEventToEventTargetInternal at :0
#29 SendEventToEventTarget at :0
#30 ToolboxEventDispatcher at :0
#31 RunApplicationEventLoop at :0
#32 APPRUN(0xe04274, {n  Proc = {procedure (POINTER)} 0xbffffa8c, n  Self = 0x2009c04n}) at carbonobject.inc:635
#33 RUN(0x2009c04) at application.inc:1407
#34 PASCALMAIN at TrainCommander.lpr:20
#35 SYSTEM_$$_FPC_SYSTEMMAIN$LONGINT$PPCHAR$PPCHAR at :0
#36 _start at :0
#37 start at :0
Title: Re: Runtime(0) Error
Post by: jamie on July 02, 2020, 04:05:15 pm
An error like that is usually related to the STACK being unrolled early..

That can happen if you flood the local stack within a procedure and this is usually caused over memory overwrites.

 Also looking at your report here, it could also be you are processing some items during a Onxxxxxx event that could be getting triggered at the wrong time and there by disrupting your algorithm .

 I don't know your structure of your classes but if you are using property fields instead of directly assigning the fields, you could have a setter method doing something to early..

 Just a guess really.
Title: Re: Runtime(0) Error
Post by: rvk on July 02, 2020, 04:10:21 pm
You could put a breakpoint on that line (with F5), run it again (with debugging F9), and examine the variables when it breaks on that breakpoint. Including if TrainDatabase itself is still valid. But it indeed doesn't seem like a classical runtime error.

After examining you could try F7 (to step into the procedure).
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 04:29:31 pm
You could put a breakpoint on that line (with F5), run it again (with debugging F9), and examine the variables when it breaks on that breakpoint. Including if TrainDatabase itself is still valid. But it indeed doesn't seem like a classical runtime error.

After examining you could try F7 (to step into the procedure).

Thanks, I have done that numerous times and all the variables are exactly what I expect them to be and if I try ti single step into the procedure it just throws the error.
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 04:32:49 pm
An error like that is usually related to the STACK being unrolled early..

That can happen if you flood the local stack within a procedure and this is usually caused over memory overwrites.

 Also looking at your report here, it could also be you are processing some items during a Onxxxxxx event that could be getting triggered at the wrong time and there by disrupting your algorithm .

 I don't know your structure of your classes but if you are using property fields instead of directly assigning the fields, you could have a setter method doing something to early.. 

Line #8 appears to be where it moves from the context of the Ethernet thread to the main thread.

 Just a guess really.

So one thing to note here this could be significant is this is an app that implements an Ethernet messaging protocol.  So I have a Synapse running in a thread receiving messages.  This is called through a Synchronize call from within that thread processing the message that was received.
Title: Re: Runtime(0) Error
Post by: rvk on July 02, 2020, 04:39:00 pm
This is called through a Synchronize call from within that thread processing the message that was received.
If this is done correctly it shouldn't be a problem (but that's a big if) :)
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 04:56:08 pm
So on its journey it passes through this object that acts as the dispatcher of the messages...  this does not create a reference counted object correct?  I would need to decend from one of the TInterfacedxxxx objects for that to occur since I have not implemented IUnknown.   

Code: Pascal  [Select][+][-]
  1.   TLccNodeManager = class(TComponent, INodeManagerCallbacks)
  2.   private
  3.     FOnLccNodeAliasIDChanged: TOnLccNodeMessage;  
Title: Re: Runtime(0) Error
Post by: Aidex on July 02, 2020, 05:14:50 pm
Just one more thing about the stack:
If it is a stack problem, you could try to define all parameters of the function as "const".
And what is the local ATrain variable for?
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 05:56:13 pm
So I moved the local variables out of the function into the global variable space and the same problem occurs...

I use it later in the method to send a reply through the ethernet link.

Code: Pascal  [Select][+][-]
  1.         if (Train <> nil) then
  2.           begin
  3.             AnEvent := LccMessage.ExtractDataBytesAsEventID(0);
  4.             if Train.LccNode is TLccCanNode then
  5.               WorkerMsg.LoadProducerIdentified(Train.LccNode.NodeID, (Train.LccNode as TLccCanNode).AliasID, AnEvent, evs_Valid )
  6.             else
  7.               WorkerMsg.LoadProducerIdentified(Train.LccNode.NodeID, 0, AnEvent, evs_Valid );
  8.  
  9.             NodeManager.SendMessage(WorkerMsg);              
  10.  
Title: Re: Runtime(0) Error
Post by: rvk on July 02, 2020, 06:09:05 pm
What happens if you remove the Train := TrainDatabase.AddTrain() line and create a Train := TLccTrain.Create; yourself there.
If that works you know it's either the TrainDatabase instance which is corrupt or one of its variables or one of the passes parameters.

Title: Re: Runtime(0) Error
Post by: jamie on July 02, 2020, 06:16:50 pm
you said you send a reply when ever you receive a ethernet message?

Are you doing this while in the synchronized called ?

If so there could be some issue there.

can you cancel the reply send or use a cached delay method of doing it?
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 06:38:24 pm
you said you send a reply when ever you receive a ethernet message?

Are you doing this while in the synchronized called ?

If so there could be some issue there.

can you cancel the reply send or use a cached delay method of doing it?

It never makes it to the code where I send the reply message.  Also the reply message does buffer the reply in a thread safe list and returns.  The thread picks up the messages from that list later.

Title: Re: Runtime(0) Error
Post by: jamie on July 02, 2020, 06:46:57 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...
Title: Re: Runtime(0) Error
Post by: JimKueneman 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.

Title: Re: Runtime(0) Error
Post by: rvk 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.
Title: Re: Runtime(0) Error
Post by: JimKueneman 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.
Title: Re: Runtime(0) Error
Post by: rvk 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.
Title: Re: Runtime(0) Error
Post by: JimKueneman 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......

Title: Re: Runtime(0) Error
Post by: JimKueneman 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.
Title: Re: Runtime(0) Error
Post by: JimKueneman 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
Title: Re: Runtime(0) Error
Post by: rvk 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.
Title: Re: Runtime(0) Error
Post by: jamie 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.

Title: Re: Runtime(0) Error
Post by: JimKueneman 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.
Title: Re: Runtime(0) Error
Post by: marcov on July 02, 2020, 07:53:59 pm
Mixing interfaces and class references can be dangerous, and hard to debug.
Title: Re: Runtime(0) Error
Post by: JimKueneman 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.....
Title: Re: Runtime(0) Error
Post by: JimKueneman 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.
Title: Re: Runtime(0) Error
Post by: JimKueneman 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
Title: Re: Runtime(0) Error
Post by: jamie on July 02, 2020, 09:02:33 pm
Is the "DoReceiveMessage" being called via  Sync code ? if not it should be..

Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 09:13:57 pm
Is the "DoReceiveMessage" being called via  Sync code ? if not it should be..

Yes through here, this is the thread code.

Code: Pascal  [Select][+][-]
  1.  RcvByte := Socket.RecvByte(1);
  2.               case Socket.LastError of
  3.                 0 :
  4.                   begin
  5.                     GridConnectStrPtr := nil;
  6.                     if GridConnectHelper.GridConnect_DecodeMachine(RcvByte, GridConnectStrPtr) then
  7.                     begin
  8.                       FEthernetRec.MessageStr := GridConnectBufferToString(GridConnectStrPtr^);
  9.                       FEthernetRec.LccMessage.LoadByGridConnectStr(FEthernetRec.MessageStr);
  10.                       Synchronize({$IFDEF FPC}@{$ENDIF}DoReceiveMessage);
  11.                     end;
  12.                   end;
  13.                 WSAETIMEDOUT :
  14.  
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 09:15:54 pm
Ok I am going to blame it on 2020, what else could it be.... I added a thread safe queue between the thread and the main process and used a timer to pull out the strings from the thread safe string list and it does the EXACT SAME THING......

Title: Re: Runtime(0) Error
Post by: jamie on July 02, 2020, 09:19:13 pm
what kind of strings are you using ? if you are using managed strings please try something that isn't managed.

array of char, ShortString etc..
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 10:04:12 pm
Well that is unfulfilling..... I lost the network connection between my VM and OS X and the only way I could get it working again was to reboot the Mac....  Now it works regardless of how I handle the thread....  The first time the Mac has been rebooted in months.
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 10:58:43 pm
I just hammered the heck out of it creating thousands of nodes and not a single issue....  Thanks for the help.
Title: Re: Runtime(0) Error
Post by: rvk on July 02, 2020, 11:08:30 pm
It's like magic  :D
Title: Re: Runtime(0) Error
Post by: JimKueneman on July 02, 2020, 11:12:57 pm
It's like magic  :D

And I hate it..... it will be back..... I know it....   things don't "fix themselves".....
Title: Re: Runtime(0) Error
Post by: winni on July 02, 2020, 11:19:13 pm
Hi!

Must not be magic.

Could be something only the NASA takes care of:

Statistically in every PC there are 2 to 3 bits flipped in 24 hours due to hard radiation from the sun.
If you are lucky it is in the empty part of the RAM.
Or just a pixel in an icon.
Or just a letter in an error message.

But if you got bad luck it can hit a node.
And then you got that miracle!

winni
Title: Re: Runtime(0) Error
Post by: TRon on July 02, 2020, 11:22:34 pm
It's like magic  :D

And I hate it..... it will be back..... I know it....   things don't "fix themselves".....
hmz... turn computer off, problem goes away... I should use that more often  :D

But jokes aside, your project wouldn't be the only one in existence that requires a reboot once in a while. I remember a piece of hardware back in the dos and early windows days, that would just confuse itself over a period of time (bad hw design) and required a proper reboot of the machines once a week. Let the machine run for 1 week and 1 day, and you were back in software-hell. And of course the hardware manufacturer blamed it on the software.
Title: Re: Runtime(0) Error
Post by: winni on July 02, 2020, 11:28:13 pm
Hi!

We got a " Master PC" runing 24/7 to get the updates for the other clients for a finance software.
That PC is Win7/64. There is a big chance that after 30 days the network connection is lost - no chance to recover without reboot. So we boot that machine every 14 days.

Winni
Title: Re: Runtime(0) Error
Post by: rvk on July 02, 2020, 11:40:02 pm
2^32 seconds is 49.7 days  :D

(The well known counter overflow  ;))
Title: Re: Runtime(0) Error
Post by: winni on July 02, 2020, 11:44:32 pm
Yes - that were the NT-days.
That is fixed now.

But what about the other 27543 unknown window bugs?

Winni



Title: Re: Runtime(0) Error
Post by: rvk on July 03, 2020, 12:34:09 am
Yes - that were the NT-days.
That is fixed now.
In Windows itself yes. I would bet there are still tons of programs (and libraries) which still use GetTickCount over GetTickCount64.

(And GetTickCount64 isn't even available on XP  :P )
Title: Re: Runtime(0) Error
Post by: winni on July 03, 2020, 12:54:46 am
Bad enough - but you are so right.

But GetTickCount64 is enough  for the near future.

For the next 500 000 000 years .....

Winni
Title: Re: Runtime(0) Error
Post by: jamie on July 03, 2020, 01:13:21 pm
2^32 seconds is 49.7 days  :D

(The well known counter overflow  ;))

Never had an issue with that. I made a function where by it would detect the overflow and increment another variable in the app.

 The app never used the return results directly from the 32 bit version of that function but the enhanced version I had in the app..

 Just let it wrap around, if its smaller than last time, increment the upper 32 bit value.

that is who you do most wrap arounds in code..  :)
Title: Re: Runtime(0) Error
Post by: winni on July 03, 2020, 02:29:40 pm

that is who you do most wrap arounds in code..  :)

You should have told that to Micro$oft.
And not to us.

The fact is: Windows NT stopped working after 49 days.
Due to the 32-Bit-overrun.
It took some time until the found the error.

Winni
Title: Re: Runtime(0) Error
Post by: jamie on July 03, 2020, 03:58:36 pm
Maybe it stopped working if your app was using that function without fixing it but we still have machines using Windows2000 early versions and they only need rebooting if for some reason Power is unstable.

The GetTick function is used in the software, we have no failures due to that.

 I don't know what to tell you, it works for us months and months without a reboot etc...


TinyPortal © 2005-2018