问题
我正在通过 ExecutorService。我希望能够中断这些方法,但不幸的是它们本身不检查中断标志。有什么方法可以强制从这些方法引发异常吗?
我知道从任意位置抛出异常是 潜在的危险,就我的具体情况而言,我愿意抓住这个机会并准备好应对后果。
详细信息
“外部方法”是指来自外部库的一些方法,并且我无法修改其代码(我可以,但这将使其成为每当发布新版本时的维护噩梦)。
外部方法的计算成本很高,不受 IO 限制,因此它们不响应常规中断,并且我无法强制关闭通道或套接字或其他东西。正如我之前提到的,它们也不检查中断标志。
该代码在概念上类似于:
// my code
public void myMethod() {
Object o = externalMethod(x);
}
// External code
public class ExternalLibrary {
public Object externalMethod(Object) {
innerMethod1();
innerMethod1();
innerMethod1();
}
private void innerMethod1() {
innerMethod2();
// computationally intensive operations
}
private void innerMethod2() {
// computationally intensive operations
}
}
我尝试过的
Thread.stop()
理论上会做我想做的事,但它不仅已被弃用,而且仅适用于实际线程,而我' m 使用执行器任务(也可能与未来的任务共享线程,例如在线程池中工作时)。尽管如此,如果没有找到更好的解决方案,我会将我的代码转换为使用老式线程并使用此方法。
我尝试过的另一个选择是使用特殊的“Interruptable”注释来标记 myMethod() 和类似的方法,然后使用 AspectJ (我承认我是新手)来捕获那里的所有方法调用 - 一些就像:
@Before("call(* *.*(..)) && withincode(@Interruptable * *.*(..))")
public void checkInterrupt(JoinPoint thisJoinPoint) {
if (Thread.interrupted()) throw new ForcefulInterruption();
}
但是 withincode
不会递归到匹配方法调用的方法,因此我必须将此注释编辑到外部代码中。
最后,这类似于我之前的问题 - 尽管一个显着的区别是现在我正在处理一个外部库。
The Problem
I'm running multiple invocations of some external method via an ExecutorService. I would like to be able to interrupt these methods, but unfortunately they do not check the interrupt flag by themselves. Is there any way I can force an exception to be raised from these methods?
I am aware that throwing an exception from an arbitrary location is potentially dangerous, in my specific case I am willing to take this chance and prepared to deal with the consequences.
Details
By "external method" I mean some method(s) that come from an external library, and I cannot modify its code (well I can, but that will make it a maintenance nightmare whenever a new version is released).
The external methods are computationally expensive, not IO-bound, so they don't respond to regular interrupts and I can't forcefully close a channel or a socket or something. As I've mentioned before, they also do not check the interrupt flag.
The code is conceptually something like:
// my code
public void myMethod() {
Object o = externalMethod(x);
}
// External code
public class ExternalLibrary {
public Object externalMethod(Object) {
innerMethod1();
innerMethod1();
innerMethod1();
}
private void innerMethod1() {
innerMethod2();
// computationally intensive operations
}
private void innerMethod2() {
// computationally intensive operations
}
}
What I've Tried
Thread.stop()
will theoretically do what I want, but not only is it deprecated but it is also only available for actual threads, while I'm working with executor tasks (which might also share threads with future tasks, for example when working in a thread pool). Nevertheless, if no better solution is found, I will convert my code to use old-fashioned Threads instead and use this method.
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.
Finally, this is similar to a previous question of mine - though a notable difference is that now I'm dealing with an external library.
发布评论
评论(8)
我想到了以下奇怪的想法:
The following weird ideas come to my mind:
这个解决方案也不容易,但它可以工作:使用 Javassist 或 CGLIB,您可以在每个内部方法(可能由主 run() 方法调用的方法)的开头插入代码来检查线程是否处于活动状态,或其他一些标志(如果是其他标志,您还必须添加它以及设置它的方法)。
我建议使用 Javassist/CGLIB,而不是通过代码扩展类,因为您提到它是外部的,并且您不想更改源代码,并且将来可能会更改。因此,即使内部方法名称(或其参数、返回值等)发生变化,在运行时添加中断检查也适用于当前版本以及未来版本。您只需获取该类并在每个非 run() 方法的方法的开头添加中断检查。
This solution isn't easy either, but it could work: Using Javassist or CGLIB, you can insert code at the beginning of each internal method (the ones presumably being called by the main run() method) to check if the thread is alive, or some other flag (if it's some other flag, you'll have to add it as well, along with a method to set it).
I'm proposing Javassist/CGLIB instead of extending the class through code because you mention it's external and you don't want to change the source code, and it may change in the future. So adding the interrupt checks at runtime will work for the current version and also in future versions even if the internal method names change (or their parameters, return values, etc). You just have to take the class and add interrupt checks at the beginning of each method that is not the run() method.
一个选项是:
id
字段,您将能够识别正在执行它的线程。)尽管我不认为停止的线程会严重干扰执行器(毕竟它们应该是故障安全的),但还有一种不涉及停止线程的替代解决方案。
假设您的任务不会修改系统其他部分的任何内容(这是一个合理的假设,否则您不会尝试将它们击落),您可以做的是使用 JDI 弹出不需要的堆栈帧并正常退出任务。
这是包装所有其他可运行对象的可运行对象。它存储对执行线程的引用(稍后会很重要)和 id,以便我们可以通过 JDI 调用找到它。
作为参考,我测试了它的“库”调用:
您可以通过使用命令行选项
-agentlib:jdwp=transport=dt_socket,server= 启动
。它的工作原理有两个警告。首先,如果正在执行本机代码(帧的Main
类来尝试一下。 y,地址=本地主机:8000,超时= 5000,挂起= nthisObject
为空),则必须等待它完成。其次,finally
块不会被调用,因此各种资源可能会泄漏。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.
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.
And for reference, the "library" call I tested it with:
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.你写道:
AspectJ 的想法很好,但是您需要
cflow()
或cflowbelow()
以便递归地匹配某个控制流(例如类似@Before (“cflow(执行(@Interruptable * *(..)))”)
)。如果您的外部库具有可以使用
within()
精确定位的包名称,您甚至可能不需要标记注释。 AspectJ 确实很强大,而且通常有不止一种方法可以解决一个问题。我建议使用它,因为它是为您这样的努力而设计的。You wrote:
The AspectJ idea is good, but you need to
cflow()
orcflowbelow()
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.我为我的问题找到了一个丑陋的解决方案。它并不漂亮,但它适用于我的情况,所以我将其发布在这里,以防对其他人有所帮助。
我所做的是分析应用程序的库部分,希望能够隔离一小组重复调用的方法 - 例如一些
get
方法或equals()
或类似的东西;然后我可以在那里插入以下代码段:要么通过编辑库的代码手动插入,要么通过编写适当的方面自动插入。请注意,如果库尝试捕获并吞下 RuntimeException,则抛出的异常可能会被库不尝试捕获的其他异常替换。
对我来说幸运的是,使用 VisualVM,我能够找到一个单一方法,称为在我对库的具体使用过程中,出现的次数非常多。添加上述代码段后,现在可以正确响应中断了。
这当然是不可维护的,而且没有什么能真正保证库会在其他场景中重复调用此方法;但它对我有用,并且由于分析其他应用程序并在那里插入检查相对容易,我认为这是一个通用的(尽管丑陋的)解决方案。
I have hacked an ugly solution to my problem. It's not pretty, but it works in my case, so I'm posting it here in case it will help anyone else.
What I did was profile the library parts of my application, hoping that I could isolate a small group of methods which are called repeatedly - for example some
get
methods orequals()
or something along these lines; and then I could insert the following code segment there:Either inserting it manually by editing the library's code, or automatically by writing an appropriate aspect. Notice that if the library attempts to catch and swallow a
RuntimeException
, the thrown exception could be replaced with something else the library doesn't try to catch.Luckily for me, using VisualVM, I was able to find a single method called a very high number of times during the specific usage I was making of the library. After adding the above code segment, it now properly responds to interrupts.
This is of course not maintainable, plus nothing really guarantees the library will call this method repeatedly in other scenarios; but it worked for me, and since it's relatively easy to profile other applications and insert the checks there, I consider this a generic, if ugly, solution.
如果内部方法具有相似的名称,那么您可以使用 xml (spring/AspectJ) 中的切入点定义而不是注释,因此不需要对外部库进行代码修改。
If internal methods have similar names, then you could use pointcut definition in xml (spring/AspectJ) instead of annotations so no code modification of the external library should be needed.
就像 mhaller 所说,最好的选择是启动一个新的流程。由于您的工作不那么合作,因此您永远无法保证线程终止。
解决您的问题的一个很好的解决方案是使用支持“轻量级线程”的任意暂停/停止的库,例如 Akka 代替执行器服务,尽管这可能有点大材小用。
虽然我从未使用过 Akka 并且无法确认它是否按您的预期工作,但文档指出有一个 stop() 用于停止 Actor 的方法。
Like mhaller said, the best option is to launch a new Process. Since your jobs are not that cooperative, you will never have guarantees on the Thread termination.
A nice solution to your problem would be using a library that supports arbitrary pause/stop of ' lightweight threads' such as Akka instead of the executor service, although this may be a bit of an overkill.
Although I've never used Akka and cannot confirm it works as you expect, the docs state there's a stop() method for stopping actors.
据我所知,有两种使用方面
AspectJ 是一个定制的编译器,它拦截正在编译的方法(这意味着它无法获取“外部方法”。
Spring AOP(默认情况下)使用类代理在运行时拦截方法(因此它可以拦截“外部方法”。但是 Spring AOP 的问题是它无法代理已经代理的类(AspectJ 可以做到这一点,因为它不代理类)我认为 AspectJ 在这种情况下可以帮助你。
To my knowledge there are two ways of using aspect
AspectJ is a customized compiler which intercepts methods that is compiles (meaning it can't get "external methods".
Spring AOP (by default) uses class proxying to intercept methods at runtime (so it can intercept "external methods". but the problem with Spring AOP is that it can't proxy already-proxied classes (which AspectJ can do because it does not proxy classes). I think AspectJ could help you in this case.