Reputation: 296
I'm looking for something that's similar to implementing the java.lang.AutoCloseable interface, where a compiler warning indicating Resource leak: 'xxxx' is never closed
is generated.
The use case for this is in a wrapper around a Synchronized Collection in java. The wrapper has an internal semaphore to prevent concurrent modification of the collection.
It allows atomic operations on the collection, in which case the semaphore is acquired and released internally. It also allows the lock to be acquired externally, providing a unique key with which operations can be executed on the collection. The key must be released at the end of the "transaction".
My goal is to create a compiler warning when the lock is acquired and not released within the same method, to prevent deadlock. An alternative design solution that would prevent this is also acceptable.
It's kind of a fun little problem, so I appreciate any insight into it.
Upvotes: 2
Views: 600
Reputation: 296
While @Christian Hujer did provide a solid solution, I chose to go another route which has been working out well.
There is a wrapper class "Resource" around the SynchronizedCollection which contains:
The class described above is enough to provide sufficient protection around the collection, but what I wanted was compiler warnings if the lock wasn't released.
To accomplish this, there is a "ResourceManager" which implements java.lang.AutoCloseable
This class:
The resource manager is created wherever multiple operations need to be performed on the Resource and a compiler warning is generated if close() is not called on any particular code path. Additionally, in java 7+, the manager can be created in a try-with-resource block and the lock is automatically released, regardless of what happens in the block.
Upvotes: 0
Reputation: 17945
As you said
An alternative design solution that would prevent this is also acceptable.
So here it is: As an alternative design solution, use Functional Programming.
Instead of finding out about the error, why not prevent the error from happening in the first place?
Lacking your source code, I make a few assumptions about your code:
Semaphore
is your class (or interface) that provides the semaphore to your SynchronizedCollection
.Semaphore
provides two methods obtain()
and release()
.The problem that you're actually facing is a problem of State resp. Change of State which leads to Temporal Coupling. obtain()
and release()
must be called in order. You can use elements from Functional Programming as an alternative design.
The Semaphore
would currently look like this:
public class Sempahore {
// ...
public void obtain() {
// Lock code
}
public void release() {
// Release code
}
}
The Semaphore
user would currently look like this:
semaphore.obtain();
// Code protected by the Sempahore.
semaphore.release();
The solution is to combine obtain()
and release()
into a single function which takes the code to be protected as its argument. This technique is also known as Passing a Block, or more formally as a higher order function - a function that takes another function as an argument or returns another function.
Java also has function pointers, not directly, but indirectly, via references to interfaces. Since Java 8, an interface that has only one abstract method is even called Functional Interface, and Java 8 provides an optional annotation @FunctionalInterface
for that.
So, your class Sempahore
could instead look like this:
public class Semaphore {
// ...
private void obtain() {
// Lock code
}
private void release() {
// Release code
}
public <V> void protect(final Callable<V> c) throws Exception {
obtain();
try {
return c.call();
} finally {
release();
}
}
}
And the caller would look like this, in Java 7 and older:
semaphore.protect(new Callable<Object>() {
public Object call() {
// Code protected by the Semaphore.
}
});
In Java 8 and newer, the code could also look like this:
semaphore.protect(() -> {
// Code protected by the Semaphore.
});
There's one aspect about Java which sucks completely in this context: Exception Handling. With functional programming, there is urgent need to fix that, but Oracle didn't. I'm still hoping for Java 9, but that won't help all that broken API like java.util.stream
that's already out there in the wild. Java 8 still maintains the handle-or-declare-rule of checked exceptions, but functional programming does not take that into account nicely.
There are a few workarounds for that:
Runnable
, if you do not need return values.Callable
interface which declares a type parameter for exceptions.I bet using Runnable
is straight-forward and self-explanatory, therefore I won't elaborate on that.
Using your own version of the Callable
interface would look like this:
public interface ProtectedCode<V,E> {
V call() throws E;
}
public class Semaphore {
// ...
private void obtain() {
// Lock code
}
private void release() {
// Release code
}
public <V, E> void protect(final ProtectedCode<V, E> c) throws E {
obtain();
try {
return c.call();
} finally {
release();
}
}
}
Now you don't need to mess around with Exception as long as the limited (because it can reflect only one type, not a type set) type inference for type parameter E
leads to reasonable results in the compiler.
If you want to be extraordinarily friendly to your users, you could actually offer three variants of the protect
method:
public void protect(final Runnable r)
public <V> V protect(final Callable<V> c) throws Exception
public <V,E> V protect(final ProtectedCode<V,E> c) throws E
Upvotes: 3
Reputation: 2623
In order to create compiler warnings, you will need to extend the Eclipse compiler.
An alternative solution was to create a custom check in a software quality analysis system such as Teamscale or SonarQube. The custom checks perform a static analysis of the code (usually based on the abstract syntax tree enriched with semantic information) and create issues whey they detect dodgy code. The issues are displayed on the user interface of the quality analysis system. Eclipse plugins allow an integration of the systems in Eclipse so that the issues can be listed there as well.
Upvotes: 1