Handle external simulator output gracefully

On Windows, ngspice doesn't output messages to stdout or stderr, it
starts the gui which causes KiCad to hang until ngspice is terminated.
We want to handle immediate responses but not get hung by the external
process.  Passing a process class to wxExecute allows us to detach the
process (it will terminate and free itself) while still handling the
immediate output and errors

Fixes https://gitlab.com/kicad/code/kicad/-/issues/15446
This commit is contained in:
Seth Hillbrand 2023-11-27 13:18:04 -08:00
parent 1a2f2d418c
commit 4a19bef2ac
1 changed files with 51 additions and 29 deletions

View File

@ -52,6 +52,7 @@
#include <wx/filedlg.h> #include <wx/filedlg.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <wx/regex.h> #include <wx/regex.h>
#include <wx/txtstrm.h>
@ -565,48 +566,69 @@ bool DIALOG_EXPORT_NETLIST::TransferDataFromWindow()
if( !commandLine.IsEmpty() ) if( !commandLine.IsEmpty() )
{ {
wxArrayString output; wxProcess* process = new wxProcess( GetEventHandler(), wxID_ANY );
wxArrayString errors; process->Redirect();
wxExecute( commandLine, output, errors, wxEXEC_ASYNC ); wxExecute( commandLine, wxEXEC_ASYNC, process );
reporter.ReportHead( commandLine, RPT_SEVERITY_ACTION ); reporter.ReportHead( commandLine, RPT_SEVERITY_ACTION );
process->Activate();
if( output.GetCount() ) sleep( 1 ); // give the process time to start and output any data or errors
if( process->IsInputAvailable() )
{ {
for( unsigned ii = 0; ii < output.GetCount(); ii++ ) wxInputStream* in = process->GetInputStream();
reporter.Report( output[ii], RPT_SEVERITY_INFO ); wxTextInputStream textstream( *in );
while( in->CanRead() )
{
wxString line = textstream.ReadLine();
if( !line.IsEmpty() )
reporter.Report( line, RPT_SEVERITY_INFO );
}
} }
if( errors.GetCount() ) if( process->IsErrorAvailable() )
{ {
for( unsigned ii = 0; ii < errors.GetCount(); ii++ ) wxInputStream* err = process->GetErrorStream();
{ wxTextInputStream textstream( *err );
// wxExecute returns -1 for all error conditions, so we've no choice but
// to scrape the stderr messages for the error code(s).
if( errors[ii].EndsWith( wxS( "failed with error 2!" ) ) ) // ENOENT while( err->CanRead() )
{
wxString line = textstream.ReadLine();
if( !line.IsEmpty() )
{
if( line.EndsWith( wxS( "failed with error 2!" ) ) ) // ENOENT
{ {
reporter.Report( _( "external simulator not found" ), RPT_SEVERITY_ERROR ); reporter.Report( _( "external simulator not found" ), RPT_SEVERITY_ERROR );
reporter.Report( _( "Note: command line is usually: " reporter.Report( _( "Note: command line is usually: "
"<tt>&lt;path to SPICE binary&gt; \"%I\"</tt>" ), "<tt>&lt;path to SPICE binary&gt; \"%I\"</tt>" ),
RPT_SEVERITY_INFO ); RPT_SEVERITY_INFO );
} }
else if( errors[ii].EndsWith( wxS( "failed with error 8!" ) ) ) // ENOEXEC else if( line.EndsWith( wxS( "failed with error 8!" ) ) ) // ENOEXEC
{ {
reporter.Report( _( "external simulator has the wrong format or " reporter.Report( _( "external simulator has the wrong format or "
"architecture" ), RPT_SEVERITY_ERROR ); "architecture" ), RPT_SEVERITY_ERROR );
} }
else if( errors[ii].EndsWith( "failed with error 13!" ) ) // EACCES else if( line.EndsWith( "failed with error 13!" ) ) // EACCES
{ {
reporter.Report( _( "permission denied" ), RPT_SEVERITY_ERROR ); reporter.Report( _( "permission denied" ), RPT_SEVERITY_ERROR );
} }
else else
{ {
reporter.Report( errors[ii], RPT_SEVERITY_ERROR ); reporter.Report( line, RPT_SEVERITY_ERROR );
} }
} }
} }
} }
process->CloseOutput();
process->Detach();
// Do not delete process, it will delete itself when it terminates
}
} }
WriteCurrentNetlistSetup(); WriteCurrentNetlistSetup();