ltlcross: recover from out-of-memory during state-space product

Fixes #96, reported by Vitus Lam & Christopher Ziegler.

* src/bin/ltlcross.cc: Catch std::bad_alloc, skip those
products and the related tests.  Display a count of
those skipped tests at the end.
* NEWS: Mention it.
* src/tests/ltlcross3.test: Adjust expected error
message.
This commit is contained in:
Alexandre Duret-Lutz 2015-08-24 00:23:51 +02:00
parent 63969b13fa
commit 0510e4dfe8
3 changed files with 125 additions and 77 deletions

2
NEWS
View file

@ -74,6 +74,8 @@ New in spot 1.99.2a (not yet released)
- the spot.translate() function of the Python binding had a typo - the spot.translate() function of the Python binding had a typo
preventing the use of 'low'/'medium'/'high' as argument. preventing the use of 'low'/'medium'/'high' as argument.
- Fix spurious failure of uniq.test under different locales. - Fix spurious failure of uniq.test under different locales.
- ltlcross now recovers from out-of-memory errors during
state-space products.
New in spot 1.99.2 (2015-07-18) New in spot 1.99.2 (2015-07-18)

View file

@ -27,6 +27,7 @@
#include <cstdio> #include <cstdio>
#include <argp.h> #include <argp.h>
#include <unistd.h> #include <unistd.h>
#include <cmath>
#include <sys/wait.h> #include <sys/wait.h>
#include "error.h" #include "error.h"
#include "argmatch.h" #include "argmatch.h"
@ -208,6 +209,7 @@ static unsigned ignored_exec_fail = 0;
static bool opt_automata = false; static bool opt_automata = false;
static bool global_error_flag = false; static bool global_error_flag = false;
static unsigned oom_count = 0U;
static std::ostream& static std::ostream&
global_error() global_error()
@ -756,13 +758,14 @@ namespace
std::cerr << "info: cross_check {"; std::cerr << "info: cross_check {";
bool first = true; bool first = true;
for (size_t i = 0; i < m; ++i) for (size_t i = 0; i < m; ++i)
{ if (maps[i])
if (first) {
first = false; if (first)
else first = false;
std::cerr << ','; else
std::cerr << l << i; std::cerr << ',';
} std::cerr << l << i;
}
std::cerr << "}, state-space #" << p << '/' << products << '\n'; std::cerr << "}, state-space #" << p << '/' << products << '\n';
} }
@ -958,6 +961,31 @@ namespace
return 0; return 0;
} }
void product_stats(statistics_formula* stats, unsigned i,
spot::scc_info* sm)
{
if (verbose && sm)
std::cerr << "info: " << sm->scc_count()
<< " SCCs\n";
// Statistics
if (want_stats)
{
if (sm)
{
(*stats)[i].product_scc.push_back(sm->scc_count());
spot::tgba_statistics s = spot::stats_reachable(sm->get_aut());
(*stats)[i].product_states.push_back(s.states);
(*stats)[i].product_transitions.push_back(s.transitions);
}
else
{
double n = nan("");
(*stats)[i].product_scc.push_back(n);
(*stats)[i].product_states.push_back(n);
(*stats)[i].product_transitions.push_back(n);
}
}
}
int int
process_formula(const spot::ltl::formula* f, process_formula(const spot::ltl::formula* f,
@ -1209,10 +1237,6 @@ namespace
<< statespace->num_edges() << statespace->num_edges()
<< " edges\n"; << " edges\n";
// Products of the state space with the positive automata.
std::vector<spot::const_twa_graph_ptr> pos_prod(m);
// Products of the state space with the negative automata.
std::vector<spot::const_twa_graph_ptr> neg_prod(m);
// Associated SCC maps. // Associated SCC maps.
std::vector<spot::scc_info*> pos_map(m); std::vector<spot::scc_info*> pos_map(m);
std::vector<spot::scc_info*> neg_map(m); std::vector<spot::scc_info*> neg_map(m);
@ -1224,27 +1248,28 @@ namespace
" P") << i " P") << i
<< " (" << pos[i]->num_states() << " st., " << " (" << pos[i]->num_states() << " st., "
<< pos[i]->num_edges() << " ed.)\n"; << pos[i]->num_edges() << " ed.)\n";
auto p = spot::product(pos[i], statespace);
pos_prod[i] = p;
if (verbose)
std::cerr << "info: product has " << p->num_states()
<< " st., " << p->num_edges()
<< " ed.\n";
auto sm = new spot::scc_info(p); spot::scc_info* sm = nullptr;
pos_map[i] = sm; try
if (verbose)
std::cerr << "info: " << sm->scc_count()
<< " SCCs\n";
// Statistics
if (want_stats)
{ {
(*pstats)[i].product_scc.push_back(sm->scc_count()); auto p = spot::product(pos[i], statespace);
spot::tgba_statistics s = spot::stats_reachable(p); if (verbose)
(*pstats)[i].product_states.push_back(s.states); std::cerr << "info: product has " << p->num_states()
(*pstats)[i].product_transitions.push_back(s.transitions); << " st., " << p->num_edges()
<< " ed.\n";
sm = new spot::scc_info(p);
} }
catch (std::bad_alloc&)
{
std::cerr << ("warning: not enough memory to build "
"product of P") << i << " with state-space";
if (products > 1)
std::cerr << " #" << p << '/' << products << '\n';
std::cerr << '\n';
++oom_count;
}
pos_map[i] = sm;
product_stats(pstats, i, sm);
} }
if (!no_checks) if (!no_checks)
@ -1257,26 +1282,28 @@ namespace
<< " (" << neg[i]->num_states() << " st., " << " (" << neg[i]->num_states() << " st., "
<< neg[i]->num_edges() << " ed.)\n"; << neg[i]->num_edges() << " ed.)\n";
auto p = spot::product(neg[i], statespace); spot::scc_info* sm = nullptr;
neg_prod[i] = p; try
if (verbose)
std::cerr << "info: product has " << p->num_states()
<< " st., " << p->num_edges()
<< " ed.\n";
auto sm = new spot::scc_info(p);
neg_map[i] = sm;
if (verbose)
std::cerr << "info: " << sm->scc_count()
<< " SCCs\n";
// Statistics
if (want_stats)
{ {
(*nstats)[i].product_scc.push_back(sm->scc_count()); auto p = spot::product(neg[i], statespace);
spot::tgba_statistics s = spot::stats_reachable(p); if (verbose)
(*nstats)[i].product_states.push_back(s.states); std::cerr << "info: product has " << p->num_states()
(*nstats)[i].product_transitions.push_back(s.transitions); << " st., " << p->num_edges()
<< " ed.\n";
sm = new spot::scc_info(p);
} }
catch (std::bad_alloc&)
{
std::cerr << ("warning: not enough memory to build "
"product of N") << i << " with state-space";
if (products > 1)
std::cerr << " #" << p << '/' << products << '\n';
std::cerr << '\n';
++oom_count;
}
neg_map[i] = sm;
product_stats(nstats, i, sm);
} }
if (!no_checks) if (!no_checks)
@ -1456,42 +1483,58 @@ main(int argc, char** argv)
" please search for 'error:' messages in the above" " please search for 'error:' messages in the above"
" trace."); " trace.");
err << std::endl; err << std::endl;
if (timeout_count > 0 || ignored_exec_fail > 0)
{
err << "Additionally, ";
if (timeout_count == 1)
err << "1 timeout occurred";
else if (timeout_count > 1)
err << timeout_count << " timeouts occurred";
if (timeout_count > 0 && ignored_exec_fail > 0)
err << " and ";
if (ignored_exec_fail == 1)
err << "1 non-zero exit status was ignored";
if (ignored_exec_fail > 1)
err << ignored_exec_fail
<< " non-zero exit statuses were ignored";
err << '.' << std::endl;
}
end_error(); end_error();
} }
else if (timeout_count == 0 && ignored_exec_fail == 0) else if (timeout_count == 0 && ignored_exec_fail == 0 && oom_count == 0)
{ {
std::cerr << "No problem detected." << std::endl; std::cerr << "No problem detected." << std::endl;
} }
else else
{ {
if (timeout_count == 1) std::cerr << "No major problem detected." << std::endl;
std::cerr << "1 timeout"; }
if (timeout_count > 1)
std::cerr << timeout_count << " timeouts"; unsigned additional_errors = 0U;
if (timeout_count > 0 && ignored_exec_fail > 0) additional_errors += timeout_count > 0;
std::cerr << " and "; additional_errors += ignored_exec_fail > 0;
if (ignored_exec_fail == 1) additional_errors += oom_count > 0;
std::cerr << "1 non-zero exit status"; if (additional_errors)
if (ignored_exec_fail > 1) {
std::cerr << ignored_exec_fail std::cerr << (global_error_flag ? "Additionally, " : "However, ");
<< " non-zero exit statuses"; if (timeout_count)
std::cerr << ", but no other problem detected." << std::endl; {
if (additional_errors > 1)
std::cerr << "\n - ";
if (timeout_count == 1)
std::cerr << "1 timeout occurred";
else
std::cerr << timeout_count << " timeouts occurred";
}
if (oom_count)
{
if (additional_errors > 1)
std::cerr << "\n - ";
if (oom_count == 1)
std::cerr << "1 state-space product was";
else
std::cerr << oom_count << "state-space products were";
std::cerr << " skipped by lack of memory";
}
if (ignored_exec_fail)
{
if (additional_errors > 1)
std::cerr << "\n - ";
if (ignored_exec_fail == 1)
std::cerr << "1 non-zero exit status was ignored";
else
std::cerr << ignored_exec_fail
<< " non-zero exit statuses were ignored";
}
if (additional_errors == 1)
std::cerr << '.';
std::cerr << std::endl;
} }
} }

View file

@ -74,7 +74,10 @@ test `grep 'warning:.*timeout' stderr | wc -l` -eq 2
test `grep 'warning:.*exit code 1' stderr | wc -l` -eq 2 test `grep 'warning:.*exit code 1' stderr | wc -l` -eq 2
test `grep '"timeout",-1' out.csv | wc -l` -eq 2 test `grep '"timeout",-1' out.csv | wc -l` -eq 2
test `grep '"exit code",1' out.csv | wc -l` -eq 2 test `grep '"exit code",1' out.csv | wc -l` -eq 2
grep '2 timeouts and 2 non-zero exit statuses, but no other problem' stderr
grep 'No major problem detected' stderr
grep '2 timeouts occurred' stderr
grep '2 non-zero exit statuses were ignored' stderr
check_csv out.csv check_csv out.csv
# 'bug' should exist but be empty # 'bug' should exist but be empty
test -f bug test -f bug