Я настроил neo4j на сервере A, и у меня есть приложение, работающее на сервере B, которое должно подключаться к нему.
Если я клонирую приложение на сервер A и запускаю модульные тесты, оно работает нормально. Но запустив их на сервере B, установка выполняется в течение 30 секунд и завершается с ошибкой IncompleteRead:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/nose-1.3.1-py2.7.egg/nose/suite.py", line 208, in run
self.setUp()
File "/usr/local/lib/python2.7/site-packages/nose-1.3.1-py2.7.egg/nose/suite.py", line 291, in setUp
self.setupContext(ancestor)
File "/usr/local/lib/python2.7/site-packages/nose-1.3.1-py2.7.egg/nose/suite.py", line 314, in setupContext
try_run(context, names)
File "/usr/local/lib/python2.7/site-packages/nose-1.3.1-py2.7.egg/nose/util.py", line 469, in try_run
return func()
File "/comps/comps/webapp/tests/__init__.py", line 19, in setup
create_graph.import_films(films)
File "/comps/comps/create_graph.py", line 49, in import_films
batch.submit()
File "/usr/local/lib/python2.7/site-packages/py2neo-1.6.3-py2.7-linux-x86_64.egg/py2neo/neo4j.py", line 2643, in submit
return [BatchResponse(rs).hydrated for rs in responses.json]
File "/usr/local/lib/python2.7/site-packages/py2neo-1.6.3-py2.7-linux-x86_64.egg/py2neo/packages/httpstream/http.py", line 563, in json
return json.loads(self.read().decode(self.encoding))
File "/usr/local/lib/python2.7/site-packages/py2neo-1.6.3-py2.7-linux-x86_64.egg/py2neo/packages/httpstream/http.py", line 634, in read
data = self._response.read()
File "/usr/local/lib/python2.7/httplib.py", line 532, in read
return self._read_chunked(amt)
File "/usr/local/lib/python2.7/httplib.py", line 575, in _read_chunked
raise IncompleteRead(''.join(value))
IncompleteRead: IncompleteRead(131072 bytes read)
-------------------- >> begin captured logging << --------------------
py2neo.neo4j.batch: INFO: Executing batch with 2 requests
py2neo.neo4j.batch: INFO: Executing batch with 1800 requests
--------------------- >> end captured logging << ---------------------
Исключение происходит, когда я отправляю достаточно большую партию. Если я уменьшу размер набора данных, он исчезнет. Кажется, это связано с размером запроса, а не с количеством запросов (если я добавлю свойства к узлам, которые я создаю, у меня может быть меньше запросов).
Если я использую batch.run()
вместо .submit()
, я не получаю ошибки, но тесты не проходят; кажется, что партия отклоняется молча. Если я использую .stream()
и не перебираю результаты, происходит то же самое, что и .run()
; если я перебираю их, я получаю ту же ошибку, что и .submit()
(за исключением того, что «прочитано 0 байтов»).
Глядя на httplib.py, можно предположить, что мы получим эту ошибку, когда ответ HTTP имеет Transfer-Encoding: Chunked
и не содержит размер фрагмента, который ожидается. Итак, я запустил tcpdump для тестов, и действительно, похоже, это то, что происходит. Последний фрагмент имеет длину 0x8000
, а его последние байты равны
"http://10.210.\r\n
0\r\n
\r\n
(Для ясности после \n добавлены разрывы строк.) Это выглядит как правильное разбиение на фрагменты, но 0x8000-й байт — это первый «/», а не второй «.». На восемь байт раньше. Это также не полный ответ, так как это недопустимый JSON.
Интересно, что внутри этого чанка мы получаем следующие данные:
"all_relatio\r\n
1280\r\n
nships":
То есть выглядит как начало нового чанка, но встроенного в старый. Этот новый фрагмент закончился бы в правильном месте (второй «.» выше), если бы мы заметили, что он начинается. А если заголовка фрагмента не было, старый фрагмент заканчивался в правильном месте (восемь байтов позже).
Затем я извлек POST-запрос пакета и запустил его, используя cat batch-request.txt | nc $SERVER_A 7474
. Ответ на это был действительным ответом HTTP с фрагментами, содержащим полный действительный объект JSON.
Я подумал, что, возможно, netcat отправляет запрос быстрее, чем py2neo, поэтому я добавил некоторое замедление.
cat batch-request.txt | perl -ne 'BEGIN { $| = 1 } for (split //) { select(undef, undef, undef, 0.1) unless int(rand(50)); print }' | nc $SERVER_A 7474
Но он продолжал работать, несмотря на то, что стал намного медленнее.
Я также пытался выполнить tcpdump на сервере A, но запросы к локальному хосту не проходят через tcp.
У меня все еще есть несколько путей, которые я не исследовал: я не выяснил, насколько надежно запрос завершается ошибкой или при каких именно условиях (однажды я видел, как это удается с пакетом, который обычно терпит неудачу, но я не исследовал границы ). И я не пробовал делать запрос напрямую из python, минуя py2neo. Но я не особенно ожидаю, что любой из них будет очень информативным. И я не смотрел внимательно на дамп TCP, за исключением использования wireshark «follow TCP stream» для извлечения HTTP-разговора; Я действительно не знаю, что я буду там искать. Есть большой раздел, который Wireshark выделяет черным цветом в неудачном дампе, и только отдельные строки черного цвета в успешном дампе, может быть, это имеет значение?
Так что пока: кто-нибудь знает, что может происходить? Что-нибудь еще я должен попытаться диагностировать проблему?
Дампы TCP находятся здесь: failed и успешно.
EDIT: я начинаю понимать неудачный дамп TCP. Весь разговор занимает ~ 30 секунд, и есть ~ 28-секундный промежуток, в течение которого оба сервера отправляют TCP-кадры ZeroWindow — это черные линии, о которых я упоминал.
Во-первых, py2neo заполняет окно neo4j; neo4j отправляет кадр с сообщением «мое окно заполнено», а затем еще один кадр, который заполняет окно py2neo. Затем мы тратим ~ 28 секунд, и каждый из них просто говорит: «Ага, мое окно все еще заполнено». В конце концов neo4j снова открывает свое окно, py2neo отправляет еще немного данных, а затем py2neo открывает свое окно. Оба они отправляют немного больше данных, затем py2neo заканчивает отправку своего запроса, а neo4j отправляет больше данных, прежде чем также закончить.
Поэтому я думаю, что, возможно, проблема в чем-то вроде того, что оба они отказываются обрабатывать больше данных, пока они не отправят еще немного, и ни один из них не может отправить еще немного, пока другой не обработает некоторые. В конце концов neo4j входит в цикл «что-то пошло не так», который py2neo интерпретирует как «продолжайте и отправьте больше данных».
Это интересно, но я не уверен, что это значит, что предпоследний кадр TCP, отправленный с neo4j на py2neo, начинается с \r\n1280\r\n
— начала поддельного чанка. \r\n8000\r\n
, с которого начинается настоящий фрагмент, просто появляется в середине ничем не примечательного кадра TCP. (Это был третий фрейм, отправленный после того, как py2neo завершил отправку почтового запроса.)
EDIT 2: я проверил, где именно висит python. Неудивительно, что это было при отправке запроса, поэтому BatchRequestList._execute()
не возвращается до тех пор, пока neo4j не сдастся, поэтому ни .run()
, ни .stream()
не справились лучше, чем .submit()
.