Comments (4)
Isn't that too much for users?
Should it be just automatically thread-safe without doing anything from usage side?
If users want to switch from Thread to whatever we don't know, ex. Kotlin coroutines, they will need to find a way to make it work.
If the lib support immutability approach, we don't need to care how users use the lib api. It will automatically be safe with any parallel programming frameworks (Java Thread, coroutines or whatever else we don't care).
from evalex.
In fact, an expression is thread-safe, as soon as the expression was parsed.
But the standard MapBasedDataAccessor
which holds the variable values is not thread-safe.
There are some possibilities to achieve thread safety here. I did a small test with a custom data accessor that is using ThreadLocal
to store the data along with the thread:
package com.ezylang.evalex;
import com.ezylang.evalex.config.ExpressionConfiguration;
import com.ezylang.evalex.data.DataAccessorIfc;
import com.ezylang.evalex.data.EvaluationValue;
import com.ezylang.evalex.data.MapBasedDataAccessor;
import com.ezylang.evalex.parser.ParseException;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.security.SecureRandom;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
public class ThreadLocalTest {
class ThreadLocalDataAccess implements DataAccessorIfc {
ThreadLocal<MapBasedDataAccessor> threadLocal =
ThreadLocal.withInitial(MapBasedDataAccessor::new);
@Override
public EvaluationValue getData(String variable) {
return threadLocal.get().getData(variable);
}
@Override
public void setData(String variable, EvaluationValue value) {
threadLocal.get().setData(variable, value);
}
}
@Test
void testThreadLocal() throws ParseException, InterruptedException {
ExpressionConfiguration configuration =
ExpressionConfiguration.builder().dataAccessorSupplier(ThreadLocalDataAccess::new).build();
Expression expression = new Expression("a+b", configuration);
// validate makes sure the expression is parsed before entering the multi-threaded code
expression.validate();
SecureRandom random = new SecureRandom();
// start 100 threads
ExecutorService es = Executors.newCachedThreadPool();
for (int t = 0; t < 100; t++) {
es.execute(
() -> {
try {
System.out.println("Starting with " + Thread.currentThread().getName());
for (int i = 0; i < 10000; i++) {
BigDecimal a = new BigDecimal(random.nextInt());
BigDecimal b = new BigDecimal(random.nextInt());
EvaluationValue result = expression.with("a", a).and("b", b).evaluate();
BigDecimal sum = a.add(b);
assertThat(result.getStringValue()).isEqualTo(sum.toPlainString());
}
System.out.println("Done with " + Thread.currentThread().getName());
} catch (EvaluationException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new RuntimeException(e);
}
});
}
es.awaitTermination(10, TimeUnit.SECONDS);
}
}
To test, I started 100 threads and each one is doing 10,000 calculations with random values.
If you do not use the ThreadLocalDataAccess
in configuration, there are a lot of errors, because all threads try to read and write to the same map.
When using ThreadLocalDataAccess
, then there are no errors because each thread has now its own map of data.
from evalex.
EvalEx is a library for use by programmers. So, the user is somebody with programming skills. And as you point out, the environment where and how the library is used may differ. And the library user should be able to adopt to different scenarios.
The above thread safe solution might get a part of a future release, so that it can be configured without the need of an own implementation.
Immutability can only be an optional configuration, the current default flexibility allows for e.g. operators and functions with side effects. This was intentional.
But I will think of a more general immutable evaluation variant, shouldn't be too hard.
from evalex.
EvalEx is a library for use by programmers. So, the user is somebody with programming skills. And as you point out, the environment where and how the library is used may differ. And the library user should be able to adopt to different scenarios.
I don't totally agree with this. IMO, user has skill, doesn't mean s/he want to manage extra work for each environment. User would be happier if the internal of lib would produce the same thing regardless of where it is running.
Immutability can only be an optional configuration, the current default flexibility allows for e.g. operators and functions with side effects. This was intentional.
You don't need to have an "optional configuration". As I mentioned in previous comment, it's possible to create a snapshot from existing expression. The "snapshot" could reuse expensive computation result of the Expression instance as the starting point plus the provided variables to create the immutable state. The evaluation will be done with the immutable state of the snapshot. So, developers could simply pass around the snapshot to anywhere and it will just work without worrying about thread safe.
This is the powerful of immutability.
from evalex.
Related Issues (20)
- Incorrect token string that causes parsing error
- Negative power returns incorrect result HOT 1
- 2ab(a+b) => ParseException Undefined function 'ab' HOT 7
- ShuntingYardConverter silently drops tokens HOT 10
- Remove variable value HOT 1
- Evaluation throws NPE when a structure value is not defined
- Caching of parsed evaluation and subtree HOT 2
- Fraction calculation, is that possible?
- BUG: Undefined operator '.' ParseException when letter 'e' is after dot '.' HOT 2
- Evaluation of asin(1.5) will throw NumberFormatException which is a bit misleading HOT 1
- Structure access with arbitrary key format HOT 5
- OutOfMemoryError/memory leak on `e.<number>` input HOT 1
- Consider some wildcards for `Map<String, Object> values` HOT 2
- Unsupported data type 'java.time.LocalDate' HOT 1
- Bundle Proguard Rules HOT 3
- How to restrict list of operators used in formula evaluation? HOT 1
- Custom ! post fix operator is not called HOT 1
- decimalPlacesRounding has confusing behavior HOT 5
- NoSuchElementException not being caught HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from evalex.