Antlr4 - How to stop the Parser when it takes too long

Viewed 1018

With my grammar, when I do analysis of entries like these:

ZZ9->ZZ9_LINHA  :=  &(_cAlias+"->(CONTAEBTA)"   )+& (_cAlias+"->(REGISTRO)"     )+& (_cAlias+"->(TRANSACAO)"    )+& (_cAlias+"->(REFERENCIA)"   )+;
                    &(_cAlias+"->(REFPAGTO)"    )+& (_cAlias+"->(STATTRANSA)"   )+& (_cAlias+"->(NAOUSADO1)"    )+& (_cAlias+"->(NAOUSADO2)"    )+;
                    &(_cAlias+"->(NUMFATURA)"   )+& (_cAlias+"->(INDDISPUTA)"   )+& (_cAlias+"->(DATFATURAM)"   )+& (_cAlias+"->(DATPARTIDA)"   )+;
                    &(_cAlias+"->(NOMVIAJANT)"  )+& (_cAlias+"->(INIVIAJANT)"   )+& (_cAlias+"->(ROTA)"         )+& (_cAlias+"->(NAOUSADO3)"    )+;
                    &(_cAlias+"->(CENTROCUST)"  )+& (_cAlias+"->(STATUSPGTO)"   )+& (_cAlias+"->(VALORPAGTO)"   )+& (_cAlias+"->(SINALPAGTO)"   )+;
                    &(_cAlias+"->(VALORTRANS)"  )+& (_cAlias+"->(SINALTRANS)"   )+& (_cAlias+"->(NAOUSADO4)"    )+& (_cAlias+"->(NAOUSADO5)"    )+;
                    &(_cAlias+"->(NAOUSADO6)"   )+& (_cAlias+"->(NAOUSADO7)"    )+& (_cAlias+"->(NUMBILHETE)"   )+& (_cAlias+"->(CIAAEREA)"     )+;
                    &(_cAlias+"->(DATEMISSAO)"  )+& (_cAlias+"->(TAXAEMBARQ)"   )+& (_cAlias+"->(SINALTXEMB)"   )+& (_cAlias+"->(VLRBILHET)"    )+;
                    &(_cAlias+"->(SINBILHET)"   )+& (_cAlias+"->(NAOUSADO8)"    )+& (_cAlias+"->(NAOUSADO9)"    )+& (_cAlias+"->(TIPDESPESA)"   )+;
                    &(_cAlias+"->(DATDESPESA)"  )+& (_cAlias+"->(DEPTO)"        )+& (_cAlias+"->(MATRICULA)"    )+& (_cAlias+"->(CODIATA)"      )+;
                    &(_cAlias+"->(NAOUSADOA)"   )+& (_cAlias+"->(REQVIAGEM)"    )+& (_cAlias+"->(NAOUSADOB)"    )+& (_cAlias+"->(CLASSEVOO)"    )

The Parser gets slower and slower, and using more and more memory.

The ANTLR4 stack, if the work thread is paused, is:

Antlr Stack

I can't do a refactory of this grammar right now. So I'm looking for a way to stop execution of the parser by timeout.

I've tried the following, with ExecutorService and Future:

public class MyParserIsolateThread implements Callable<ParseTree> {

    public String ppo;
    public MyParserIsolateThread(String ppoInfo)
    {
        this.ppo = ppoInfo;
    }

    @Override
    public ParseTree call() throws Exception {

        NoCaseANTLRStringStream input = new NoCaseANTLRStringStream(this.ppo);
        MyLexer lexer = new MyLexer(input);
        CommonTokenStream token = new CommonTokenStream(lexer);             
        MyParser parser = new MyParser(token);
        AntlrToSonarErrorListener error = new AntlrToSonarErrorListener(null,null,ppo);         
        parser.addErrorListener(error);
        long startTime = System.currentTimeMillis();        
        System.out.println("Starting parsing...");        
        ParseTree tree = parser.program();        
        long estimatedTime = System.currentTimeMillis() - startTime;
        System.out.println("Parse Finished. Elapsed time:" + estimatedTime);        
        return tree;
    }

}


    MyParserIsolateThread threadParser = new MyParserIsolateThread(ppo);
    ExecutorService executorService = Executors.newFixedThreadPool(1);

    Future<ParseTree> result = executorService.submit(threadParser);
    try {
        ParseTree tree = result.get(60, TimeUnit.SECONDS);
        System.out.println("Parser OK ");
    } catch (Exception e) {
        // interrupts if there is any possible error
        System.out.println("too long parsing...");
        result.cancel(true);

    }       
    executorService.shutdownNow();
    try {
        executorService.awaitTermination(1, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("end.");

If I use an entry that takes more than 60 seconds, the message is displayed correctly, but even using shutdownNow, the work and pool threads continue to run.

So,

Is there any way to stop the .< startRule > ?

I thought of some callback called from time to time to see if we should stop.

I don't want to use a Thread, and use .stop () since it is deprecated and unsafe.

Any idea?

2 Answers

I definitely need to review the grammar, but I can't do that right now.

I followed Mike's idea, I got the expected behavior.

I implemented extending the ParserATNSimulator class:

public class ParserATNSimulatorWithTimeOut extends ParserATNSimulator {

    private Instant start = Instant.now();
    public ParserATNSimulatorWithTimeOut(org.antlr.v4.runtime.Parser parser, ATN atn, DFA[] decisionToDFA,
            PredictionContextCache sharedContextCache) {
        super(parser, atn, decisionToDFA, sharedContextCache);
    }
    @Override
    protected void closure(ATNConfig config,
                           ATNConfigSet configs,
                           Set<ATNConfig> closureBusy,
                           boolean collectPredicates,
                           boolean fullCtx,
                           boolean treatEofAsEpsilon)
    {

        Duration timeElapsed = Duration.between(start, Instant.now());
        if (timeElapsed.toMinutes() >= 1 )
        {
            Exception e = new ParseCancellationException("Too long!!!");
            throw new ParseCancellationException(e);

        }
        super.closure(config, configs, closureBusy,collectPredicates,fullCtx,treatEofAsEpsilon);

    }
}

And in MyParser change it.

public MyParser(TokenStream input) {
        super(input);
        _interp = new ParserATNSimulatorWithTimeOut(this,_ATN,_decisionToDFA,_sharedContextCache);
    }

You could use the same approach as the BailOutErrorStrategy does: in case of an error it throws an exception, which is not catched by the parsing machinery.

Add a listener to your parser before running it. Override the enterEveryRule method and when called check the time the parser ran so far. If the timeout is reached throw your stopping exception, which will immediately stop the parse run. You can catch this exception in your code to see if the timeout was reached.

Related