簡體   English   中英

ClassCastException NativeRegExpExecResult無法強制轉換為NativeArray

[英]ClassCastException NativeRegExpExecResult cannot be cast to NativeArray

我正在使用Nashorn在OpenShift上運行的WildFly 10上開發一個應用程序,以對React應用程序進行服務器端渲染。

該應用程序可以在本地計算機上正常運行(我不喜歡perf級別),但是當我將其部署到OpenShift時,會發生一些神秘的事情。

幾次請求后,先前請求上運行的代碼突然開始拋出

java.lang.ClassCastException:jdk.nashorn.internal.objects.NativeRegExpExececResult無法轉換為jdk.nashorn.internal.objects.NativeArray

堆棧跟蹤揭示了這來自於React-Router中的一行代碼 ...我在手動添加了幾行日志記錄到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的調用...我添加了這些)

如果查看完整的日志,您會發現對於第一個請求,一切似乎運行良好,但是突然,它開始拋出此ClassCastException並且將不再停止。 我的應用程序所做的只是返回503 service not available用於任何請求。

我弄亂了代碼,多次重寫它以獲得正確的行為,但是我有點卡住了。 最后,我陷入了一個synchronized塊中,試圖消除線程問題,但問題仍然存在。 奇怪的是,如果我在WildFly中將max-worker-threads1 ,問題似乎就消失了。

我說這似乎是因為我發現很難解決問題,OpenShift的部署時間長且問題的“隨機”行為如何。

以下是我的ReactRenderFilter的相關代碼。 在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);
        }
    }
}

如您所見,對於每個線程(在ThreadLocal ),我都有一個靜態ScriptEngine和一個單獨的ScriptContext + Bindings ...我認為(基於我發現的文檔)這應該是線程安全的。 LOCK和此鎖上的synchronized塊,但這似乎無濟於事。

我不確定它甚至與線程無關,但確實如此。

上面的代碼是否看起來像創建要同時使用的多個腳本上下文的正確方法?

有什么技巧可以解決這個問題,甚至可以進行調試?

此問題似乎類似於https://bugs.openjdk.java.net/browse/JDK-8145550 ,該問題已在jdk9中修復,並反向移植到jdk8u-dev( http://hg.openjdk.java.net/jdk8u/jdk8u -dev / nashorn / rev / fa7dce1af94e )。 如果您可以拉http://hg.openjdk.java.net/jdk8u/jdk8u-dev/nashorn並構建nashorn.jar來針對您的應用進行測試,那就太好了。 謝謝。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM