Zbudowałem bibliotekę C++ używając ASIO doładowania. Biblioteka musi być zarówno bezpieczna dla wątków, jak i bezpieczna dla widżetów. Ma wątek programu do planowania usług, który wywołuje io_service::run()
. Aby wesprzeć bezpieczeństwo widelca, zarejestrowałem obsługę pre_fork, post_fork_parent i post_fork_child. Obsługa pre_fork()
dzwoni _io_service.notify_fork(boost::io_service:fork_prepare()
, post_fork_parent obsługi połączeń _io_service.notify_fork(boost::asio::io_service::fork_parent)
i post_fork_child połączeń _io_service.notify_fork(boost::asio::io_service::fork_child)
.Jak sprawić, by Asio rozwidlało się bezpieczniej
Problem, z którym mam do czynienia, kiedy występuje fork()
, wątek programu do planowania usług może znajdować się w środku pewnej operacji i mógł uzyskać blokadę elementów danych obiektu io_service
. Tak więc proces potomny widzi je w tym samym stanie i w post_fork_child(), gdy wywołujemy _io_service.notify_fork(boost::asio::io_service::fork_child)
próbuje uzyskać blokadę na tym samym obiekcie i tym samym zostaje zablokowany na czas nieokreślony (ponieważ nie ma wątku w pliku potomnym, aby zwolnić odblokowanie).
Ślad stosu widzę w procesie potomnym, który jest zablokowany, to -
fffffd7ffed07577 lwp_park (0, 0, 0)
fffffd7ffecffc18 mutex_lock_internal() + 378
fffffd7ffecfffb2 mutex_lock_impl() + 112
fffffd7ffed0007b mutex_lock() + b
fffffd7fff26419d __1cFboostEasioGdetailLscoped_lock4n0CLposix_mutex__2t5B6Mrn0D__v_() + 1d
fffffd7fff2866a2 __1cFboostEasioGdetailQdev_poll_reactorMfork_service6Mn0BKio_serviceKfork_event__v_() + 32
fffffd7fff278527 __1cFboostEasioGdetailQservice_registryLnotify_fork6Mn0BKio_serviceKfork_event__v_() + 107
fffffd7fff27531c __1cDdesGtunnelQServiceSchedulerPpost_fork_child6M_v_() + 1c
fffffd7fff29de24 post_fork_child() + 84
fffffd7ffec92188 _postfork_child_handler() + 38
fffffd7ffecf917d fork() + 12d
fffffd7ffec172d5 fork() + 45
fffffd7ffef94309 fork() + 9
000000000043299d main() + 67d
0000000000424b2c ????????()
Widocznie „dev_poll_reactor” jest zablokowana (ponieważ wydaje się być wysyłając jakieś oczekujące Wydarzenia) w wątku harmonogramu usługi kiedy nastąpiło rozwidlenie, które powoduje problem.
Myślę, że aby rozwiązać ten problem, muszę się upewnić, że wątek programu do obsługi usług nie znajduje się w trakcie przetwarzania, gdy widelec się wydarzy, i jednym ze sposobów zagwarantowania, że będzie to wywołanie io_service.stop()
w procedurze pre_fork(), ale to robi brzmi jak dobre rozwiązanie. Czy mógłbyś mi powiedzieć, jakie jest właściwe podejście, aby rozwidlenie biblioteki było bezpieczne?
Fragmenty kodu wyglądają mniej więcej tak.
/**
* Combines Boost.ASIO with a thread for scheduling.
*/
class ServiceScheduler : private boost::noncopyable
{
public :
/// The actual thread used to perform work.
boost::shared_ptr<boost::thread> _service_thread;
/// Service used to manage async I/O events
boost::asio::io_service _io_service;
/// Work object to block the ioservice thread.
std::auto_ptr<boost::asio::io_service::work> _work;
...
};
/**
* CTOR
*/
ServiceScheduler::ServiceScheduler()
: _io_service(),
_work(std::auto_ptr<boost::asio::io_service::work>(
new boost::asio::io_service::work(_io_service))),
_is_running(false)
{
}
/**
* Starts a thread to run async I/O service to process the scheduled work.
*/
void ServiceScheduler::start()
{
ScopedLock scheduler_lock(_mutex);
if (!_is_running) {
_is_running = true;
_service_thread = boost::shared_ptr<boost::thread>(
new boost::thread(boost::bind(
&ServiceScheduler::processServiceWork, this)));
}
}
/**
* Processes work passed to the ASIO service and handles uncaught
* exceptions
*/
void ServiceScheduler::processServiceWork()
{
try {
_io_service.run();
}
catch (...) {
}
}
/**
* Pre-fork handler
*/
void ServiceScheduler::pre_fork()
{
_io_service.notify_fork(boost::asio::io_service::fork_prepare);
}
/**
* Post-fork parent handler
*/
void ServiceScheduler::post_fork_parent()
{
_io_service.notify_fork(boost::asio::io_service::fork_parent);
}
/**
* Post-fork child handler
*/
void ServiceScheduler::post_fork_child()
{
_io_service.notify_fork(boost::asio::io_service::fork_child);
}
Używam doładowania 1.47 i uruchomienia aplikacji w systemie Solaris i386. Biblioteka i aplikacja zostały zbudowane przy użyciu studio-12.0.
Czy spodziewasz się wykonać cokolwiek innego wywołując exec() lub _exit() w urządzeniu po wywołaniu fork? Jeśli tak, powinieneś ponownie rozważyć. Jeśli nie, nie widzę problemu. – janm
Możesz zarezerwować główny wątek tylko do administrowania, zadań interfejsu poleceń i obsługi rodzic-dziecko. Po rozwidleniu w wątku istnieje tylko główny wątek. Możesz przechowywać wewnętrzne dane konfiguracyjne do przywracania i tworzyć potrzebne wątki w procesie potomnym. W ten sposób zapewnia czystą hermetyzację i pozwala uniknąć blokowania. –
Po próbie użycia boost :: asio dla dwóch projektów, doszedłem do wniosku, że lepiej nie używać boostu. Odbija się nawet na prostych przykładach. Jego skomplikowana struktura szablonów jest nadmiernie trudna do zrozumienia i niemożliwa do znacznego przekroczenia oraz określenia nawet prawdopodobnej przyczyny. – wallyk