LINUX.ORG.RU

[C++] curl_multi + pthreads = segfault

 


0

0

Стоит задача написания fastcgi-обработчика, который по запросу вытягивает некие данные из сети и отдает наружу. Данные необходимо получать параллельно. В среднем живет около 400-500 потоков, каждый тянет порядка 10 источников.

Проблема в том что похоже при такой конфигурации curl_multi рушит мне стек - приведенный ниже пример падает на совершенно случайных местах и случайными ошибками. Может кто-либо сталкивался с этим или хотя бы протестирует указанный код? У меня на curl 7.20 и amd64 - segfault в течении 1-5 секунд после запуска. Что самое печальное - под valgrind'ом сегфолтов нет, что очень затрудняет отладку (скорее всего тормозной valgrind не может обеспечить должный уровень concurrency потоков). Сам код:

#include <iostream>
#include <curl/curl.h>
#include <cstdlib>

#define THREADS 400
#define MULTI_COUNT 20

using namespace std;

static int writer(char *data, size_t size, size_t nmemb, std::string *buffer)
{
	int result = 0;
	if (buffer != NULL)
	{
		buffer->append(data, size * nmemb);
		result = size * nmemb;
	}
	return result;
}



void doCURL(CURLM* multi_handle, CURL** easy_handles, std::string *memory, char** envp) {
//	Query* queryParams = initQuery(envp);

	for (int i = 0; i < MULTI_COUNT; i++) {
		easy_handles[i]=curl_easy_init();
		curl_easy_setopt(easy_handles[i],CURLOPT_URL, "http://localhost/");
		curl_easy_setopt(easy_handles[i],CURLOPT_WRITEFUNCTION, writer);
		curl_easy_setopt(easy_handles[i],CURLOPT_TIMEOUT,15);
		curl_easy_setopt(easy_handles[i],CURLOPT_WRITEDATA,&memory[i]);
		curl_easy_setopt(easy_handles[i], CURLOPT_NOSIGNAL, 1);
		curl_multi_add_handle(multi_handle,easy_handles[i]);
	}

	int still_running = 0;
	while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle, &still_running));

	while(still_running) {
		struct timeval timeout;
		int rc;

		fd_set fdread;
		fd_set fdwrite;
		fd_set fdexcep;
		int maxfd;

		FD_ZERO(&fdread);
		FD_ZERO(&fdwrite);
		FD_ZERO(&fdexcep);

		timeout.tv_sec = 1;
		timeout.tv_usec = 0;


		CURLMcode res = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
		if (res) {
			std::cout << "You are doomed: "<< curl_multi_strerror(res) << "\n";
		}

		if (maxfd != -1) {
			rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

			switch(rc) {
			case -1:
			break;
			case 0:
			default:
				while(CURLM_CALL_MULTI_PERFORM ==
						curl_multi_perform(multi_handle, &still_running));
				break;
			}
		}
		int run;
        while (NULL != curl_multi_info_read(multi_handle,&run)) ;
	}
}


static void* thread_main(void* a) {
	/*static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER;
	pthread_mutex_lock(&accept_mutex);
	curl_global_init(0);
	pthread_mutex_unlock(&accept_mutex);
	*/

	std::string* memory = new std::string[MULTI_COUNT];
	CURL** easy_handles = new CURL*[MULTI_COUNT]; //создаем массив хэндлов


	for (;;) {
		CURLM* multi_handle = curl_multi_init();
		doCURL(multi_handle, easy_handles,memory, NULL);
		for (int i = 0; i < MULTI_COUNT; i++) {
			memory[i].clear();
			curl_multi_remove_handle(multi_handle, easy_handles[i]);
			curl_easy_cleanup(easy_handles[i]);
		}
		curl_multi_cleanup(multi_handle);
	        sleep(1);
        }
	return NULL;
}


int main(int argc, char* const argv[] ) {
	pthread_t* id = new pthread_t[THREADS];
	for (int i = 1; i < THREADS; i++) {
		pthread_create(&id[i], NULL, thread_main, NULL);
		if (i % 50 == 0) {
			std::cout <<"50 spawned"<<std::endl;
		}
	}
	std::cout <<"ALL spawned"<<std::endl;
	thread_main(0);

	return 0;
}

Если у вас не сегфолтится - попробуйте убрать sleep.


Под Karmic i386 не падает независимо от sleep.

asaw ★★★★★
()

man curl_global_init

This function must be called at least once within a program (a program is all the code that shares a memory space) before the program calls any other function in libcurl.

MULTI_COUNT * THREADS = 8000. Предлагаю подумать, почему:

1. закачка в 8000 потоков не заработает без соответствуещего setrlimit

2. select не подходит для дескрипторов со значениями большим или равным FD_SETSIZE

linuxfan
()
Ответ на: комментарий от linuxfan

select не подходит для дескрипторов со значениями большим или равным FD_SETSIZE


Это как?!

anonymous
()
Ответ на: комментарий от linuxfan

лимиты подкручены на уровне /etc/security/limits.conf (на предмет количества сокетов)

про select поподробнее

xanf
() автор топика
Ответ на: комментарий от linuxfan

curl_global_init пробовал. Толку никакого. Да и сами они часто его не упоминают почему-то даже в своих примерах

xanf
() автор топика
Ответ на: комментарий от xanf

>про select поподробнее

Посмотри уже объявление структуры fd_set в sys/select.h. Это массив. А потом погугли по слову «FD_SETSIZE».

лимиты подкручены на уровне /etc/security/limits.conf (на предмет количества сокетов)

У тебя не совсем корректное представление: это ограничение не на количество сокетов, а на количество одновременно открытых файловых дескрипторов в процессе. stdin/stdout/stderr также входят в их число.

linuxfan
()
Ответ на: комментарий от linuxfan

Про «количество сокетов» - согласен, мой ляп. Думал о сокетах, про них и написал. Что такое stdin/stdout/stderr - я понимаю (0,1,2 ^_^ )

А вот за FD_SETSIZE - большое спасибо, просветился. Только вот судя по реализации curl_multi - там не потоки, а пайпы используются - а ulimit -n у меня 20000 - так что проблема наверное не там.

xanf
() автор топика
Ответ на: комментарий от xanf

>так что проблема наверное не там

Думаю, что все-таки проблема в сокетах. Если действительно открывается более 1024 сокетов (это FD_SETSIZE по умолчанию), вызов curl_multi_fdset и поганит стек, устанавливая биты за пределами массива.

linuxfan
()
Ответ на: комментарий от xanf

А кто тебе сказал, что #define FD_SETSIZE определен внутри блока #ifndef FD_SETSIZE? Поставь printf(«%d\n», FD_SETSIZE) и убедись сам.

linuxfan
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.