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

Generated by: LCOV version 1.13