boost::program_options, libev и ctrl+c

darkstarx
Дата: 02.10.2014 15:42:09
Простите за мою тупость и лень самостоятельно разобраться, но может кто-то просто встречался с такой проблемой.
Использую в консольном приложении boost::program_options вместе с libev. Вешаю на ev::sig колбэк для обработки SIGINT. В программе один поток.
Все работает здорово, когда я запускаю приложение без параметров: по ctrl+c приложение благополучно завершается и отдает консоль для дальнейшей работы. Но если запустить, указав в командной строке параметры, то первый ctrl+c останавливает приложение, но процесс продолжает висеть после выхода из main (кажется его держит epoll), второй ctrl+c вырубает процесс окончательно.

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

код см. ниже

namespace po = boost::program_options;
int main(int argc, char * argv[])
{
	FLAGS_logtostderr = true;
	FLAGS_colorlogtostderr = true;
	google::InitGoogleLogging(argv[0]);

	int c_port_no, w_port_no;

	po::options_description desc("Allowed options");
	desc.add_options()
		("help,h", "produce help message.")
		("clients-port,cp", po::value<int>(&c_port_no)->default_value(1025), "The port to listen clients on. Must be greater than 1024. Default is 1025.")
		("worlds-port,wp", po::value<int>(&w_port_no)->default_value(1026), "The port to listen worlds on. Must be greater than 1024. Default is 1026.")
	;

	po::variables_map vm;
	try {
		po::store(po::parse_command_line(argc, argv, desc), vm);
	} catch (boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<po::unknown_option> > e) {
		std::cout << "Unknown parameters" << std::endl;
		std::cout << desc << std::endl;
		return 1;
	} catch (...) {
		std::cout << desc << std::endl;
		return 1;
	}
	po::notify(vm);

	if (vm.count("help")) {
		std::cout << desc << std::endl;
		return 1;
	}

	if (c_port_no <= 1024 || w_port_no <= 1024) {
		std::stringstream str;
		str << desc;
		fprintf(stderr, " bad port number: %i\n usage: %s [options]\n%s\n", c_port_no <= 1024 ? c_port_no : w_port_no, argv[0], str.str().c_str());
		return 1;
	}

	ev::default_loop loop;
	// Запускаем сервер
	Server::instance().start(c_port_no, w_port_no);
	// Запускаем цикл ожидания сигналов от сокетов и системы
	loop.run(0);
	// Останавливаем сервер и завершаем приложение
	Server::instance().stop();
	LOG(INFO) << "Application terminated normally.";
	return 0;
}


так отлавливаю сигналы:
void Server::start(const int c_port, const int w_port)
{
	if (active) return;

	assert(c_port > 1024);
	assert(w_port > 1024);
	assert(c_port != w_port);

	if (active) stop();
	c_portno = c_port;
	w_portno = w_port;

	c_sockfd = socket(PF_INET, SOCK_STREAM, 0);
	w_sockfd = socket(PF_INET, SOCK_STREAM, 0);

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	
	addr.sin_port = htons(c_port);
	if (bind(c_sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
		LOG(FATAL) << "Server starting: binding clientside socket with address";
	}
	fcntl(c_sockfd, F_SETFL, fcntl(c_sockfd, F_GETFL, 0) | O_NONBLOCK);

	addr.sin_port = htons(w_port);
	if (bind(w_sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
		LOG(FATAL) << "Server starting: binding worldside socket with address";
	}
	fcntl(w_sockfd, F_SETFL, fcntl(w_sockfd, F_GETFL, 0) | O_NONBLOCK);

	listen(c_sockfd, 5);
	listen(w_sockfd, 5);

	cio.set<Server, &Server::accept_cb>(this);
	cio.start(c_sockfd, ev::READ);
	wio.set<Server, &Server::accept_w_cb>(this);
	wio.start(w_sockfd, ev::READ);

	sig.set<&Server::signal_cb>();
	sig.start(SIGINT);

	active = true;
	LOG(INFO) << "Server started. Listening clients on port " << c_port << ", worlds on port " << w_port;
}

void Server::signal_cb(ev::sig &signal, int revents)
{
	LOG(INFO) << "Catch interruption.";
	signal.loop.break_loop();
}


и так останавливаю приложение:
void Server::stop()
{
	if (!active) return;

	cio.stop();
	shutdown(c_sockfd, SHUT_RDWR);
	close(c_sockfd);
	wio.stop();
	shutdown(w_sockfd, SHUT_RDWR);
	close(w_sockfd);
	sig.stop();

	active = false;
	LOG(INFO) << "Server stopped.";
}
MasterZiv
Дата: 02.10.2014 17:00:23
darkstarx
Простите за мою тупость и лень самостоятельно разобраться, но может кто-то просто встречался с такой проблемой.
Использую в консольном приложении boost::program_options вместе с libev. Вешаю на ev::sig колбэк для обработки SIGINT. В программе один поток.
Все работает здорово, когда я запускаю приложение без параметров: по ctrl+c приложение благополучно завершается и отдает консоль для дальнейшей работы.



Сразу недоумевающий вопрос -- а зачем для того, чтобы по ctrl+c приложение благополучно завершилось и отдало консоль для дальнейшей работы, нужно вешать какие-то сигналы и использовать какую-то билбиотеку ?

Это -- поведение С-шной программы по умолчанию. Вот чтобы приложение НЕ завершалось по Ctrl-C -- надо постараться.
MasterZiv
Дата: 02.10.2014 17:07:12
darkstarx , возможно, приложение в этом случае действительно уходит в глубокий IO, из которого даже kill -9 иногда может не выводить. Но что-то я в этом немного сомневаюсь.
Или что-то не так с логикой потока управления в случае, когда параметры есть.
В любом случае, других идей у меня нет.
darkstarx
Дата: 02.10.2014 19:58:42
MasterZiv
Сразу недоумевающий вопрос -- а зачем для того, чтобы по ctrl+c приложение благополучно завершилось и отдало консоль для дальнейшей работы, нужно вешать какие-то сигналы и использовать какую-то билбиотеку ?

Это -- поведение С-шной программы по умолчанию.


Не, это мелочь, просто чтобы корректно деинициализировать сервис. Можно не ловить этот сигнал, тогда не вызовится Server::stop(), не выполнится деструктор Server::~Server() и не закроютя сокеты, не сдампится лог например какой-то, и всё такое, на что расчитана внутренняя логика этого класса и вообще сервиса.

Если не перехватывать этот сигнал, то процесс завершается тут же :) Но я так не хочу. Хочу, при завершении выполнить ряд действий, но чтобы и дважды SIGINT не нужно было посылать процессу.
Проблема именно в этом.

MasterZiv
Вот чтобы приложение НЕ завершалось по Ctrl-C -- надо постараться.

Просто не останавливаем наш event loop при обработке этого сигнала (см. Server::signal_cb). Не знаю, правда зачем так делать.

MasterZiv
возможно, приложение в этом случае действительно уходит в глубокий IO, из которого даже kill -9 иногда может не выводить. Но что-то я в этом немного сомневаюсь.
Или что-то не так с логикой потока управления в случае, когда параметры есть.


Да, gdb указывает на подвисший epoll:

!darkstarx@darkstarx:~/projects/server/build$ gdb --args ./server -c 1935
GNU gdb (Debian 7.7.1+dfsg-3) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./server...done.
(gdb) r
Starting program: /home/darkstarx/projects/server/build/server -c 1935
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
I1002 19:54:33.873543 30230 Server.cpp:91] Server started. Listening clients on port 1935, worlds on port 1026
^C
Program received signal SIGINT, Interrupt.
0x00007ffff6607573 in __epoll_wait_nocancel () at ../sysdeps/unix/syscall-template.S:81
81      ../sysdeps/unix/syscall-template.S: Нет такого файла или каталога.
(gdb) bt
#0  0x00007ffff6607573 in __epoll_wait_nocancel () at ../sysdeps/unix/syscall-template.S:81
#1  0x00007ffff731edd5 in ?? () from /usr/lib/x86_64-linux-gnu/libev.so.4
#2  0x00007ffff7321068 in ev_run () from /usr/lib/x86_64-linux-gnu/libev.so.4
#3  0x000000000044212f in ev::loop_ref::run (this=0x7fffffffe320, flags=0) at /usr/include/ev++.h:211
#4  0x0000000000441d28 in main (argc=3, argv=0x7fffffffe4d8) at /home/darkstarx/projects/server/main.cpp:53


но я нуб в этом (( что за __epoll_wait_nocancel и чего он ждет?..

MasterZiv
В любом случае, других идей у меня нет.

Все равно спасибо за участие