ClassCastException NativeRegExpExecResult无法强制转换为NativeArray

时间:2015-12-14 20:36:23

标签: java multithreading nashorn

我正在开发一个在OpenShift上运行的WildFly 10上的应用程序,利用Nashorn对React应用程序进行服务器端渲染。

应用程序在我的本地机器上运行正常(我不喜欢性能水平),但是当我将它部署到OpenShift时,会发生一些神秘的事情。

在几次请求之后,在先前请求中运行查找的代码突然开始抛出

  

java.lang.ClassCastException:jdk.nashorn.internal.objects.NativeRegExpExecResult无法强制转换为jdk.nashorn.internal.objects.NativeArray

堆栈跟踪揭示了这一行来自code in React-Router ......我手动添加了几行日志记录到react-router,奇怪的是反应路由器正在处理的参数失败的情景。这就是改变后的代码:

if (match != null) {
  if (captureRemaining) {if (typeof global != 'undefined') {global.log.warn('match.length=' + match.length);}
    remainingPathname = match.pop();
    var matchedPath = match[0].substr(0, match[0].length - remainingPathname.length);
    if (typeof global != 'undefined') {global.log.warn('remainingPathname=' + remainingPathname + ', matchedPath=' + matchedPath);}
    // If we didn't match the entire pathname, then make sure that the match
    // we did get ends at a path separator (potentially the one we added
    // above at the beginning of the path, if the actual match was empty).

(请注意对global.log.warn的调用...我添加了这些内容)

如果查看full logs,您可以看到第一次请求时,事情似乎正常,但突然之间,它开始抛出此ClassCastException并且不再停止。对于任何请求,我的所有应用都会返回503 service not available

我弄乱了代码,多次重写以获得正确的行为,但我有点卡住了。最后,我陷入synchronized块以试图消除线程问题,但问题仍然存在。奇怪的是,如果我在WildFly中将max-worker-threads设置为1,问题似乎就会消失。

我说似乎是因为我发现很难确定问题,OpenShift的部署时间长以及问题的“随机”行为是什么。

以下是我ReactRenderFilter的相关代码。 Full code on pastebin

public class ReactRenderFilter implements Filter {
    private static final Object LOCK = new Object();

    private static final ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("text/javascript");  
    private static final List<CompiledScript> scripts = new ArrayList<>();
    private static final ThreadLocal<RenderEngine> renderEngine = new ThreadLocal<>();

    private FilterConfig config;
    private String bundlePath;
    private String jspPath;

    public static class RenderEngine {
        private final ScriptContext context;
        private final ReactRenderer renderer;
        private final long lastModified;

        public RenderEngine(File bundle) throws ScriptException, UnsupportedEncodingException, FileNotFoundException {
            context = new SimpleScriptContext();
            Bindings global = engine.createBindings();
            context.setBindings(global, ScriptContext.ENGINE_SCOPE);
            global.put("global", global);
            for (CompiledScript script : scripts) {
                script.eval(context);
            }
            engine.eval(new InputStreamReader(new FileInputStream(bundle), "utf-8"), context);
            lastModified = bundle.lastModified(); 
            LOG.finer("Getting renderer");
            renderer = ((ScriptObjectMirror) engine.eval("global.render", context)).to(ReactRenderer.class);
        }

        String render(String path, String initialDataJSON) {
            return renderer.render(path, initialDataJSON);
        }

        boolean isOutdated(File bundle) {
            return lastModified != bundle.lastModified();
        }
    }


    public ReactRenderFilter() {super();}
    @Override public void destroy() {}

    @Override public void init(FilterConfig filterConfig) throws ServletException {
        config = filterConfig;
        try {
            String[] paths = ...
            for (String path : paths) {
                if (path.trim().isEmpty()) {continue;}
                File file = new File(config.getServletContext().getRealPath(path.trim()));
                scripts.add(((Compilable) engine).compile(new InputStreamReader(new FileInputStream(file), "utf-8")));
            }
            bundlePath = config.getServletContext().getRealPath(config.getInitParameter(PARAM_APP_BUNDLE_PATH).trim());
            jspPath = config.getInitParameter(PARAM_MARKUP_JSP_PATH).trim();
        } catch (UnsupportedEncodingException | FileNotFoundException | ScriptException e) {
            throw new ServletException("Unable to initialize ReactRenderServlet.", e);
        }
    }

    @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        File bundle = new File(bundlePath);
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        String path = req.getRequestURI().substring(req.getContextPath().length());
        String initialDataJSON = "{}";
        @SuppressWarnings("unchecked")
        Map<String, Object> initialData = (Map<String, Object>) req.getAttribute("initialData");
        if  (initialData != null) {
            ObjectMapper mapper = new ObjectMapper();
            initialDataJSON = mapper.writeValueAsString(initialData);
            req.setAttribute("initialDataJSON", initialDataJSON);
        }
        String renderResult = null;
        try {
            if (renderEngine.get() == null || renderEngine.get().isOutdated(bundle)) {
                // prevent multiple render engines to be instantiated simultaneously
                synchronized (LOCK) {
                    renderEngine.set(new RenderEngine(bundle));
                }
            }

            // I sure hope there is a way around this... locking on central object
            // during rendering can't be good for performance... But it beats having
            // only one worker thread
            synchronized (LOCK) {
                renderResult = renderEngine.get().render(path, initialDataJSON);
            }

            if (renderResult.startsWith(MARKUP)) {
                String markup = renderResult.substring(MARKUP.length());
                req.setAttribute("markup", markup);
                int maxAge = 60 * 60; // 60 minutes 
                res.addHeader("Cache-Control", "public, max-age=" + maxAge);
                res.addDateHeader("Expires", new Date().getTime() + maxAge);
                req.getRequestDispatcher(jspPath).forward(request, response);       
            }
            else if (renderResult.startsWith(REDIRECT)) {
                String url = renderResult.substring(REDIRECT.length());
                res.sendRedirect(url); 
            }
            else if (renderResult.startsWith(NOTFOUND)) {
                int maxAge = 365 * 24 * 60 * 60; // 365 days 
                res.addHeader("Cache-Control", "public, max-age=" + maxAge);
                res.addDateHeader("Expires", new Date().getTime() + maxAge);
                chain.doFilter(request, response);
            }
            else {
                String msg = renderResult.substring(ERROR.length());
                throw new ServletException("Unable to generate response for route [" + path + "]: " + msg);
            }
        } catch (ScriptException e) {
            throw new ServletException(e);
        }
    }
}

正如您所看到的,我为每个帖子(ScriptEngine)都有一个静态ScriptContext和一个单独的Bindings + ThreadLocal ...我认为(基于在我发现的文档上,这应该是线程安全的...在绝望中我在这个锁上添加了LOCKsynchronized块,但它似乎没有帮助。

我不确定它是否与线程有关,但确实如此。

上述代码是否是创建同时使用的多个脚本上下文的正确方法?

有什么提示可以摆脱这个问题,甚至调试它?

0 个答案:

没有答案