LCOV - code coverage report
Current view: top level - eventdispatcher - signal_child.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 87 126 69.0 %
Date: 2022-02-12 12:27:47 Functions: 19 26 73.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2012-2022  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/eventdispatcher
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software; you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation; either version 2 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License along
      17             : // with this program; if not, write to the Free Software Foundation, Inc.,
      18             : // 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      19             : 
      20             : /** \file
      21             :  * \brief Implementation of the Signal class.
      22             :  *
      23             :  * The Signal class listens for Unix signals to happen. This wakes us
      24             :  * up when the signal happens.
      25             :  */
      26             : 
      27             : // self
      28             : //
      29             : #include    "eventdispatcher/signal_child.h"
      30             : 
      31             : #include    "eventdispatcher/communicator.h"
      32             : #include    "eventdispatcher/exception.h"
      33             : 
      34             : 
      35             : // snaplogger lib
      36             : //
      37             : #include    <snaplogger/message.h>
      38             : 
      39             : 
      40             : // cppthread lib
      41             : //
      42             : #include    <cppthread/guard.h>
      43             : 
      44             : 
      45             : // snapdev lib
      46             : //
      47             : #include    <snapdev/not_used.h>
      48             : #include    <snapdev/safe_variable.h>
      49             : 
      50             : 
      51             : // C++ lib
      52             : //
      53             : #include    <iostream>
      54             : 
      55             : 
      56             : // C lib
      57             : //
      58             : #include    <string.h>
      59             : //#include    <signal.h>
      60             : 
      61             : 
      62             : // last include
      63             : //
      64             : #include    <snapdev/poison.h>
      65             : 
      66             : 
      67             : 
      68             : 
      69             : namespace ed
      70             : {
      71             : 
      72             : 
      73             : namespace
      74             : {
      75             : 
      76             : 
      77             : 
      78             : /** \brief This singleton is saved here once created.
      79             :  *
      80             :  * The signal_child object is a singleton. It is created the first time
      81             :  * you call the get_instance() function. It is used to handle the SIGCHLD
      82             :  * signal with any number of children from any library or function you
      83             :  * are running. This allows for one location to manage that specific
      84             :  * signal, but many location to handle the death of a child process.
      85             :  */
      86           2 : signal_child::pointer_t    g_signal_child = signal_child::pointer_t();
      87             : 
      88             : 
      89             : 
      90             : }
      91             : // no name namespace
      92             : 
      93             : 
      94             : 
      95             : 
      96             : /** \brief Initialize a child_status object.
      97             :  *
      98             :  * This constructor saves the wait info of the last waitid() call to this
      99             :  * object. It represents the current status of the child process.
     100             :  *
     101             :  * You can listen to changes to the status of a process. If the process
     102             :  * is still running, then you get a reply which says the child process
     103             :  * is not exited, signaled, or stopped. You can decide on which signal
     104             :  * your callback gets called.
     105             :  *
     106             :  * \param[in] info  The signal information as received from the system.
     107             :  */
     108          10 : child_status::child_status(siginfo_t const & info)
     109          10 :     : f_info(info)
     110             : {
     111          10 : }
     112             : 
     113             : 
     114             : /** \brief Return the PID of the concerned child.
     115             :  *
     116             :  * This function gives you the PID of the child that died. This is
     117             :  * particularly useful if you handle multiple children in the
     118             :  * same callback.
     119             :  *
     120             :  * \return The child that just received a status change.
     121             :  */
     122          44 : pid_t child_status::child_pid() const
     123             : {
     124          44 :     return f_info.si_pid;
     125             : }
     126             : 
     127             : 
     128             : /** \brief Return the UID of the user that was running the child.
     129             :  *
     130             :  * This function returns the user identifier of the real user who
     131             :  * was running the child process.
     132             :  *
     133             :  * \return The child process user identifier.
     134             :  */
     135           0 : uid_t child_status::child_uid() const
     136             : {
     137           0 :     return f_info.si_uid;
     138             : }
     139             : 
     140             : 
     141             : /** \brief Whether the status means the process is still up and running.
     142             :  *
     143             :  * This function returns true if the process did not exit, was not
     144             :  * signaled, and was not stopped. In all other circumstances, this
     145             :  * function returns false.
     146             :  *
     147             :  * \return true if the process is still running.
     148             :  */
     149           8 : bool child_status::is_running() const
     150             : {
     151           8 :     return !is_exited()
     152           0 :         && !is_signaled()
     153           8 :         && !is_stopped();
     154             : }
     155             : 
     156             : 
     157             : /** \brief The process terminated cleanly, with a call to exit().
     158             :  *
     159             :  * This function returns true if the child process ended by calling
     160             :  * the exit() function.
     161             :  *
     162             :  * You can further call the exit_code() function to retrieve the
     163             :  * exit code returned by that process (a number between 0 and 255).
     164             :  *
     165             :  * \return true if the process cleanly exited.
     166             :  */
     167          32 : bool child_status::is_exited() const
     168             : {
     169          32 :     return f_info.si_code == CLD_EXITED;
     170             : }
     171             : 
     172             : 
     173             : /** \brief Whether the process terminated because of a signal.
     174             :  *
     175             :  * After receiving a signal which kills the process, this function returns
     176             :  * true. In most cases, we do not expect children to exit with a signal,
     177             :  * but if that happens, this is how you can detect the matter.
     178             :  *
     179             :  * You can further call the is_core_dumped() to detect whether a coredump
     180             :  * was generated and terminate_signal() to get the signal number that
     181             :  * terminated this process.
     182             :  *
     183             :  * \return true if the process was terminated because of a signal.
     184             :  */
     185           0 : bool child_status::is_signaled() const
     186             : {
     187           0 :     return f_info.si_code == CLD_KILLED || f_info.si_code == CLD_DUMPED;
     188             : }
     189             : 
     190             : 
     191             : /** \brief Whether a core dumped was generated.
     192             :  *
     193             :  * When a process receives a signal, it can be asked to generate a core
     194             :  * dump. In most cases, the core dump size is set to 0 so nothing actually
     195             :  * gets saved to disk. So this flag may be true, but it doesn't mean you
     196             :  * will find an actual coredump file in your folder.
     197             :  *
     198             :  * \return true if the process was signaled and a core dump generated.
     199             :  */
     200           0 : bool child_status::is_core_dumped() const
     201             : {
     202           0 :     return f_info.si_code == CLD_DUMPED;
     203             : }
     204             : 
     205             : 
     206             : /** \brief The process received a signal to stop.
     207             :  *
     208             :  * A SIGSTOP or a trace signal (i.e. as in a debugger). The process is
     209             :  * still in memory but it is not currently running.
     210             :  *
     211             :  * You can further call the stop_signal() to know the signal used to
     212             :  * stop this process.
     213             :  *
     214             :  * \return true if the process stopped.
     215             :  */
     216           0 : bool child_status::is_stopped() const
     217             : {
     218             :     // TODO: have a separate is_trapped()
     219           0 :     return f_info.si_code == CLD_STOPPED || f_info.si_code == CLD_TRAPPED;
     220             : }
     221             : 
     222             : 
     223             : /** \brief The process was sent the SIGCONT signal.
     224             :  *
     225             :  * The process was previously stopped by a SIGSTOP or a trap or some other
     226             :  * similar signal. It was then continued. This signals the continuation.
     227             :  *
     228             :  * \return true if the process was signaled to continue.
     229             :  */
     230           0 : bool child_status::is_continued() const
     231             : {
     232           0 :     return f_info.si_code == CLD_CONTINUED;
     233             : }
     234             : 
     235             : 
     236             : /** \brief Transform the status in a mask.
     237             :  *
     238             :  * This function transforms this status in a mask. This is used to know
     239             :  * which callback to call whenever an event occurs.
     240             :  *
     241             :  * The function returns 0 if the current status is not properly understood.
     242             :  *
     243             :  * \return The mask representing this child status.
     244             :  */
     245           8 : flag_t child_status::status_mask() const
     246             : {
     247           8 :     if(is_running())
     248             :     {
     249           0 :         return SIGNAL_CHILD_FLAG_RUNNING;
     250             :     }
     251             : 
     252           8 :     if(is_exited())
     253             :     {
     254           8 :         return SIGNAL_CHILD_FLAG_EXITED;
     255             :     }
     256             : 
     257           0 :     if(is_signaled())
     258             :     {
     259           0 :         return SIGNAL_CHILD_FLAG_SIGNALED;
     260             :     }
     261             : 
     262           0 :     if(is_stopped())
     263             :     {
     264           0 :         return SIGNAL_CHILD_FLAG_STOPPED;
     265             :     }
     266             : 
     267           0 :     if(is_continued())
     268             :     {
     269           0 :         return SIGNAL_CHILD_FLAG_CONTINUED;
     270             :     }
     271             : 
     272             :     // invalid / unknown / not understood status
     273             :     //
     274           0 :     return 0;
     275             : }
     276             : 
     277             : 
     278             : /** \brief The exit code of the child process.
     279             :  *
     280             :  * This function return the exit code as returned by the child process by
     281             :  * returning from the main() function or explicitly calling one of the
     282             :  * exit() functions.
     283             :  *
     284             :  * The function returns -1 if the process did not exit normally or is
     285             :  * still running. Note that the exit() function can only return a number
     286             :  * between 0 and 255.
     287             :  *
     288             :  * \return The exit code.
     289             :  */
     290           8 : int child_status::exit_code() const
     291             : {
     292           8 :     if(is_exited())
     293             :     {
     294           8 :         return f_info.si_status;
     295             :     }
     296           0 :     return -1;
     297             : }
     298             : 
     299             : 
     300             : /** \brief The signal that terminated the process.
     301             :  *
     302             :  * This function returns the signal that terminated this process if
     303             :  * it was terminated by a signal.
     304             :  *
     305             :  * If the process was not terminated by a signal (or is still running)
     306             :  * then the function returns -1.
     307             :  *
     308             :  * \return The signal that terminated the concerned child process.
     309             :  */
     310           0 : int child_status::terminate_signal() const
     311             : {
     312           0 :     if(is_signaled())
     313             :     {
     314           0 :         return f_info.si_status;
     315             :     }
     316           0 :     return -1;
     317             : }
     318             : 
     319             : 
     320             : /** \brief Return the signal used to stop the process.
     321             :  *
     322             :  * This function returns the signal number used to stop the process.
     323             :  *
     324             :  * If the process is not stopped, then the function returns -1.
     325             :  *
     326             :  * \return The signal used to stop the process.
     327             :  */
     328           0 : int child_status::stop_signal() const
     329             : {
     330           0 :     if(is_stopped())
     331             :     {
     332           0 :         return f_info.si_status;
     333             :     }
     334             : 
     335           0 :     return -1;
     336             : }
     337             : 
     338             : 
     339             : 
     340             : 
     341             : 
     342             : 
     343             : 
     344             : 
     345             : 
     346             : /** \brief Initializes the signal_child object.
     347             :  *
     348             :  * This signal_child object is a singleton. It is used to listen on the
     349             :  * SIGCHLD signal via a ed::signal connection. You can listen for the
     350             :  * death of your child by listening for its pid_t. It will get called
     351             :  * on various events (running, exited, signaled, stopped, continued).
     352             :  */
     353           1 : signal_child::signal_child()
     354           1 :     : signal(SIGCHLD)
     355             : {
     356           1 : }
     357             : 
     358             : 
     359             : /** \brief Restore the SIGCHLD signal as it was.
     360             :  *
     361             :  * Since the signal_child is a singleton, this function only gets called
     362             :  * when you exit your process. It is expected that all the children you
     363             :  * were listening on died before you call this function.
     364             :  */
     365           2 : signal_child::~signal_child()
     366             : {
     367           2 : }
     368             : 
     369             : 
     370             : /** \brief Get the pointer to the signal_child singleton.
     371             :  *
     372             :  * This function retrieves the pointer to the signal_child singleton.
     373             :  *
     374             :  * The first time you call the function, the singleton gets created.
     375             :  *
     376             :  * \return The singleton pointer.
     377             :  */
     378           6 : signal_child::pointer_t signal_child::get_instance()
     379             : {
     380          12 :     cppthread::guard lock(*cppthread::g_system_mutex);
     381             : 
     382           6 :     if(g_signal_child == nullptr)
     383             :     {
     384             :         // WARNING: can't use std::make_shared<> because constructor is
     385             :         //          private (since we have a singleton)
     386             :         //
     387           1 :         g_signal_child.reset(new signal_child());
     388             :     }
     389             : 
     390          12 :     return g_signal_child;
     391             : }
     392             : 
     393             : 
     394             : /** \brief Add this connection to the communicator.
     395             :  *
     396             :  * \note
     397             :  * You should not call this function. It automatically gets called when
     398             :  * you add a listener (see add_listener() in this class). After all, you
     399             :  * do not need to listen to anything until you ask for it and similarly
     400             :  * the remove gets called automatically when the listener gets removed
     401             :  * (which again is automatic once the child dies).
     402             :  *
     403             :  * This function adds this connection to the communicator. This function can
     404             :  * be called any number of times. It will increase a counter which will
     405             :  * then be decremented by the remove_connection().
     406             :  *
     407             :  * This is used because the communicator::add_connection() will not add the
     408             :  * signal_child connection more than once because many different functions
     409             :  * and libraries may need to add it and these would not know whether to
     410             :  * add or remove the connection and in the end we want it to be properly
     411             :  * accounted for.
     412             :  *
     413             :  * You actually will not be able to add it directly using the
     414             :  * communicator::add_connection(). It will throw if you try to do that.
     415             :  * Instead, you must call this function.
     416             :  */
     417           8 : void signal_child::add_connection()
     418             : {
     419           8 :     if(f_count == 0)
     420             :     {
     421             :         // add the connection to the communicator
     422             :         //
     423          12 :         snapdev::safe_variable safe(f_adding_to_communicator, true, false);
     424           6 :         ed::communicator::instance()->add_connection(shared_from_this());
     425             :     }
     426           8 :     ++f_count;
     427           8 : }
     428             : 
     429             : 
     430             : /** \brief Remove the connection from the communicator.
     431             :  *
     432             :  * \note
     433             :  * You do not need to call this function. The listener callback function
     434             :  * gets called and assuming the child died (i.e. a child that received a
     435             :  * signal that killed it or one that called _exit() to terminate) this
     436             :  * function gets called automatically.
     437             :  *
     438             :  * You must call this function to remove the signal child for each time you
     439             :  * added it with the corresponding add_connection().
     440             :  *
     441             :  * This function is used along the add_connection() because the basic
     442             :  * add & remove functions of the communicator do not allow you to add
     443             :  * the same connection more than once (which makes sense), yet the signal
     444             :  * may be added and removed by many different systems. The means it would
     445             :  * be unlikely that you would know of all the adds and all the removes in
     446             :  * one place.
     447             :  *
     448             :  * \exception event_dispatcher_count_mismatch
     449             :  * The remove_connection() function must be called exactly once for each
     450             :  * call to the add_connection() function. If called more than this many
     451             :  * times, then this exception is raised.
     452             :  */
     453           8 : void signal_child::remove_connection()
     454             : {
     455           8 :     if(f_count == 0)
     456             :     {
     457             :         throw event_dispatcher_count_mismatch(
     458             :             "the signal_child::remove_connection() was called more times"
     459           0 :             " than the add_connection()");
     460             :     }
     461             : 
     462           8 :     --f_count;
     463           8 :     if(f_count == 0)
     464             :     {
     465             :         // remove the connection to the communicator
     466             :         //
     467          12 :         snapdev::safe_variable safe(f_removing_to_communicator, true, false);
     468           6 :         ed::communicator::instance()->remove_connection(shared_from_this());
     469             :     }
     470           8 : }
     471             : 
     472             : 
     473             : /** \brief Process the SIGCHLD signal.
     474             :  *
     475             :  * This function process the SIGCHLD signal. Note that the function is
     476             :  * expected to be called once per SIGCHLD signaled, however, if several
     477             :  * children die \em simultaneously, then it would not work to process
     478             :  * only one child at a time. For this reason, we instead process all the
     479             :  * children that have died in one go and if we get called additional times
     480             :  * nothing happens.
     481             :  */
     482          16 : void signal_child::process_signal()
     483             : {
     484             :     for(;;)
     485             :     {
     486             :         // Note: to retrieve the rusage() of the process, we could use our
     487             :         //       process_info, however, that has to be done while the
     488             :         //       process is still a zombie... if the callback wants to
     489             :         //       do that, then it is possible since the call here uses
     490             :         //       the WNOWAIT (which means the zombie stays until later)
     491             :         //
     492          16 :         siginfo_t info = {};
     493             :         int const r(waitid(
     494             :                   P_ALL
     495             :                 , 0
     496             :                 , &info
     497          16 :                 , WEXITED | WSTOPPED | WCONTINUED | WNOHANG | WNOWAIT));
     498          16 :         if(r != 0)
     499             :         {
     500             :             // if there are no more children, we get an ECHILD error
     501             :             // and we can ignore those
     502             :             //
     503           6 :             if(errno != ECHILD)
     504             :             {
     505           0 :                 int const e(errno);
     506           0 :                 SNAP_LOG_ERROR
     507           0 :                     << "waitid() failed to wait for a child: "
     508             :                     << e
     509             :                     << ", "
     510           0 :                     << strerror(e)
     511             :                     << SNAP_LOG_SEND;
     512             :             }
     513           6 :             return;
     514             :         }
     515             : 
     516          10 :         child_status const status(info);
     517          10 :         if(status.child_pid() == 0)
     518             :         {
     519             :             // no more state changes
     520             :             //
     521           2 :             break;
     522             :         }
     523             : 
     524          16 :         callback_t::list_t listeners;
     525             :         {
     526          16 :             cppthread::guard lock(f_mutex);
     527           8 :             listeners = f_listeners;
     528             :         }
     529             : 
     530           8 :         flag_t const mask(status.status_mask());
     531          18 :         for(auto & listener : listeners)
     532             :         {
     533          20 :             if(listener.f_child == status.child_pid()
     534          10 :             && (listener.f_flags & mask) != 0)
     535             :             {
     536           8 :                 listener.f_callback(status);
     537             :             }
     538             :         }
     539             : 
     540          16 :         if(status.is_exited()
     541           8 :         || status.is_signaled())
     542             :         {
     543             :             // release the zombie, we're done
     544             :             //
     545           8 :             siginfo_t ignore = {};
     546           8 :             snapdev::NOT_USED(waitid(P_PID, status.child_pid(), &ignore, WEXITED));
     547             : 
     548           8 :             remove_listener(status.child_pid());
     549             :         }
     550           8 :     }
     551             : }
     552             : 
     553             : 
     554             : /** \brief The connection was added to the communicator.
     555             :  *
     556             :  * The connection was added, make sure it was by us (through our own
     557             :  * add_connection() function).
     558             :  *
     559             :  * \warning
     560             :  * The test in this function works only for the very first connection.
     561             :  * After that, the communicator prevents this callback from happening.
     562             :  *
     563             :  * \exception event_dispatcher_runtime_error
     564             :  * The signal_child connection must be added by the add_connection() function.
     565             :  * If you directly call the communicator::add_connection(), then this
     566             :  * exception is raised.
     567             :  */
     568           6 : void signal_child::connection_added()
     569             : {
     570           6 :     if(!f_adding_to_communicator)
     571             :     {
     572             :         throw event_dispatcher_runtime_error(
     573             :             "it looks like you directly called communicator::add_connection()"
     574             :             " with the signal_child connection. This is not allowed. Make sure"
     575           0 :             " to call the signal_child::add_connection() instead.");
     576             :     }
     577           6 : }
     578             : 
     579             : 
     580             : /** \brief The connection was removed from the communicator.
     581             :  *
     582             :  * The connection was removed, make sure it was by us (through our own
     583             :  * remove_connection() function).
     584             :  *
     585             :  * \exception event_dispatcher_runtime_error
     586             :  * The signal_child connection must be added by the add_connection() function.
     587             :  * If you directly call the communicator::add_connection(), then this
     588             :  * exception is raised.
     589             :  */
     590           6 : void signal_child::connection_removed()
     591             : {
     592           6 :     if(!f_removing_to_communicator)
     593             :     {
     594             :         throw event_dispatcher_runtime_error(
     595             :             "it looks like you directly called communicator::remove_connection()"
     596             :             " with the signal_child connection. This is not allowed. Make sure"
     597           0 :             " to call the signal_child::remove_connection() instead.");
     598             :     }
     599           6 : }
     600             : 
     601             : 
     602             : /** \brief Add a listener function.
     603             :  *
     604             :  * This function adds a listener so when a SIGCHLD occurs with the specified
     605             :  * \p child, the \p callback gets called.
     606             :  *
     607             :  * You can further define which signals you are interested in. In most
     608             :  * likelihood, only the ed::SIGNAL_CHILD_FLAG_EXITED and the
     609             :  * ed::SIGNAL_CHILD_FLAG_SIGNALED are going to be useful (i.e. to get
     610             :  * called when the process dies).
     611             :  *
     612             :  * At the time your \p callback function is called, the process is still
     613             :  * up (as a zombie). This gives you the opportunity to gather information
     614             :  * about the process. You can do so with the process_info class using
     615             :  * the callback child_info::child_pid() function to get the necessary
     616             :  * process identifier. (TODO: test that this is indeed true)
     617             :  *
     618             :  * The function can be called multiple times with the same child PID to
     619             :  * add multiple callbacks (useful if you vary the mask parameter).
     620             :  *
     621             :  * This function automatically calls the add_connection() function any
     622             :  * time it succeeeds in adding a new child/callback listener.
     623             :  *
     624             :  * \exception event_dispatcher_invalid_parameter
     625             :  * The mask cannot be set to zero, the child identifier must be positive,
     626             :  * the callback pointer cannot be nullptr.
     627             :  *
     628             :  * \param[in] child  The process identifier (pid_t) of the child to listen on.
     629             :  * \param[in] callback  A function pointer to call when the event occurs.
     630             :  * \param[in] mask  A mask representing the events you are interested in.
     631             :  */
     632           8 : void signal_child::add_listener(
     633             :           pid_t child
     634             :         , func_t callback
     635             :         , flag_t mask)
     636             : {
     637           8 :     if(child <= 0)
     638             :     {
     639             :         throw event_dispatcher_invalid_parameter(
     640             :               "the child parameter must be a valid pid_t (not "
     641           0 :             + std::to_string(child)
     642           0 :             + ")");
     643             :     }
     644           8 :     if(callback == nullptr)
     645             :     {
     646           0 :         throw event_dispatcher_invalid_parameter("callback cannot be nullptr");
     647             :     }
     648           8 :     if(mask == 0)
     649             :     {
     650           0 :         throw event_dispatcher_invalid_parameter("mask cannot be set to zero");
     651             :     }
     652             : 
     653          16 :     cppthread::guard lock(f_mutex);
     654           8 :     f_listeners.emplace(f_listeners.end(), callback_t{ child, callback, mask });
     655           8 :     add_connection();
     656           8 : }
     657             : 
     658             : 
     659             : /** \brief Remove all the listeners for a specific child.
     660             :  *
     661             :  * This function is the converse of the add_listener(). It is used to
     662             :  * remove a listener from the list maintained by the signal_child
     663             :  * singleton.
     664             :  *
     665             :  * This function automatically gets called whenever the signal_child
     666             :  * detects the death of a child and finds a corresponding listener.
     667             :  *
     668             :  * Further, this function automatically calls the remove_connection()
     669             :  * function when it indeeds removes the specifed \p child. If found
     670             :  * more than once, then it gets called once for each instance.
     671             :  *
     672             :  * \warning
     673             :  * All the listener that use the specified \p child parameter are
     674             :  * removed from the list of listeners.
     675             :  *
     676             :  * \note
     677             :  * Whenever you create a child with fork(), make sure to add a listener
     678             :  * right then before returning to the communicator::run() loop. That
     679             :  * way everything happens in the right order. Although the functions
     680             :  * handling the listener are thread safe, a fork() is not. So you
     681             :  * should not use both together making everything very safe.
     682             :  *
     683             :  * \param[in] child  The child for which the listener has to be removed.
     684             :  */
     685           8 : void signal_child::remove_listener(pid_t child)
     686             : {
     687          16 :     cppthread::guard lock(f_mutex);
     688             : 
     689          18 :     for(auto it(f_listeners.begin()); it != f_listeners.end(); )
     690             :     {
     691          10 :         if(it->f_child == child)
     692             :         {
     693           8 :             it = f_listeners.erase(it);
     694           8 :             remove_connection();
     695             :         }
     696             :         else
     697             :         {
     698           2 :             ++it;
     699             :         }
     700             :     }
     701           8 : }
     702             : 
     703             : 
     704             : 
     705           6 : } // namespace ed
     706             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13