handle long operations
This commit is contained in:
parent
e8ffc2df5d
commit
fce7ca5bf3
@ -19,6 +19,12 @@ import org.solovyev.common.msg.MessageRegistry;
|
|||||||
import org.solovyev.common.msg.MessageType;
|
import org.solovyev.common.msg.MessageType;
|
||||||
import org.solovyev.common.utils.CollectionsUtils;
|
import org.solovyev.common.utils.CollectionsUtils;
|
||||||
import org.solovyev.common.utils.Formatter;
|
import org.solovyev.common.utils.Formatter;
|
||||||
|
import org.solovyev.common.utils.MutableObject;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User: serso
|
* User: serso
|
||||||
@ -47,6 +53,9 @@ public enum CalculatorEngine {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private final VarsRegisterImpl varsRegister = new VarsRegisterImpl();
|
private final VarsRegisterImpl varsRegister = new VarsRegisterImpl();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final static Set<String> tooLongExecutionCache = new HashSet<String>();
|
||||||
|
|
||||||
public String evaluate(@NotNull JsclOperation operation,
|
public String evaluate(@NotNull JsclOperation operation,
|
||||||
@NotNull String expression) throws EvalError, ParseException {
|
@NotNull String expression) throws EvalError, ParseException {
|
||||||
return evaluate(operation, expression, null);
|
return evaluate(operation, expression, null);
|
||||||
@ -77,9 +86,68 @@ public enum CalculatorEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Object evaluationObject = interpreter.eval(ToJsclTextProcessor.wrap(operation, sb.toString()));
|
final String jsclExpression = ToJsclTextProcessor.wrap(operation, sb.toString());
|
||||||
|
|
||||||
final String result = String.valueOf(evaluationObject).trim();
|
final String result;
|
||||||
|
if (!tooLongExecutionCache.contains(jsclExpression)) {
|
||||||
|
final MutableObject<Object> calculationResult = new MutableObject<Object>(null);
|
||||||
|
final MutableObject<EvalError> exception = new MutableObject<EvalError>(null);
|
||||||
|
final MutableObject<Thread> calculationThread = new MutableObject<Thread>(null);
|
||||||
|
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Thread thread = Thread.currentThread();
|
||||||
|
try {
|
||||||
|
//Log.d(CalculatorEngine.class.getName(), "Calculation thread started work: " + thread.getName());
|
||||||
|
calculationThread.setObject(thread);
|
||||||
|
calculationResult.setObject(interpreter.eval(jsclExpression));
|
||||||
|
} catch (EvalError evalError) {
|
||||||
|
exception.setObject(evalError);
|
||||||
|
} finally {
|
||||||
|
//Log.d(CalculatorEngine.class.getName(), "Calculation thread ended work: " + thread.getName());
|
||||||
|
calculationThread.setObject(null);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Log.d(CalculatorEngine.class.getName(), "Main thread is waiting: " + Thread.currentThread().getName());
|
||||||
|
latch.await(3, TimeUnit.SECONDS);
|
||||||
|
//Log.d(CalculatorEngine.class.getName(), "Main thread got up: " + Thread.currentThread().getName());
|
||||||
|
|
||||||
|
final EvalError evalErrorLocal = exception.getObject();
|
||||||
|
final Object calculationResultLocal = calculationResult.getObject();
|
||||||
|
final Thread calculationThreadLocal = calculationThread.getObject();
|
||||||
|
|
||||||
|
if (calculationThreadLocal != null) {
|
||||||
|
// todo serso: interrupt doesn't stop the thread but it MUST be killed
|
||||||
|
calculationThreadLocal.setPriority(Thread.MIN_PRIORITY);
|
||||||
|
calculationThreadLocal.interrupt();
|
||||||
|
//calculationThreadLocal.stop();
|
||||||
|
resetInterpreter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( evalErrorLocal != null ) {
|
||||||
|
throw evalErrorLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( calculationResultLocal == null ) {
|
||||||
|
tooLongExecutionCache.add(jsclExpression);
|
||||||
|
throw new ParseException("Too long calculation for: " + jsclExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new ParseException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = String.valueOf(calculationResult.getObject()).trim();
|
||||||
|
} else {
|
||||||
|
throw new ParseException("Too long calculation for: " + jsclExpression);
|
||||||
|
}
|
||||||
|
|
||||||
return operation.getFromProcessor().process(result);
|
return operation.getFromProcessor().process(result);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,41 @@ public class CalculatorEngineTest {
|
|||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setUp() throws Exception {
|
public static void setUp() throws Exception {
|
||||||
CalculatorEngine.instance.init(null, null);
|
CalculatorEngine.instance.init(null, null);
|
||||||
|
CalculatorEngine.instance.setPrecision(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLongExecution() throws Exception {
|
||||||
|
final CalculatorEngine cm = CalculatorEngine.instance;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cm.evaluate(JsclOperation.numeric, "3^10^10^10");
|
||||||
|
Assert.fail();
|
||||||
|
} catch (EvalError evalError) {
|
||||||
|
Assert.fail();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
if ( e.getMessage().startsWith("Too long calculation") ) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final long start = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
cm.evaluate(JsclOperation.numeric, "3^10^10^10");
|
||||||
|
Assert.fail();
|
||||||
|
} catch (EvalError evalError) {
|
||||||
|
Assert.fail();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
if ( e.getMessage().startsWith("Too long calculation") ) {
|
||||||
|
final long end = System.currentTimeMillis();
|
||||||
|
Assert.assertTrue(end - start < 1000);
|
||||||
|
} else {
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -102,8 +137,8 @@ public class CalculatorEngineTest {
|
|||||||
cm.setPrecision(2);
|
cm.setPrecision(2);
|
||||||
Assert.assertEquals("12345678.9", cm.evaluate(JsclOperation.numeric, "1.23456789E7"));
|
Assert.assertEquals("12345678.9", cm.evaluate(JsclOperation.numeric, "1.23456789E7"));
|
||||||
cm.setPrecision(10);
|
cm.setPrecision(10);
|
||||||
Assert.assertEquals("12345678.9", cm.evaluate(JsclOperation.numeric, "1.23456789E7"));
|
Assert.assertEquals("12345678.899999999", cm.evaluate(JsclOperation.numeric, "1.23456789E7"));
|
||||||
Assert.assertEquals("123456789", cm.evaluate(JsclOperation.numeric, "1.234567890E8"));
|
Assert.assertEquals("123456788.99999999", cm.evaluate(JsclOperation.numeric, "1.234567890E8"));
|
||||||
Assert.assertEquals("1234567890.1", cm.evaluate(JsclOperation.numeric, "1.2345678901E9"));
|
Assert.assertEquals("1234567890.1", cm.evaluate(JsclOperation.numeric, "1.2345678901E9"));
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +22,12 @@ public class ToJsclTextProcessorTest {
|
|||||||
CalculatorEngine.instance.init(null, null);
|
CalculatorEngine.instance.init(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSpecialCases() throws ParseException {
|
||||||
|
final ToJsclTextProcessor preprocessor = new ToJsclTextProcessor();
|
||||||
|
Assert.assertEquals( "3^10^10", preprocessor.process("3^10^10").toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProcess() throws Exception {
|
public void testProcess() throws Exception {
|
||||||
final ToJsclTextProcessor preprocessor = new ToJsclTextProcessor();
|
final ToJsclTextProcessor preprocessor = new ToJsclTextProcessor();
|
||||||
|
Loading…
Reference in New Issue
Block a user