动态执行Groovy脚本会遇到一个问题,那就是写Groovy脚本的人整活,在脚本里写了一个死循环。这时候整个系统就都GG了。

解决方案

在一个线程池里运行groovy脚本,如果线程运行超时就杀死这个线程

核心代码

  1. private ExecutorService executor = Executors.newCachedThreadPool();
  2. private int scriptTimeout = 1000;
  3. try {
  4. future = invoke(method, instance);
  5. return future.get(scriptTimeout, TimeUnit.MILLISECONDS);
  6. // 超时
  7. } catch (TimeoutException e) {
  8. killFutureThread(future);
  9. getLogger().error(e.toString(), e);
  10. return false;
  11. // interrupt
  12. } catch (InterruptedException e) {
  13. future.cancel(true);
  14. throw e;
  15. // 执行异常
  16. } catch (ExecutionException e) {
  17. Throwable cause = t.getCause();
  18. if(cause instanceof InvocationTargetException) {
  19. // InvocationTargetException 的异常栈不一样
  20. getLogger().error("执行错误", ((InvocationTargetException) cause).getTargetException());
  21. }else{
  22. getLogger().error("执行错误", StackTraceUtils.deepSanitize(cause));
  23. }
  24. } finally {
  25. bindings.clear();
  26. }
  27. public FutureTask<Boolean> invoke(Method method, Script instance){
  28. return (FutureTask<Boolean>) executor.submit(() -> {
  29. return (boolean) method.invoke(instance);
  30. });
  31. }
  32. // 单纯的future.cancel() 只有在线程阻塞时才会停掉线程,如果线程里是一个不会sleep的死循环,就无法成功停止。
  33. // 这种情况下只能通过Thread.stop()来强行杀掉线程(这是一个因为比较危险所以被标记为废弃的函数)。
  34. public void killFutureThread(FutureTask future){
  35. try {
  36. if (future.isDone()) {
  37. return;
  38. }
  39. // 利用反射,强行取出正在运行该任务的线程
  40. // 发现FutureTask有两种实现,目前开发使用的oracle java8和Linux上的openjdk8 / openjdk11,Thread都是直接作为私有属性runner
  41. // 但是看到老的GNU的实现Thread属性是藏在FutureTask.Sync.runner属性,需要多反射一次Sync (http://fuseyism.com/classpath/doc/java/util/concurrent/FutureTask-source.html)
  42. // 虽然只支持第一种在不通环境测试都通过了,还是做一下兼容
  43. Thread execThread = null;
  44. try{
  45. Field runner = FutureTask.class.getDeclaredField("runner");
  46. runner.setAccessible(true);
  47. execThread = (Thread) runner.get(future);
  48. }catch(NoSuchFieldException e) {
  49. Field sync = FutureTask.class.getDeclaredField("sync");
  50. sync.setAccessible(true);
  51. Object obj = sync.get(future);
  52. Field runner = obj.getClass().getDeclaredField("runner");
  53. runner.setAccessible(true);
  54. execThread = (Thread) runner.get(obj);
  55. }
  56. if (execThread == null) {
  57. return;
  58. }
  59. Thread.State state = execThread.getState();
  60. if (state == Thread.State.RUNNABLE) {
  61. execThread.stop();// 非阻塞线程才STOP,例如死循环
  62. }
  63. // 其他情况cancel里会使用interrupt()方式,例如sleep、wait等等
  64. future.cancel(true);
  65. } catch (Exception e) {
  66. getLogger().error("杀死超时的线程失败");
  67. }
  68. }