SAMPro
SAMPro

Reputation: 1109

Some packet is dropped when using TIdNotify.NotifyMethod

I have a server that sends it's status every 0.1 seconds. I use this code to access UI and show the result on the client program.

procedure TModules.TCPServerExecute(AContext: TIdContext);
begin
   Buffer:= AContext.Connection.IOHandler.ReadLn();
   TIdNotify.NotifyMethod( ServerExecute );
end;

But some packet is not received by ServerExecute. I change the code and used TIdSync.SynchronizeMethod. The problem solved:

procedure TModules.TCPServerExecute(AContext: TIdContext);
begin
   Buffer:= AContext.Connection.IOHandler.ReadLn();
   TIdSync.SynchronizeMethod( ServerExecute );
end;

I have read on this site that TIdSync.SynchronizeMethod may produce deadlock. So I want to know what is the problem with TIdNotify.NotifyMethod. Also I see an answer that suggest using a Timer to synchronize UI and don't using Notify() nor Synchronize() method.

Also I must mention that the program does rest of the work inside ServerExecute.

As you see I'm narrowing down the code to the following. I must mention that I executed the program with exact the following code and it already has the problem and doesn't get desired results:

procedure TModules.ServerExecute;
var
  I: Word;
  tmp, Packet, Cmd:string;
  CheckSum: Boolean;
begin
  //try
    frmDiag.DataLog.Lines.Add(Buffer);

    {ExplodeStr(Buffer, Cmd, Packet);

    if Cmd='status' then
    begin

      //ExplodeStr(Packet, Cmd, Packet);

      if trim(Packet)='' then
        raise Exception.Create('Empty packet received.');

      //Prepare for log
      {tmp:='';
      for I := 1 to Length(Packet) do
      begin
        if (Ord(Packet[I])>=48) and (Ord(Packet[I])<=122) then
          tmp:=tmp+Packet[I]+''
        else
          tmp:=tmp+IntToStr(Ord(Packet[I]))+'';
      end;

      //frmITCAMain.Memo1.Lines.Add(Packet);
      CheckSum:=ParsePackets(Packet);
      IntersectionsList.Int.CallNotifier;       //Call the notifier to execute assigned proc

      if frmLoader.Visible=true then
        with frmLoader do
        begin
          if
            //(Packet[1]='X') or            //Server responce
            (Packet[1]<>'l') and (Packet[1]<>'s') and (Packet[1]<>'e')and   //ignore general packet
            (
                (Req[1]<>'f')                       //Only receive ACK
                or
                ((Req[1]='f')and( (Req[2]='m')or(Req[2]='a')or(Req[2]='b') ))
                or
                (                       //Receive Data+ACK
                  (Req[1]='f')and( (Req[2]='g')or(Req[2]='h')or(Req[2]='p')or(Req[2]='j') )
                  and
                  (Packet[1]<>'k')      //Ignore ACK
                )
            )
          then
            begin
              if CheckSum then
              begin
                Res:= Packet;
                Confirm;
              end
              else
              begin
                if Packet='n' then  //Checksum failure
                  Fail
                else
                  SendPacket;   //Resend.
                //lblError.Visible:=true;
              end;
            end;
        end;

      if (Packet[1]='g') or (Packet[1]='h') or (Packet[1]='p') or
      (Packet[1]='j')  or (Packet[1]='k') then
          frmIntDetails.memReceived.Lines.Text:=tmp;

    end

    else if Cmd='server' then
    begin
      with frmLoader do
      begin
          if Visible=false then exit;

        Res:= Packet;
        if Copy(Res, 1, 2)='ok' then
            Confirm
        else
            Cancel;
      end;
    end

    else
      ClientLog.Add(Buffer);

  except on E: Exception do
    begin
      if E.Message='Connection Closed Gracefully.' then
        ClientLog.Add('X:Connection closed(Gracefully)')
      else
        ClientLog.Add(E.Message+' Buffer="'+Buffer+'"');
    end;
  end;
  //Memo2.Lines.Add( FloatToStr(TimeStampToMSecs(DateTimeToTimeStamp(Now))));
  }
end;

frmDiag.DataLog is a TMemo component.

Inside frmDiag.DataLog for example the following list is the result that i expected (The following strings is extracted from the Datalog component with TIdSync.SynchronizeMethod solution):

status:l77770000140000
status:eFFFF20000140
status:s0000
status:s0000
status:s0000
status:s0000
status:l00005555140000
status:eFFFF20000140
status:s0000
status:s0000
status:s0000
status:s0000
status:l77770000140000
status:eFFFF20000140

But instead I get this result:

status:eFFFF20000140
status:eFFFF20000140    
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:s0000
status:l00005555140000
status:l77770000140000  

As you see order is not met.

I must get a status:l77770000140000 like packet then a status:eFFFF20000140 an then 4 status:s0000 and so on.

@Remy I've changed your code a little:

TMyNotify = class(TIdNotify)
protected
  FBuffer: String;
  FMethod: TThreadMethod;
  procedure DoNotify; override;
public
  constructor Create(const ABuffer: String; AMethod: TThreadMethod);
end;

///......

{ TMyNotify }

constructor TMyNotify.Create(const ABuffer: String; AMethod: TThreadMethod);
begin
  inherited Create;
  FBuffer := ABuffer;
  FMethod := AMethod;
end;

procedure TMyNotify.DoNotify;
begin
  inherited;
  FMethod;
  //Destroy;
end;

And this is how I call ServerExcecute now:

procedure TModules.TCPServerExecute(AContext: TIdContext);
begin
  Buffer:= AContext.Connection.IOHandler.ReadLn();

  TMyNotify.Create(Buffer, ServerExecute).Notify;

//  ServerExecute;
//  TIdNotify.NotifyMethod( ServerExecute );
//  TIdSync.SynchronizeMethod( ServerExecute );
end;

Upvotes: 2

Views: 934

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597036

TIdNotify is asynchronous. If you receive a new line and overwrite your Buffer variable before a previous line has a chance to be processed, the previous line will be lost. Change your code to pass the line value inside the TIdNotify itself, eg:

type
  TMyDataProc = procedure(const ABuffer: String) of object;

  TMyNotify = class(TIdNotify)
  protected
    fBuffer: String;
    fProc: TMyDataProc;
    procedure DoNotify; override;
  public
    constructor Create(const ABuffer: String; AProc: TMyDataProc);
  end;

constructor TMyNotify.Create(const ABuffer: String; AProc: TMyDataProc);
begin
  inherited Create;
  fBuffer := ABuffer;
  fProc := AProc;
end;

procedure TMyNotify.DoNotify;
begin
  fProc(fBuffer);
end;

procedure TModules.TCPServerExecute(AContext: TIdContext);
var
  LBuffer: String;
begin
  LBuffer := AContext.Connection.IOHandler.ReadLn();
  TMyNotify.Create(LBuffer, ServerExecute).Notify();
end;

procedure TModules.ServerExecute(const ABuffer: String);
begin
  // use ABuffer as needed...
end;

Upvotes: 4

Related Questions