fix #336 - Out of order messages

This commit is contained in:
Basile Burg 2019-01-20 12:12:03 +01:00
parent a5d47eb6d1
commit 492cd4e8e5
5 changed files with 74 additions and 44 deletions

View File

@ -2586,7 +2586,7 @@ end;
procedure TGdbWidget.gdboutQuiet(sender: TObject); procedure TGdbWidget.gdboutQuiet(sender: TObject);
begin begin
fCommandProcessed := true; fCommandProcessed := true;
fGdb.OutputStack.Clear; fGdb.StdoutEx.Clear;
fGdb.OnReadData:=@gdboutJsonize; fGdb.OnReadData:=@gdboutJsonize;
end; end;
{$ENDREGION} {$ENDREGION}

View File

@ -5,7 +5,7 @@ unit u_processes;
interface interface
uses uses
Classes, SysUtils, ExtCtrls, process, asyncprocess; Classes, SysUtils, ExtCtrls, process, asyncprocess, pipes;
type type
@ -19,6 +19,9 @@ type
- TAsyncProcess.OnReadData event is not usable to read full output lines. - TAsyncProcess.OnReadData event is not usable to read full output lines.
Here the output is accumulated in a TMemoryStream which allows to keep data Here the output is accumulated in a TMemoryStream which allows to keep data
at the left of an unterminated line when a buffer is available. at the left of an unterminated line when a buffer is available.
- When StdErr is redirect to Output, both streams can be blended. Here the
option is deactivated on execution, a falg is set, and the error stream
is always added to after the output.
The member Output is not usable anymore. Instead: The member Output is not usable anymore. Instead:
@ -29,10 +32,11 @@ type
} }
TDexedProcess = class(TASyncProcess) TDexedProcess = class(TASyncProcess)
private private
fErrToOut: boolean;
fRealOnTerminate: TNotifyEvent; fRealOnTerminate: TNotifyEvent;
fRealOnReadData: TNotifyEvent; fRealOnReadData: TNotifyEvent;
fOutputStack: TMemoryStream; fStdoutEx: TMemoryStream;
fStdError: TMemoryStream; fStderrEx: TMemoryStream;
fTerminateChecker: TTimer; fTerminateChecker: TTimer;
fDoneTerminated: boolean; fDoneTerminated: boolean;
fHasRead: boolean; fHasRead: boolean;
@ -49,12 +53,14 @@ type
constructor create(aOwner: TComponent); override; constructor create(aOwner: TComponent); override;
destructor destroy; override; destructor destroy; override;
procedure execute; override; procedure execute; override;
// reads TProcess.OUtput in OutputStack // reads TProcess.Output and StdErr in their "Ex" versions.
procedure fillOutputStack; procedure fillOutputStack;
// fills list with the full lines contained in OutputStack // fills list with the full lines contained in StdoutEx
procedure getFullLines(list: TStrings; consume: boolean = true); procedure getFullLines(list: TStrings; consume: boolean = true);
// access to a flexible copy of TProcess.Output // access to a flexible copy of TProcess.Output
property OutputStack: TMemoryStream read fOutputStack; property StdoutEx: TMemoryStream read fStdoutEx;
// access to a flexible copy of TProcess.Error
property StdErrEx: TMemoryStream read fStderrEx;
// indicates if an output buffer is read // indicates if an output buffer is read
property hasRead: boolean read fHasRead; property hasRead: boolean read fHasRead;
end; end;
@ -160,13 +166,12 @@ end;
constructor TDexedProcess.create(aOwner: TComponent); constructor TDexedProcess.create(aOwner: TComponent);
begin begin
inherited; inherited;
FOutputStack := TMemoryStream.Create; fStdoutEx := TMemoryStream.Create;
fStdError := TMemoryStream.Create; fStderrEx := TMemoryStream.Create;
FTerminateChecker := TTimer.Create(nil); FTerminateChecker := TTimer.Create(nil);
FTerminateChecker.Interval := 50; FTerminateChecker.Interval := 50;
fTerminateChecker.OnTimer := @checkTerminated; fTerminateChecker.OnTimer := @checkTerminated;
fTerminateChecker.Enabled := false; fTerminateChecker.Enabled := false;
//fTerminateChecker.AutoEnabled:= true;
TAsyncProcess(self).OnTerminate := @internalDoOnTerminate; TAsyncProcess(self).OnTerminate := @internalDoOnTerminate;
TAsyncProcess(self).OnReadData := @internalDoOnReadData; TAsyncProcess(self).OnReadData := @internalDoOnReadData;
end; end;
@ -174,17 +179,20 @@ end;
destructor TDexedProcess.destroy; destructor TDexedProcess.destroy;
begin begin
FTerminateChecker.Free; FTerminateChecker.Free;
FOutputStack.Free; fStdoutEx.Free;
fStdError.Free; fStderrEx.Free;
inherited; inherited;
end; end;
procedure TDexedProcess.Execute; procedure TDexedProcess.Execute;
begin begin
fHasRead := false; fHasRead := false;
fOutputStack.Clear; fStdoutEx.Clear;
fStdError.Clear; fStderrEx.Clear;
fDoneTerminated := false; fDoneTerminated := false;
fErrToOut := poStderrToOutPut in Options;
if fErrToOut then
Options := Options - [poStderrToOutPut];
TAsyncProcess(self).OnReadData := @internalDoOnReadData; TAsyncProcess(self).OnReadData := @internalDoOnReadData;
TAsyncProcess(self).OnTerminate := @internalDoOnTerminate; TAsyncProcess(self).OnTerminate := @internalDoOnTerminate;
fTerminateChecker.Enabled := true; fTerminateChecker.Enabled := true;
@ -192,21 +200,28 @@ begin
end; end;
procedure TDexedProcess.fillOutputStack; procedure TDexedProcess.fillOutputStack;
var
sum, cnt: Integer; procedure fill(inStr: TInputPipeStream; outStr: TMemoryStream);
var
s: integer;
c: integer;
begin
s := outStr.size;
while (inStr <> nil) and (inStr.NumBytesAvailable > 0) do
begin
outStr.SetSize(s + 1024);
c := inStr.Read((outStr.Memory + s)^, 1024);
s += c;
end;
outStr.SetSize(s);
end;
begin begin
if not (poUsePipes in Options) then if not (poUsePipes in Options) then
exit; exit;
// output fill(Output, StdoutEx);
sum := fOutputStack.Size; fill(Stderr, stderrEx);
while (Output <> nil) and (NumBytesAvailable > 0) do
begin
fOutputStack.SetSize(sum + 1024);
cnt := Output.Read((fOutputStack.Memory + sum)^, 1024);
sum += cnt;
end;
fOutputStack.SetSize(sum);
end; end;
procedure TDexedProcess.getFullLines(list: TStrings; consume: boolean = true); procedure TDexedProcess.getFullLines(list: TStrings; consume: boolean = true);
@ -219,23 +234,33 @@ var
begin begin
if not Running then if not Running then
begin begin
list.LoadFromStream(fOutputStack); // stderr has been read in its own stream
// preventing interleaving, now put it at the end...
if (poStderrToOutPut in Options) or fErrToOut then
begin
fStdoutEx.Position:=fStdoutEx.Size;
fStdoutEx.Write(fStderrEx.Memory^, fStderrEx.Size);
if consume then if consume then
fOutputStack.Clear; fStderrEx.Clear;
end;
fStdoutEx.Position:=0;
list.LoadFromStream(fStdoutEx);
if consume then
fStdoutEx.Clear;
end else end else
begin begin
lastTerm := fOutputStack.Position; lastTerm := fStdoutEx.Position;
stored := fOutputStack.Position; stored := fStdoutEx.Position;
while fOutputStack.Read(buff, 1) = 1 do while fStdoutEx.Read(buff, 1) = 1 do
if buff = 10 then lastTerm := fOutputStack.Position; if buff = 10 then lastTerm := fStdoutEx.Position;
fOutputStack.Position := stored; fStdoutEx.Position := stored;
if lastTerm <> stored then if lastTerm <> stored then
begin begin
str := TMemoryStream.Create; str := TMemoryStream.Create;
try try
toread := lastTerm - stored; toread := lastTerm - stored;
str.SetSize(toRead); str.SetSize(toRead);
fOutputStack.Read(str.Memory^, toread); fStdoutEx.Read(str.Memory^, toread);
list.LoadFromStream(str); list.LoadFromStream(str);
finally finally
str.Free; str.Free;
@ -271,6 +296,11 @@ begin
if fDoneTerminated then exit; if fDoneTerminated then exit;
fDoneTerminated := true; fDoneTerminated := true;
// restore if same proc is called again,
// self.execute will exclude the option.
if fErrToOut then
Options := Options + [poStderrToOutPut];
// note: made to fix a leak in the process used by the linter // note: made to fix a leak in the process used by the linter
// onTerminate is sometimes determined by an internal timer // onTerminate is sometimes determined by an internal timer
// and not the base method of TAsyncProcess (which usually unhooks) // and not the base method of TAsyncProcess (which usually unhooks)
@ -310,19 +340,19 @@ end;
procedure TAutoBufferedProcess.execute; procedure TAutoBufferedProcess.execute;
begin begin
fPreviousSize := fOutputStack.Size; fPreviousSize := fStdoutEx.Size;
fNewBufferChecker.Enabled:=true; fNewBufferChecker.Enabled:=true;
inherited; inherited;
end; end;
procedure TAutoBufferedProcess.newBufferCheckerChecks(sender: TObject); procedure TAutoBufferedProcess.newBufferCheckerChecks(sender: TObject);
begin begin
if fOutputStack.Size = fPreviousSize then if fStdoutEx.Size = fPreviousSize then
begin begin
if assigned(fRealOnReadData) then if assigned(fRealOnReadData) then
fRealOnReadData(self); fRealOnReadData(self);
end; end;
fPreviousSize := fOutputStack.Size; fPreviousSize := fStdoutEx.Size;
end; end;
procedure TAutoBufferedProcess.internalDoOnReadData(sender: TObject); procedure TAutoBufferedProcess.internalDoOnReadData(sender: TObject);

View File

@ -861,10 +861,10 @@ begin
fToolProc.OnTerminate := nil; fToolProc.OnTerminate := nil;
fToolProc.OnReadData := nil; fToolProc.OnReadData := nil;
fToolProc.OutputStack.Position:=0; fToolProc.StdoutEx.Position:=0;
if fToolProc.OutputStack.Size = 0 then if fToolProc.StdoutEx.Size = 0 then
exit; exit;
fSyms.LoadFromTool(fToolProc.OutputStack); fSyms.LoadFromTool(fToolProc.StdoutEx);
flt := TreeFilterEdit1.Filter; flt := TreeFilterEdit1.Filter;
TreeFilterEdit1.Text := ''; TreeFilterEdit1.Text := '';

View File

@ -488,8 +488,8 @@ end;
procedure TTodoListWidget.toolTerminated(Sender: TObject); procedure TTodoListWidget.toolTerminated(Sender: TObject);
begin begin
fToolProc.OutputStack.Position := 0; fToolProc.StdoutEx.Position := 0;
fTodos.loadFromTxtStream(fToolProc.OutputStack); fTodos.loadFromTxtStream(fToolProc.StdoutEx);
fillTodoList; fillTodoList;
fToolProc.OnTerminate := nil; fToolProc.OnTerminate := nil;
end; end;

View File

@ -235,9 +235,9 @@ begin
if previous.isNotNil and previous.outputToNext if previous.isNotNil and previous.outputToNext
and (poUsePipes in previous.Options) and (poUsePipes in Options) then and (poUsePipes in previous.Options) and (poUsePipes in Options) then
begin begin
setLength(inp, previous.process.OutputStack.Size); setLength(inp, previous.process.StdoutEx.Size);
previous.process.OutputStack.Position:=0; previous.process.StdoutEx.Position:=0;
previous.process.OutputStack.Read(inp[1], inp.length); previous.process.StdoutEx.Read(inp[1], inp.length);
fProcess.Input.Write(inp[1], inp.length); fProcess.Input.Write(inp[1], inp.length);
fProcess.CloseInput; fProcess.CloseInput;
end; end;