Переполнение буфера в httperf при высокой скорости
Я использую httperf 0.9.0 ( скачано из Google Code) в Ubuntu 12.04.1 LTS 64-bit с 2CPU и 4 ГБ оперативной памяти. Я пытаюсь протестировать веб-серверы, но столкнулся со следующей проблемой переполнения буфера.
Терминальная команда:
httperf --timeout=5 --client=0/1 --server=localhost --port=9090 --uri=/?value=benchmarks --rate=1200 --send-buffer=4096 --recv-buffer=16384 --num-conns=5000 --num-calls=10
После нескольких секунд работы происходит сбой:
*** buffer overflow detected ***: httperf terminated
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7f1f5efa1007]
/lib/x86_64-linux-gnu/libc.so.6(+0x107f00)[0x7f1f5ef9ff00]
/lib/x86_64-linux-gnu/libc.so.6(+0x108fbe)[0x7f1f5efa0fbe]
httperf[0x404054]
httperf[0x404e9f]
httperf[0x406953]
httperf[0x406bd1]
httperf[0x40639f]
httperf[0x4054d5]
httperf[0x40285e]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7f1f5eeb976d]
httperf[0x4038f1]
======= Memory map: ========
...
...
7f1f5fd74000-7f1f5fd79000 rw-p 00000000 00:00 0
7f1f5fd91000-7f1f5fd95000 rw-p 00000000 00:00 0
7f1f5fd95000-7f1f5fd96000 r--p 00022000 08:03 4849686 /lib/x86_64-linux-gnu/ld-2.15.so
7f1f5fd96000-7f1f5fd98000 rw-p 00023000 08:03 4849686 /lib/x86_64-linux-gnu/ld-2.15.so
7fff10452000-7fff10473000 rw-p 00000000 00:00 0 [stack]
7fff1054f000-7fff10550000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Aborted
Я проверил файл дампа ядра с помощью gdb следующим образом:
(gdb) list
198 event_signal (EV_PERF_SAMPLE, 0, callarg);
199
200 /* prepare for next sample interval: */
201 perf_sample_start = timer_now ();
202 timer_schedule (perf_sample, regarg, RATE_INTERVAL);
203 }
204
205 int
206 main (int argc, char **argv)
207 {
(gdb) bt
#0 0x00007f33d4643445 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x00007f33d4646bab in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2 0x00007f33d4680e2e in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#3 0x00007f33d4716007 in __fortify_fail () from /lib/x86_64-linux-gnu/libc.so.6
#4 0x00007f33d4714f00 in __chk_fail () from /lib/x86_64-linux-gnu/libc.so.6
#5 0x00007f33d4715fbe in __fdelt_warn () from /lib/x86_64-linux-gnu/libc.so.6
#6 0x0000000000404054 in set_active (s=<optimized out>, fdset=0x612bc0) at core.c:367
#7 0x0000000000404e9f in core_connect (s=0x17e7100) at core.c:980
#8 0x0000000000406953 in make_conn (arg=...) at conn_rate.c:64
#9 0x0000000000406bd1 in tick (t=<optimized out>, arg=...) at rate.c:94
#10 0x000000000040639f in timer_tick () at timer.c:104
#11 0x00000000004054d5 in core_loop () at core.c:1255
#12 0x000000000040285e in main (argc=11, argv=<optimized out>) at httperf.c:971
Я немного отследил исходный код и обнаружил, что FD_SET
кажется, причина.
Наконец, для более низких ставок (например, --rate=100
или же --rate=500
) httperf работает отлично. Я тестирую различные веб-серверы, и скорость, вызывающая сбой, отличается. Мои ставки варьируются от 100 до 1200.
Более подробно, на самом деле я пытаюсь повторить эксперименты, проведенные Роберто Остинелли, и я уже настроил параметры TCP и применил патч, упомянутый в его сообщении в блоге.
Есть идеи о том, что вызывает эту проблему? Спасибо!
4 ответа
Вы пытаетесь использовать fd> 1024. При низких нагрузках вам не нужно / не используйте столько fds. При высоких нагрузках вам нужно больше fds и, в конечном итоге, получить 1024, и это вызывает проблему.
Даже когда я увеличиваю __FD_SETSIZE, я получаю эту проблему, поэтому я думаю, что на самом деле есть ошибка в любом коде, выполняющем проверку привязки (gcc/llvm?)
Более новые версии glibc выполняют свои собственные проверки для внутреннего FD_SET (вызванного из httperf), и эти проверки завершаются неудачно, вызывая прерывание. Несмотря на то, что httperf был создан с другим __FD_SET_SIZE, glibc все еще использовал исходный файл, с которым он был скомпилирован.
Чтобы обойти это, я откопал старую версию sys/select.h и bits / select.h еще до того, как __FD_ELT выполнил проверки, и поместил их в каталог sperc / httperf (в битах sys / и). Таким образом, httperf использует старые макросы FD_SET, которые не выполняют проверки, которые вызывают прерывание. Я использовал glibc-2.14.1, но любая версия, в которой нет битов /select2.h, должна сработать.
Мой друг собирает эти и другие исправления для httperf для нашего собственного использования (и вашего!) По адресу https://github.com/klueska/httperf
У меня был похожий сбой, и в моем случае замена select() на epoll() решила его.
Я смог воспроизвести проблему только при компиляции релиза (я использую eclipse как devenv, я позволил eclipse установить параметры компилятора как для отладки, так и для релиза).
Вот авария:
1354813976/SBNotificationServer terminated
06/12 21:13:54 > ======= Backtrace: =========
06/12 21:13:54 > /lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7f4e10f90807]
06/12 21:13:54 > /lib/x86_64-linux-gnu/libc.so.6(+0x109700)[0x7f4e10f8f700]
06/12 21:13:54 > /lib/x86_64-linux-gnu/libc.so.6(+0x10a7be)[0x7f4e10f907be]
06/12 21:13:54 > 1354813976/SBNotificationServer[0x49db90]
06/12 21:13:54 > 1354813976/SBNotificationServer[0x49de05]
06/12 21:13:54 > 1354813976/SBNotificationServer[0x4a4b07]
06/12 21:13:54 > 1354813976/SBNotificationServer[0x4a5318]
06/12 21:13:54 > 1354813976/SBNotificationServer[0x4a2628]
06/12 21:13:54 > /lib/x86_64-linux-gnu/libpthread.so.0(+0x7e9a)[0x7f4e10c70e9a]
06/12 21:13:54 > /lib/x86_64-linux-gnu/libc.so.6(clone+0x6d)[0x7f4e10f79cbd]
06/12 21:13:54 > ======= Memory map: ========
06/12 21:13:54 > 00400000-00507000 r-xp 00000000 ca:01 141328 /usr/share/spotbros/SBNotServer/1354813976/SBNotificationServer
06/12 21:13:54 > 00706000-00707000 r--p 00106000 ca:01 141328 /usr/share/spotbros/SBNotServer/1354813976/SBNotificationServer
06/12 21:13:54 > 00707000-00708000 rw-p 00107000 ca:01 141328 /usr/share/spotbros/SBNotServer/1354813976/SBNotificationServer
06/12 21:13:54 > 00708000-0070d000 rw-p 00000000 00:00 0
06/12 21:13:54 > 0120d000-01314000 rw-p 00000000 00:00 0 [heap]
06/12 21:13:54 > 7f49f8000000-7f49f8021000 rw-p 00000000 00:00 0
.......
.......
После этого мне удалось создать дамп ядра, и я получил:
warning: Can't read pathname for load map: Input/output error.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./SBNotificationServer ../config.xml'.
Program terminated with signal 6, Aborted.
0 0x00007feb90d67425 in raise () from /lib/x86_64-linux-gnu/libc.so.6
(gdb)
(gdb) bt
0 0x00007feb90d67425 in raise () from /lib/x86_64-linux-gnu/libc.so.6
1 0x00007feb90d6ab10 in abort () from /lib/x86_64-linux-gnu/libc.so.6
2 0x00007feb90da539e in ?? () from /lib/x86_64-linux-gnu/libc.so.6
3 0x00007feb90e3b807 in __fortify_fail () from /lib/x86_64-linux-gnu/libc.so.6
4 0x00007feb90e3a700 in __chk_fail () from /lib/x86_64-linux-gnu/libc.so.6
5 0x00007feb90e3b7be in __fdelt_warn () from /lib/x86_64-linux-gnu/libc.so.6
6 0x000000000049e290 in CPhpResponseReader::WaitUntilReadable(int, int&, bool&) ()
7 0x000000000049e505 in CPhpResponseReader::Read(CReallocableBuffer&, int) ()
8 0x00000000004a5207 in CHttpPostBufferInterface::Flush() ()
9 0x00000000004a5a18 in CPhpRequestJob::Execute() ()
10 0x00000000004a2d28 in CThreadPool::Worker(void*) ()
11 0x00007feb90b1be9a in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
12 0x00007feb90e24cbd in clone () from /lib/x86_64-linux-gnu/libc.so.6
БОЛЬШЕ ДЕТАЛЕЙ:
WaitUntilReadable (см. Обратный след) - это функция, которая в основном использует select() для ожидания чтения некоторых данных с веб-сервера. Я переписал эту функцию снова, используя вместо этого epoll().
Моя программа - это сервер, который поддерживает соединения с тысячами клиентов. Клиенты отправляют запросы на сервер, а затем сервер передает эти запросы веб-серверу и отправляет ответ клиентам. Для запросов к веб-серверу на моем сервере есть пул потоков. Итак, вот еще одна важная деталь: сбой происходит, если я установил большое число потоков в пуле потоков, что означает, что одновременно выполняется много вызовов select() (я воспроизвел его, установив его в 1024 потока). Затем я запустил симулятор клиента, который я создал для тестирования, и запустил 1000 клиентских потоков, выполняющих запросы так быстро, как только мог. Это привело к быстрому падению при использовании select() и скомпилировано в релизе, но без проблем после введения epoll().
Обратите внимание, что каждый поток в пуле выполняет вызов select() с 1 fd.
Вот функция, которую я изменил, чтобы решить эту проблему:
bool WaitUntilReadableDeprecated(int timeoutMSecs, int& elapsedMSecs, bool& closed)
{
CTimeLapsus t;
fd_set fileDescriptorSet;
struct timeval timeStruct;
closed = false;
timeStruct.tv_sec = timeoutMSecs / 1000;
timeStruct.tv_usec = 0;
FD_ZERO(&fileDescriptorSet);
FD_SET(m_socket, &fileDescriptorSet);
int sel = select(m_socket + 1, &fileDescriptorSet, NULL, NULL, &timeStruct);
if(sel == 0)
{
LogDebug("[CPhpResponseReader::WaitUntilReadable] select() returned 0, no data available");
elapsedMSecs = t.GetElapsedMilis();
return false;
}
else if(sel == -1)
{
if(errno == EBADF)
{
LogDebug("[CPhpResponseReader::WaitUntilReadable] select() returned -1, errno is EBADF, connection reset by host?");
closed = true;
elapsedMSecs = t.GetElapsedMilis();
return true;
}
throw "CPhpResponseReader::WaitUntilReadable select error";
}
elapsedMSecs = t.GetElapsedMilis();
return true;
}
bool WaitUntilReadableEpoll(int timeoutMSecs, int& elapsedMSecs, bool& closed)
{
CIoPoller poller(8);
CTimeLapsus t;
closed = false;
if(poller.Add(m_socket, EPOLLIN) == -1)
LogError("[CPhpResponseReader::WaitUntilReadableEpoll] poller.Add(%d, EPOLLIN) failed", m_socket);
int nfds = poller.Wait(timeoutMSecs);
if (nfds > 0)
{
int theSocket = poller.GetEvents()[0].data.fd;
uint32_t event = poller.GetEvents()[0].events;
if(theSocket != m_socket)
{
LogError("[CPhpResponseReader::WaitUntilReadableEpoll] socket is different than expected", m_socket);
elapsedMSecs = t.GetElapsedMilis();
return false;
}
if((event & EPOLLERR) || (event & EPOLLHUP))
{
LogWarning("[CPhpResponseReader::WaitUntilReadableEpoll] Disconnected socket %d (event %d)", m_socket, event);
elapsedMSecs = t.GetElapsedMilis();
closed = true;
return false;
}
if(event & EPOLLIN)
{
// ok
}
}
else if (nfds == -1)
{
if(errno == EBADF)
{
LogWarning("[CPhpResponseReader::WaitUntilReadableEpoll] poller.Wait() returned -1, errno is EBADF, maybe connection reset by host");
closed = true;
elapsedMSecs = t.GetElapsedMilis();
return true;
}
LogError("[CPhpResponseReader::WaitUntilReadableEpoll] poller.Wait() failed");
elapsedMSecs = t.GetElapsedMilis();
closed = true;
return false;
}
else
{
LogDebug("[CPhpResponseReader::WaitUntilReadableEpoll] poller.Wait() returned 0, no data available");
elapsedMSecs = t.GetElapsedMilis();
return false;
}
elapsedMSecs = t.GetElapsedMilis();
return true;
}
CIoPoller - это просто оболочка C++ для epoll.
Версия Ubuntu:
Distributor ID: Ubuntu
Description: Ubuntu 12.04.1 LTS
Release: 12.04
Я встречаю тот же случай переполнения буфера:
httperf --hog --wsess=1,3000,1 --rate 1 --burst-length=3000 --max-piped-calls 1 --max-connections=3000 --timeout 20 --server 127.0.0.1 --port 8888 --uri /
посмотрите на: https://gist.github.com/diyism/9586262