Java8 非阻塞读有竞争条件?
Java 8 non-blocking read has race condition?
现在这个问题困扰了我一段时间。
在我处理的工作应用程序中,我在非阻塞模式下使用 SocketChannel 与嵌入式设备通信。
现在我收到偶尔损坏的数据。
在某些 PC 上它不会发生,现在它发生在我的身上。
但是当我在程序中改变太多时,问题就消失了。
这么多可能会产生影响。时机,网络接口硬件,win7,java版本,公司防火墙,...
数据读取归结为这段代码:
byteBuffer.compact();
socketChannel.read(byteBuffer); // <<< problem here ?
byteBuffer.flip();
if( byteBuffer.hasRemaining() ){
handleData( byteBuffer );
}
This is 运行 in the same thread as the writing, when the selector wakes up and the interest op OP_READ is set.
这段代码是唯一引用byteBuffer的地方。 socketChannel 仅在写入时从同一个线程使用。
我检测了代码,所以当错误发生时,我可以打印出最后几次 read() 调用的内容。同时我在Wireshark上分析网络流量。我添加了很多断言来检查字节缓冲区的完整性。
在 Wireshark 中,接收到的流看起来不错。没有 DUP-ACK 或其他可疑的东西。最后的 read() 调用与 Wireshark 中的数据完全匹配。
在 Wireshark 中,我看到许多小的 TCP 帧以 10 毫秒的间隔接收 90 字节的有效载荷数据。一般情况下Java线程在数据刚到的时候也是10ms读取数据。
当谈到这个问题时,Java 线程有点延迟,因为读取发生在 300 毫秒之后,而读取 returns 大约有 ~3000 个字节,这似乎是合理的。但数据已损坏。
数据看起来像,如果它被复制到缓冲区并且并发接收数据已经覆盖了第一个数据。
现在我不知道如何进行。我无法创建一个小示例,因为这种情况很少发生,而且我不知道需要的确切条件。
有人可以给个提示吗?
如何证明是不是Java库?
哪些条件可能也很重要?
谢谢
弗兰克
2015 年 6 月 29 日:
现在我能够构建一个用于复制的示例。
发送方正在使用阻塞IO,首先等待连接,然后每2ms 发送90 字节块。前 4 个字节是一个 运行ning 计数器,其余未设置。发件人使用 setNoTcpDelay(true).
接收器正在使用非阻塞 IO。首先,它连接到发送器,然后只要选择键准备就绪,它就会读取通道。有时,读取循环执行 Thread.sleep(300).
如果他们 运行 通过环回在同一台 PC 上,这对我一直有效。如果我将发件人放在另一台 PC 上,直接通过 LAN 连接,则会触发错误。使用 Wireshark 检查,流量和发送的数据看起来不错。
至 运行,首先在一台 PC 上启动 Sender,然后(编辑主机地址后)启动 Receiver。
只要它能正常工作,它大约每 2 秒打印一行。如果失败,它会打印有关最后 5 次 read() 调用的信息。
我发现触发因素:
- 发送方配置了setNoTcpDelay(true)
- 接收方有时在执行 read() 之前有一个 Thread.sleep(300)。
谢谢
弗兰克
buf.order(ByteOrder.BIG_ENDIAN);
这是默认设置。删除这个。
buf.clear();
缓冲区已经是空的,因为你刚刚分配了它。删除这个。
buf.limit(0);
在 clear() 之后以及初始分配之后,限制已经为零。删除这个。
while( true ) {
这里应该有一个 select() 调用。
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
// ...
if( key == keyData && key.isConnectable() ) {
ch.finishConnect();
这个方法可以return false。你不处理那个案子。
// ...
if( key == keyData && key.isReadable() ) {
// ...
readPos += ch.read(buf);
完全不正确。您完全忽略了 read()
returns -1 的情况,这意味着对等方已断开连接。在这种情况下,您必须关闭频道。
// without this Thread.sleep, it would not trigger the error
所以呢?一分钱不是掉了吗? 删除睡眠。 完全没有意义。 select()
将阻塞直到数据到达。它不需要你的帮助。这种睡眠简直就是浪费时间。
if( rnd.nextInt(20) == 0 ) {
Thread.sleep(300);
}
删除这个。
selector.select();
这应该在循环的顶部,而不是底部。
原来是驱动的问题,至少看起来是这样
我使用了 USB 转以太网适配器 "D-Link E-DUB100 Rev A"。
由于 wireshark 显示了正确的数据,我想排除硬件可能导致故障的原因。
但与此同时,我尝试了 "D-Link E-DUB100 Rev C1",问题消失了。
所以我认为这是 D-Link 为 Rev A 提供的驱动程序中的一个问题。对于 Rev C1,它可能使用没有这个问题的系统驱动程序。
谢谢大家花时间阅读我的问题。
现在这个问题困扰了我一段时间。
在我处理的工作应用程序中,我在非阻塞模式下使用 SocketChannel 与嵌入式设备通信。 现在我收到偶尔损坏的数据。 在某些 PC 上它不会发生,现在它发生在我的身上。 但是当我在程序中改变太多时,问题就消失了。
这么多可能会产生影响。时机,网络接口硬件,win7,java版本,公司防火墙,...
数据读取归结为这段代码:
byteBuffer.compact();
socketChannel.read(byteBuffer); // <<< problem here ?
byteBuffer.flip();
if( byteBuffer.hasRemaining() ){
handleData( byteBuffer );
}
This is 运行 in the same thread as the writing, when the selector wakes up and the interest op OP_READ is set.
这段代码是唯一引用byteBuffer的地方。 socketChannel 仅在写入时从同一个线程使用。
我检测了代码,所以当错误发生时,我可以打印出最后几次 read() 调用的内容。同时我在Wireshark上分析网络流量。我添加了很多断言来检查字节缓冲区的完整性。
在 Wireshark 中,接收到的流看起来不错。没有 DUP-ACK 或其他可疑的东西。最后的 read() 调用与 Wireshark 中的数据完全匹配。
在 Wireshark 中,我看到许多小的 TCP 帧以 10 毫秒的间隔接收 90 字节的有效载荷数据。一般情况下Java线程在数据刚到的时候也是10ms读取数据。
当谈到这个问题时,Java 线程有点延迟,因为读取发生在 300 毫秒之后,而读取 returns 大约有 ~3000 个字节,这似乎是合理的。但数据已损坏。
数据看起来像,如果它被复制到缓冲区并且并发接收数据已经覆盖了第一个数据。
现在我不知道如何进行。我无法创建一个小示例,因为这种情况很少发生,而且我不知道需要的确切条件。
有人可以给个提示吗?
如何证明是不是Java库?
哪些条件可能也很重要?
谢谢 弗兰克
2015 年 6 月 29 日:
现在我能够构建一个用于复制的示例。
发送方正在使用阻塞IO,首先等待连接,然后每2ms 发送90 字节块。前 4 个字节是一个 运行ning 计数器,其余未设置。发件人使用 setNoTcpDelay(true).
接收器正在使用非阻塞 IO。首先,它连接到发送器,然后只要选择键准备就绪,它就会读取通道。有时,读取循环执行 Thread.sleep(300).
如果他们 运行 通过环回在同一台 PC 上,这对我一直有效。如果我将发件人放在另一台 PC 上,直接通过 LAN 连接,则会触发错误。使用 Wireshark 检查,流量和发送的数据看起来不错。
至 运行,首先在一台 PC 上启动 Sender,然后(编辑主机地址后)启动 Receiver。
只要它能正常工作,它大约每 2 秒打印一行。如果失败,它会打印有关最后 5 次 read() 调用的信息。
我发现触发因素:
- 发送方配置了setNoTcpDelay(true)
- 接收方有时在执行 read() 之前有一个 Thread.sleep(300)。
谢谢 弗兰克
buf.order(ByteOrder.BIG_ENDIAN);
这是默认设置。删除这个。
buf.clear();
缓冲区已经是空的,因为你刚刚分配了它。删除这个。
buf.limit(0);
在 clear() 之后以及初始分配之后,限制已经为零。删除这个。
while( true ) {
这里应该有一个 select() 调用。
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
// ...
if( key == keyData && key.isConnectable() ) {
ch.finishConnect();
这个方法可以return false。你不处理那个案子。
// ...
if( key == keyData && key.isReadable() ) {
// ...
readPos += ch.read(buf);
完全不正确。您完全忽略了 read()
returns -1 的情况,这意味着对等方已断开连接。在这种情况下,您必须关闭频道。
// without this Thread.sleep, it would not trigger the error
所以呢?一分钱不是掉了吗? 删除睡眠。 完全没有意义。 select()
将阻塞直到数据到达。它不需要你的帮助。这种睡眠简直就是浪费时间。
if( rnd.nextInt(20) == 0 ) {
Thread.sleep(300);
}
删除这个。
selector.select();
这应该在循环的顶部,而不是底部。
原来是驱动的问题,至少看起来是这样
我使用了 USB 转以太网适配器 "D-Link E-DUB100 Rev A"。
由于 wireshark 显示了正确的数据,我想排除硬件可能导致故障的原因。
但与此同时,我尝试了 "D-Link E-DUB100 Rev C1",问题消失了。
所以我认为这是 D-Link 为 Rev A 提供的驱动程序中的一个问题。对于 Rev C1,它可能使用没有这个问题的系统驱动程序。
谢谢大家花时间阅读我的问题。