-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace finalizers with PhantomReferences in Java API #648
Conversation
A lot of existing code in Java bindings catches exceptions just to silence them later. This is: a) Unnecessary: it is OK for a function to throw a RuntimeException without declaring it. b) Highly unidiomatic and not recommended by Java experts (see Effective Java and others) c) Confusing as has the potential to hide the existing bugs and have them resurface at the most inconvenient/unexpected moment.
Replacing finalizers with PhantomReferences, required quite a lot of changes to the codebase.
...as it does not use any fields of the outer FuncInterp object.
One method should do one thing only, it's easy to mix things up otherwise.
Cool,
|
/** | ||
* An implementation of this method should decrement the reference on a | ||
* given native object. | ||
* This function should be always called on the {@code ctx} thread. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
be always -> always be
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed.
No problem if multiple things are adressed in one PR; the main issue from my point of view is that we don't break old applications too severly. It seems you mostly tried to achieve that though. I'll run our Java example through it, that one also runs some simple tests involving goals and tactics. Regarding the lock on context creation, I think I added that because at one point there was a concurrency bug in the Context creation/initialization, but that may (and should have been) fixed by now. Either way, I don't expect many applications will need thousands of Contexts per second, so performance shouldn't be critical there. Generally I'm happy with style fixes. None of us is a full-time (or even part-time) Java programmer and we don't have the resources to become one either. At the current stage, we can think about marking some old bad-style things as deprecated (to not break existing applications) and to introduce new concepts that make more sense to Java programmers. Ideally I would like to get a pro-Java hacker to take over the official maintenance of the Java API, so if you or someone in your sphere of influence is keen on giving this a try let us know! |
BTW, does anybody have a good test for concurrency issues in the Java API? Does the JavaSMT test suite contain some stress tests for that? |
So, at the moment, our JavaExample doesn't compile because:
Is that a required/recommend style change? If it's easy I'd rather not break this one. Also:
I think there's no downside whatsoever to keeping the HashMap constructor next to the Map constructor, could you add that back? |
I would also be inclined to add back Context as an accessible constructor as it isn't a factory creating contexts. |
@NikolajBjorner @wintersteiger I've changed this back. In general, I would say that indeed in Java static methods are mostly preferred over constructors, as they can be given names, and don't contain arbitrary restrictions on chaining (super() has to be the first call in a method, etc). IIRC the reason for the initial change was to make the native field final (which is indeed a very good idea most of the time). In general, I was trying to minimize the number of API changes.
|
@wintersteiger I have relatively few concurrency tests in JavaSMT, but I most probably will add more soon. Before that I should try to figure out how to communicate the interrupt leading to segfault issue. |
|
@NikolajBjorner I've started concurrency testing with hundred different threads. There seems to be two issues:
|
Somewhat unhelpful log is available at metaworld.me/concurrent_z3_crash.log |
@cheshire: Can you share the test program with us? |
Regarding the pull request, though, seems ready to integrate. |
@wintersteiger yes, it basically creates a random formula, checks it for satisfiability, modifies by traversal, and checks again, doing that in parallel on 100 different contexts. private static class ConcurrencyTester implements Callable<Boolean> {
private final int threadNo;
private final SolverContext runnableContext;
private ConcurrencyTester(int pThreadNo, SolverContext pRunnableContext) {
threadNo = pThreadNo;
runnableContext = pRunnableContext;
}
@Override
public Boolean call() {
try {
FormulaManager rMgr = runnableContext.getFormulaManager();
Fuzzer f = new Fuzzer(rMgr, new Random(0));
BooleanFormula random = f.fuzz(100, 3);
boolean ans1, ans2;
try (ProverEnvironment e = runnableContext.newProverEnvironment()) {
e.push(random);
ans1 = e.isUnsat();
}
BooleanFormula lowercased = rMgr.transformRecursively(new FormulaTransformationVisitor(rMgr) {
@Override
public Formula visitFreeVariable(
Formula f, String name) {
return rMgr.makeVariable(rMgr.getFormulaType(f), name.toLowerCase());
}
}, random);
try (ProverEnvironment e = runnableContext.newProverEnvironment()) {
e.push(random);
ans2 = e.isUnsat();
}
assertThat(ans1).isEqualTo(ans2);
return ans1;
} catch (SolverException | InterruptedException pE) {
throw new UnsupportedOperationException("fail");
}
}
}
@Test
public void concurrencyTest() throws Exception {
SolverContextFactory localFactory = new SolverContextFactory(config, logger, shutdownNotifierToUse());
int poolSize = 100;
ExecutorService executor = Executors.newFixedThreadPool(poolSize);
List<Callable<Boolean>> callables = new ArrayList<>(poolSize);
for (int i = 0; i < poolSize; i++) {
callables.add(new ConcurrencyTester(i, localFactory.generateContext()));
}
List<Future<Boolean>> out = executor.invokeAll(callables);
executor.shutdown();
executor.awaitTermination(30, TimeUnit.SECONDS);
for (Future<Boolean> future : out) {
assertThat(future.isDone()).isTrue();
}
} |
Right, so what should I do to get this merged? |
We can merge that now, but I expect new bug reports will trickle in shortly after that and I won't be available for any testing for bugfixing for about 2 weeks from now. Will you be available to fix at least the simple issues? |
Yes, I would be available for that in the next two weeks (and in general On Fri, Jun 24, 2016 at 2:39 PM, Christoph M. Wintersteiger <
|
That's fantastic, thanks for making room for this! No hard guarantees needed, but it's good to know somebody will look at it if needed. I'm merging this right now and I'll at least check to see whether the JavaExample produces anything unexpected. |
Super! Thanks a lot for the substantial contribution. |
Hi,
This pull request replaces very inefficient finalizers usage with PhantomReferences, as described in #442.
The new solution relies on ReferenceQueues, and does not require any locking at all.
I have tested this code with our rather extensive JavaSMT test suite, however it does not poke around specific Z3 areas JavaSMT does not cover (e.g. goals, datatype sorts, etc).
This pull request might require a few more iterations:
If absolutely needed I can split it into different pull requests as well, but I think the process would be much easier if we can treat them atomically.
Without fixing some of those style issues it would have been very difficult to write a new solution.