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: 2021-09-19 09:06:58 Functions: 19 26 73.1 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13