spark广播变量遇到这个异常,初始化数据库操作没加lazy
java nio里的channel是实现自InterruptibleChannel
接口的,这个接口的注释里有说明,当正在操作这个channel的线程被其他线程中断,则会close
这个channel,当前(被中断的)线程抛出一个ClosedByInterruptException
异常。
我们今天在排查一个问题时,用户线程执行了下面的调用过程(从上往下):
org.apache.catalina.connector.CoyoteOutputStream.flush ---》
org.apache.tomcat.util.net.NioChannel.write ---》
sun.nio.ch.SocketChannelImpl.write ---》
java.nio.channels.spi.AbstractInterruptibleChannel.end // 这里抛出异常
来看一下这个sun.nio.ch.SocketChannelImpl.write
方法内部,它的详细代码可以看这里 这里简化一些:
public int write(ByteBuffer buf) throws IOException {
...
synchronized (writeLock) {
...
try {
begin();
...
} finally {
...
end(n > 0 || (n == IOStatus.UNAVAILABLE));
...
}
}
}
主要看一下它里面的begin
和end
,先看end
方法,异常抛出的地方:
protected final void end(boolean completed)
throws AsynchronousCloseException
{
blockedOn(null);
Thread interrupted = this.interrupted;
if (interrupted != null && interrupted == Thread.currentThread()) {
interrupted = null;
throw new ClosedByInterruptException();
}
if (!completed && !open)
throw new AsynchronousCloseException();
}
可以看到ClosedByInterruptException
异常抛出的前提是当前线程被标记为已中断的;而这个判断是在begin
方法里做的:
protected final void begin() {
if (interruptor == null) {
interruptor = new Interruptible() {
public void interrupt(Thread target) {
synchronized (closeLock) {
if (!open)
return;
open = false;
interrupted = target;
try {
AbstractInterruptibleChannel.this.implCloseChannel();
} catch (IOException x) { }
}
}};
}
blockedOn(interruptor);
Thread me = Thread.currentThread();
if (me.isInterrupted()) // 检测当前线程是否已中断
interruptor.interrupt(me);
}
在begin
方法里,检查当前线程如果是中断状态,用引用记录起来(为了后边比较使用),并关闭了channel。
现在用scala模拟一下这个异常:
$ cat server
import java.nio._
import java.net._
import java.nio.channels._
val serverSock = ServerSocketChannel.open()
serverSock.socket().bind(new InetSocketAddress(54321))
val ch:SocketChannel = serverSock.accept()
println("ok,received")
Thread.currentThread().interrupt() //中断当前线程
try{
ch.socket().getOutputStream().write(200)
}catch{
case e:Throwable => println(e)
}
上面的这段脚本,用nio模拟了一个server等待客户端的链接,当链接过来的时候,中断当前线程,然后继续channel进行处理的时候会捕获到ClosedByInterruptException
异常。
启动上面的脚本
$ scala server
在另一个终端下模拟一次client请求:
$ telnet localhost 54321
这时会看到server端的输出信息:
$ scala server
ok,received
java.nio.channels.ClosedByInterruptException