2015-06-26 2 views
3

сейчас эта проблема прослушивает меня какое-то время.Java 8 неблокирующее чтение имеет состояние гонки?

В рабочем приложении, над которым я работаю, я использую SocketChannel в неблокирующем режиме для связи со встроенными устройствами. Теперь я получаю спорадически поврежденные данные. На некоторых ПК этого не происходит, теперь это происходит на моем. Но когда я слишком сильно меняю программу, проблема исчезает.

Сколько может иметь влияние. Время, аппаратные средства сетевого интерфейса, win7, Java-версия, брандмауэр компании, ...

считывание данных сводятся к этому коду:

byteBuffer.compact(); 
socketChannel.read(byteBuffer); // <<< problem here ? 
byteBuffer.flip(); 
if(byteBuffer.hasRemaining()){ 
    handleData(byteBuffer); 
} 

Это выполняется в том же потоке, как запись, когда селектор просыпается и задан параметр op OP_READ.

Этот код является единственным местом, где указано byteBuffer. socketChannel используется только из одного потока при записи.

Я измерил код, поэтому я могу распечатать содержимое последних нескольких вызовов read(), когда произошла ошибка. В то же время я анализирую сетевой трафик на Wireshark. Я добавил много утверждений, чтобы проверить целостность байтов.

В Wireshark полученный поток выглядит хорошо. Нет DUP-ACK или что-то еще подозрительное. Последние вызовы read() точно совпадают с данными в Wireshark.

В Wireshark я вижу много мелких кадров TCP, получающих с 90 байтами данных полезной нагрузки в промежутках, таких как 10 мс. Обычно поток Java считывает данные, а также все 10 мс, когда он только что прибыл.

Когда дело доходит до проблемы, поток Java представляет собой битную задержку, так как чтение происходит после 300 мс, а чтение возвращается с размером ~ 3000 байт, что является правдоподобным. Но данные повреждены.

Данные выглядят, если они были скопированы в буфер, и одновременно полученные данные перезаписали первые данные.

Теперь я не знаю, как действовать. Я не могу создать небольшой пример, поскольку это редко случается, и я не знаю точное условие, которое необходимо.

Может кто-нибудь дать подсказку?

Как я могу доказать, что это Java-библиотека или нет?

Какие условия могут быть важны для просмотра?

благодаря Frank

29-июня-2015:

Теперь я смог построить пример для размножения.

Существует одна программа Sender и Receiver.

Отправитель использует блокировку ввода-вывода, сначала ожидая подключения, а затем отправляя 90 байтовых блоков каждые 2 мс. Первый 4-байтовый счетчик работает, остальные не установлены. Отправитель использует setNoTcpDelay (true).

Приемник использует неблокирующий IO. Сначала он подключается к отправителю, затем он читает канал всякий раз, когда для него готов ключ выбора. Когда-нибудь, цикл чтения выполняет Thread.sleep (300).

Если они работают на одном ПК через петлю, это работает для меня все время. Если я поставлю Sender на другой компьютер, напрямую подключенный через локальную сеть, он вызывает ошибку. Проверка с Wireshark, трафик и отправленные данные выглядят хорошо.

Чтобы запустить, сначала запустите Отправитель на одном ПК, затем (после редактирования hostaddress) запустите приемник.

Пока он работает, он печатает строку примерно каждые 2 секунды. Если он терпит неудачу, он печатает информацию о последних 5 вызовах read().

Что я нашел, чтобы быть триггером:

  1. Отправитель настроил setNoTcpDelay (истина)
  2. Приемник имеет иногда Thread.sleep (300) перед выполнением чтения().

благодаря Frank

+1

Хотя возможно, что на Java есть ошибка, это крайне маловероятно ... учитывая огромное количество других программистов на Java 8, которые (по-видимому) не испытывают такого рода проблемы. Скорее всего, это ошибка в коде. Что делать? Хорошо, если ваш код слишком сложно свести к MCVE, и слишком большой, чтобы показать нам, тогда лучше всего собрать коллегу, чтобы помочь вам. –

+2

Если код выполняется в одном потоке, это вряд ли будет «условием гонки» в обычном смысле. (Я не знаю, помогает ли это ...) –

+0

Из того, что вы упомянули, может возникнуть проблема - в том, как вы потребляете данные. Впрочем, это всего лишь предположение. По какой-то причине не объясняется тем, что вы упомянули, - когда поток java, который считывает данные, получает запланированное после 300 мс, вы получаете так много пакетов с 90 байтами назад. Если вы предполагали читать их в кусках в 90 байт, явным образом читаю их в 90 байтах. А затем потребляйте больше - когда их больше 90 байтов. Я не знаю конкретных API, но может быть, это должно указывать в каком-то направлении? – gabhijit

ответ

0

Я оказался проблемой с драйвером, по крайней мере, кажется.

Я использовал адаптеру USB для Ethernet «D-Link E-DUB100 Rev A».
Из-за того, что wirehark показывает правильные данные, я решил устранить аппаратное обеспечение возможной причины отказа.
Но пока я попробовал «D-Link E-DUB100 Rev C1», и проблема исчезла.
Поэтому я предполагаю, что это проблема в поставляемых драйверах от D-Link для Rev A. И с Rev C1 он может использовать системный драйвер, который не имеет этой проблемы.

thx на все время, чтобы прочитать мой вопрос.

1
 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(); 

Этот метод может возвращать значение false. Вы не справляетесь с этим делом.

  // ... 
      if(key == keyData && key.isReadable()) { 

        // ... 
        readPos += ch.read(buf); 

Совершенно неверно. Вы полностью игнорируете случай, когда read() возвращает -1, что означает, что одноранговый узел отключен. В этом случае вы должны закрыть канал.

  // without this Thread.sleep, it would not trigger the error 

So? Разве пенни не упало? Снимите сон. Это совершенно и совершенно бессмысленно. select() будет блокироваться до тех пор, пока данные не поступят. Это не нуждается в вашей помощи. Этот сон - буквально пустая трата времени.

  if(rnd.nextInt(20) == 0) { 
       Thread.sleep(300); 
      } 

Удалить это.

  selector.select(); 

Это должно быть в верхней части петли, а не снизу.

+0

Вы знаете, что это минимальный пример? Случай read() -> -1 не имеет значения. порядок байтов/clear/limit может быть избыточным, но все же это не так. Thread.sleep * является важным пунктом здесь. Поскольку я хочу вызвать проблему, замеченную в более сложной программе. Я знаю, что здесь это не нужно. если выбор находится в конце, первая итерация будет иметь пустой набор выбранных клавиш. – fbenoit

+0

stackoverflow не позволял мне вставлять код. Поэтому я отправил по ссылке. – fbenoit

+0

Из javadoc ByteBuffer.allocate: «Позиция нового буфера будет равна нулю, ее предел будет ее емкостью». Это означает, что требуется ограничение (0) * *. – fbenoit

Смежные вопросы