写入stderr时,共享C库(JNI)在jetty下挂起

时间:2015-02-06 01:30:28

标签: java java-native-interface jetty jna jetty-9

我发现在打印到stderr时,在jetty下使用JNA调用的共享库会遇到问题。

我通过首先创建一个非常简单的C共享库来简化问题,使其易于重现,只需调用fprintf(stderr,"0123456789\n"); 100次,然后返回。

在java端,我们在全局锁上有一个synchronize语句,以确保一次只有一个线程在共享库中。

synchronized (lock) {
  Foo.INSTANCE.shared_lib_function();
}

我在jetty下部署它并最终向jetty发出请求以最终调用共享库(在少于100个请求之后)我发现共享库被卡住了。

使用jstack我们可以看到在共享库调用中卡住的线程(类已被重命名):

Thread 5991: (state = BLOCKED)
 - com.whats.going.on.connector.MyFooCaller.callIt() @bci=55, line=105 (Interpreted frame)
 - com.whats.going.on.Controller.callSharedLib() @bci=101, line=71 (Interpreted frame)
 - com.whats.going.on.Controller$$FastClassBySpringCGLIB$$d6a0f4b3.invoke(int, java.lang.Object, java.lang.Object[]) @bci=72 (Interpreted frame)
 - org.springframework.cglib.proxy.MethodProxy.invoke(java.lang.Object, java.lang.Object[]) @bci=19, line=204 (Interpreted frame)
 - org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint() @bci=19, line=717 (Interpreted frame)
 - org.springframework.aop.framework.ReflectiveMethodInvocation.proceed() @bci=19, line=157 (Interpreted frame)
 - org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(org.aopalliance.intercept.MethodInvocation) @bci=7, line=64 (Interpreted frame)
 - org.springframework.aop.framework.ReflectiveMethodInvocation.proceed() @bci=101, line=179 (Interpreted frame)
 - org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.springframework.cglib.proxy.MethodProxy) @bci=112, line=653 (Interpreted frame)

使用gdb我可以从共享库中获取回溯:

#0  0x00007f1136ec153d in write () from /lib64/libc.so.6
#1  0x00007f1136e57ad3 in _IO_new_file_write () from /lib64/libc.so.6
#2  0x00007f1136e5799a in _IO_new_file_xsputn () from /lib64/libc.so.6
#3  0x00007f1136e4da4d in fwrite () from /lib64/libc.so.6
#4  0x00007f10ed2dc122 in shared_lib_function () at foo/bar.c:357
#5  0x00007f10ed4d227c in ?? ()
#6  0x000000000000000e in ?? ()
#7  0x00007f110c2309c0 in ?? ()
#8  0x00007f110c230700 in ?? ()
#9  0x00007f10ed4d1ddf in ?? ()
#10 0x0000000000000000 in ?? ()

第357行是fprintf()行。

我担心这个问题可能会被困住,只能从stdout准备而且从来没有stderr。在java中我创建了一个线程,它继续打印到stdout和stderr,我可以看到它们。

我还试图看看如果我们在java中对System.err.println("9876543210");进行了100次调用会发生什么,但是这并没有导致java中的线程卡住。

最初记录此stderr时,stdout被重定向:

PrintStream errorLog = new PrintStream(new RolloverFileOutputStream(new Fil("yyyy_mm_dd.error.log").getCanonicalPath(), false, 90));
System.setErr(errorLog);
System.setOut(errorLog);

我能够看到共享库在日志文件中写入stderr的内容。然后我删除了stderr和stdout的重定向,并注意到我再也看不到共享库写入stderr的内容了,但我可以看到System.err.println()正在打印。

当我尝试在测试中调用共享库(没有jetty)时,我无法重现该问题。我从日食和母亲那里跑了我的测试。我也尝试重定向stderr和stdout,但是我发现只有对java中的stderr和stdout的写入被重定向(即fprintf()从共享库中的stderr继续显示在eclipse或控制台中)。

Java版:

java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

Jetty版本:9.2.6.v20141205

2 个答案:

答案 0 :(得分:0)

从JNI写入STDERR,对大多数java日志框架都不会很好。

选项#1:Java Side Fix

您需要做的事情:

  • 不要使用Jetty的默认日志记录模块
  • 不要使用Jetty的StdErrLog实施
  • 配置备用日志框架。从slf4j开始,并将其设置为使用logback或log4j。
  • 将备用日志记录框架配置为NOT capture STDERR。
  • 将备用日志记录框架配置为不写入STDERR,只将其写入日志文件。

选项#2:JNI边修复

另一种选择是将JNI代码编写为永不输出到默认的STDERR或STDOUT流。但是,获取对Java System.err和System.out的文件句柄引用,然后写入它们。

可以在

的comp.lang.java.programmer组中找到一个例子

https://groups.google.com/forum/#!msg/comp.lang.java.programmer/SUN7EEjk8AU/JWjGGaD0ey0J

答案 1 :(得分:0)

我遇到的问题是YAJSW只是从jetty的System.out和System.err读取而没有读取任何共享库的stdout或stderr,如果发生了一些错误,最终会被填充。

解决方案是设置wrapper.console.pipestreams = true,请参阅http://yajsw.sourceforge.net/YAJSW%20Configuration%20Parameters.html

使用freopen重定向stdout和stderr同时工作。谢谢你的帮助。