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