ClassCastException NativeRegExpExecResult 无法转换为 NativeArray
ClassCastException NativeRegExpExecResult cannot be cast to NativeArray
我正在 OpenShift 上的 WildFly 10 运行 上开发应用程序,利用 Nashorn 对 React 应用程序进行服务器端渲染。
该应用程序在我的本地计算机上运行正常(我不喜欢性能级别),但是当我将它部署到 OpenShift 时,发生了一些神秘的事情。
几次请求后,运行 在之前的请求中找到的代码突然开始抛出
java.lang.ClassCastException: jdk.nashorn.internal.objects.NativeRegExpExecResult cannot be cast to 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
并且不会停止了。对于任何请求,我的应用程序所做的只是 return 503 service not available
。
我弄乱了代码,多次重写它以获得正确的行为,但我有点卡住了。最后,我卡在 synchronized
块中以尝试消除线程问题,但问题仍然存在。奇怪的是,如果我在 WildFly 中将 max-worker-threads
设置为 1
,问题似乎就消失了。
我说似乎是因为我发现很难确定问题所在,OpenShift 的部署时间长以及问题的 'random' 行为。
下面是我的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
中)......我想(基于我发现的文档)这应该是线程安全的......无奈之下我在这个锁上添加了一个 LOCK
和 synchronized
块,但它似乎没有帮助。
我什至不确定它是否与线程有关,但确实如此。
上面的代码看起来是创建多个同时使用的脚本上下文的正确方法吗?
有什么技巧可以解决这个问题,甚至调试它吗?
此问题与 https://bugs.openjdk.java.net/browse/JDK-8145550 which has been fixed in jdk9 and backported to jdk8u-dev ( http://hg.openjdk.java.net/jdk8u/jdk8u-dev/nashorn/rev/fa7dce1af94e ). It'd be great If you can pull http://hg.openjdk.java.net/jdk8u/jdk8u-dev/nashorn 相似,构建 nashorn.jar 以针对您的应用进行测试。谢谢
我正在 OpenShift 上的 WildFly 10 运行 上开发应用程序,利用 Nashorn 对 React 应用程序进行服务器端渲染。
该应用程序在我的本地计算机上运行正常(我不喜欢性能级别),但是当我将它部署到 OpenShift 时,发生了一些神秘的事情。
几次请求后,运行 在之前的请求中找到的代码突然开始抛出
java.lang.ClassCastException: jdk.nashorn.internal.objects.NativeRegExpExecResult cannot be cast to 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
并且不会停止了。对于任何请求,我的应用程序所做的只是 return 503 service not available
。
我弄乱了代码,多次重写它以获得正确的行为,但我有点卡住了。最后,我卡在 synchronized
块中以尝试消除线程问题,但问题仍然存在。奇怪的是,如果我在 WildFly 中将 max-worker-threads
设置为 1
,问题似乎就消失了。
我说似乎是因为我发现很难确定问题所在,OpenShift 的部署时间长以及问题的 'random' 行为。
下面是我的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
中)......我想(基于我发现的文档)这应该是线程安全的......无奈之下我在这个锁上添加了一个 LOCK
和 synchronized
块,但它似乎没有帮助。
我什至不确定它是否与线程有关,但确实如此。
上面的代码看起来是创建多个同时使用的脚本上下文的正确方法吗?
有什么技巧可以解决这个问题,甚至调试它吗?
此问题与 https://bugs.openjdk.java.net/browse/JDK-8145550 which has been fixed in jdk9 and backported to jdk8u-dev ( http://hg.openjdk.java.net/jdk8u/jdk8u-dev/nashorn/rev/fa7dce1af94e ). It'd be great If you can pull http://hg.openjdk.java.net/jdk8u/jdk8u-dev/nashorn 相似,构建 nashorn.jar 以针对您的应用进行测试。谢谢