I\'m running multiple invocations of some external method via an ExecutorService. I would like to be able to interrupt these methods, but unfortunately the
You wrote:
Another option I've tried is to mark
myMethod()
and similar methods with a special "Interruptable" annotation and then use AspectJ (which I am admittedly a newbie at) for catching all method invocations there - something like:@Before("call(* *.*(..)) && withincode(@Interruptable * *.*(..))") public void checkInterrupt(JoinPoint thisJoinPoint) { if (Thread.interrupted()) throw new ForcefulInterruption(); }
But
withincode
isn't recursive to methods called by the matching methods, so I would have to edit this annotation into the external code.
The AspectJ idea is good, but you need to
cflow()
or cflowbelow()
in order to recursively match a certain control flow (e.g. something like @Before("cflow(execution(@Interruptable * *(..)))")
).You might not even need your marker annotation if your external library has a package name you can pinpoint with within()
. AspectJ is really powerful, and often there is more than one way to solve a problem. I would recommend using it because it was made for such endeavours as yours.
An option is to:
id
field in your task objects, you will be able to identify the thread that is executing it.)Although I don't think a stopped thread would seriously interfere with executors (they should be fail-safe after all), there is an alternative solution that doesn't involve stopping the thread.
Provided that your tasks don't modify anything in other parts of the system (which is a fair assumption, otherwise you wouldn't be trying to shoot them down), what you can do is to use JDI to pop unwanted stack frames off and exit the task normally.
public class StoppableTask implements Runnable {
private boolean stopped;
private Runnable targetTask;
private volatile Thread runner;
private String id;
public StoppableTask(TestTask targetTask) {
this.targetTask = targetTask;
this.id = UUID.randomUUID().toString();
}
@Override
public void run() {
if( !stopped ) {
runner = Thread.currentThread();
targetTask.run();
} else {
System.out.println( "Task "+id+" stopped.");
}
}
public Thread getRunner() {
return runner;
}
public String getId() {
return id;
}
}
This is the runnable that wraps all your other runnables. It stores a reference to the executing thread (will be important later) and an id so we can find it with a JDI call.
public class Main {
public static void main(String[] args) throws IOException, IllegalConnectorArgumentsException, InterruptedException, IncompatibleThreadStateException, InvalidTypeException, ClassNotLoadedException {
//connect to the virtual machine
VirtualMachineManager manager = Bootstrap.virtualMachineManager();
VirtualMachine vm = null;
for( AttachingConnector con : manager.attachingConnectors() ) {
if( con instanceof SocketAttachingConnector ) {
SocketAttachingConnector smac = (SocketAttachingConnector)con;
Map<String,? extends Connector.Argument> arg = smac.defaultArguments();
arg.get( "port" ).setValue( "8000");
arg.get( "hostname" ).setValue( "localhost" );
vm = smac.attach( arg );
}
}
//start the test task
ExecutorService service = Executors.newCachedThreadPool();
StoppableTask task = new StoppableTask( new TestTask() );
service.execute( task );
Thread.sleep( 1000 );
// iterate over all the threads
for( ThreadReference thread : vm.allThreads() ) {
//iterate over all the objects referencing the thread
//could take a long time, limiting the number of referring
//objects scanned is possible though, as not many objects will
//reference our runner thread
for( ObjectReference ob : thread.referringObjects( 0 ) ) {
//this cast is safe, as no primitive values can reference a thread
ReferenceType obType = (ReferenceType)ob.type();
//if thread is referenced by a stoppable task
if( obType.name().equals( StoppableTask.class.getName() ) ) {
StringReference taskId = (StringReference)ob.getValue( obType.fieldByName( "id" ));
if( task.getId().equals( taskId.value() ) ) {
//task with matching id found
System.out.println( "Task "+task.getId()+" found.");
//suspend thread
thread.suspend();
Iterator<StackFrame> it = thread.frames().iterator();
while( it.hasNext() ) {
StackFrame frame = it.next();
//find stack frame containing StoppableTask.run()
if( ob.equals( frame.thisObject() ) ) {
//pop all frames up to the frame below run()
thread.popFrames( it.next() );
//set stopped to true
ob.setValue( obType.fieldByName( "stopped") , vm.mirrorOf( true ) );
break;
}
}
//resume thread
thread.resume();
}
}
}
}
}
}
And for reference, the "library" call I tested it with:
public class TestTask implements Runnable {
@Override
public void run() {
long l = 0;
while( true ) {
l++;
if( l % 1000000L == 0 )
System.out.print( ".");
}
}
}
You can try it out by launching the Main
class with the command line option -agentlib:jdwp=transport=dt_socket,server=y,address=localhost:8000,timeout=5000,suspend=n
. It works with two caveats. Firstly, if there is native code being executed (thisObject
of a frame is null), you'll have to wait until it's finished. Secondly, finally
blocks are not invoked, so various resources may potentially be leaking.