Minor fixes to tests.
[oweals/dinit.git] / src / dasynq / dasynq-childproc.h
1 #include <signal.h>
2 #include "dasynq-btree_set.h"
3
4 namespace dasynq {
5
6 namespace dprivate {
7
8 // Map of pid_t to void *, with possibility of reserving entries so that mappings can
9 // be later added with no danger of allocator exhaustion (bad_alloc).
10 class pid_map
11 {
12     using bmap_t = btree_set<void *, pid_t>;
13     bmap_t b_map;
14     
15     public:
16     using pid_handle_t = bmap_t::handle_t;
17     
18     // Map entry: present (bool), data (void *)
19     using entry = std::pair<bool, void *>;
20
21     entry get(pid_t key) noexcept
22     {
23         auto it = b_map.find(key);
24         if (it == nullptr) {
25             return entry(false, nullptr);
26         }
27         return entry(true, b_map.node_data(*it));
28     }
29     
30     entry remove(pid_t key) noexcept
31     {
32         auto it = b_map.find(key);
33         if (it == nullptr) {
34             return entry(false, nullptr);
35         }
36         b_map.remove(*it);
37         return entry(true, b_map.node_data(*it));
38     }
39     
40     void remove(pid_handle_t &hndl)
41     {
42         if (b_map.is_queued(hndl)) {
43             b_map.remove(hndl);
44         }
45     }
46
47     // Throws bad_alloc on reservation failure
48     void reserve(pid_handle_t &hndl)
49     {
50         b_map.allocate(hndl);
51     }
52     
53     void unreserve(pid_handle_t &hndl) noexcept
54     {
55         b_map.deallocate(hndl);
56     }
57     
58     void add(pid_handle_t &hndl, pid_t key, void *val) // throws std::bad_alloc
59     {
60         reserve(hndl);
61         b_map.node_data(hndl) = val;
62         b_map.insert(hndl, key);
63     }
64     
65     void add_from_reserve(pid_handle_t &hndl, pid_t key, void *val) noexcept
66     {
67         b_map.node_data(hndl) = val;
68         b_map.insert(hndl, key);
69     }
70 };
71
72 inline void sigchld_handler(int signum)
73 {
74     // If SIGCHLD has no handler (is ignored), SIGCHLD signals will
75     // not be queued for terminated child processes. (On Linux, the
76     // default disposition for SIGCHLD is to be ignored but *not* have
77     // this behavior, which seems inconsistent. Setting a handler doesn't
78     // hurt in any case).
79 }
80
81 } // dprivate namespace
82
83 using pid_watch_handle_t = dprivate::pid_map::pid_handle_t;
84
85 template <class Base> class child_proc_events : public Base
86 {
87     public:
88     using reaper_mutex_t = typename Base::mutex_t;
89
90     class traits_t : public Base::traits_t
91     {
92         public:
93         constexpr static bool supports_childwatch_reservation = true;
94     };
95
96     private:
97     dprivate::pid_map child_waiters;
98     reaper_mutex_t reaper_lock; // used to prevent reaping while trying to signal a process
99     
100     protected:
101     using sigdata_t = typename traits_t::sigdata_t;
102     
103     template <typename T>
104     bool receive_signal(T & loop_mech, sigdata_t &siginfo, void *userdata)
105     {
106         if (siginfo.get_signo() == SIGCHLD) {
107             int status;
108             pid_t child;
109             reaper_lock.lock();
110             while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
111                 auto ent = child_waiters.remove(child);
112                 if (ent.first) {
113                     Base::receive_child_stat(child, status, ent.second);
114                 }
115             }
116             reaper_lock.unlock();
117             return false; // leave signal watch enabled
118         }
119         else {
120             return Base::receive_signal(loop_mech, siginfo, userdata);
121         }
122     }
123     
124     public:
125     void reserve_child_watch_nolock(pid_watch_handle_t &handle)
126     {
127         child_waiters.reserve(handle);
128     }
129     
130     void unreserve_child_watch(pid_watch_handle_t &handle) noexcept
131     {
132         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
133         unreserve_child_watch_nolock(handle);
134     }
135     
136     void unreserve_child_watch_nolock(pid_watch_handle_t &handle) noexcept
137     {
138         child_waiters.unreserve(handle);
139     }
140
141     void add_child_watch_nolock(pid_watch_handle_t &handle, pid_t child, void *val)
142     {
143         child_waiters.add(handle, child, val);
144     }
145     
146     void add_reserved_child_watch(pid_watch_handle_t &handle, pid_t child, void *val) noexcept
147     {
148         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
149         child_waiters.add_from_reserve(handle, child, val);
150     }
151
152     void add_reserved_child_watch_nolock(pid_watch_handle_t &handle, pid_t child, void *val) noexcept
153     {
154         child_waiters.add_from_reserve(handle, child, val);
155     }
156     
157     // Stop watching a child, but retain watch reservation
158     void stop_child_watch(pid_watch_handle_t &handle) noexcept
159     {
160         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
161         child_waiters.remove(handle);
162     }
163
164     void remove_child_watch(pid_watch_handle_t &handle) noexcept
165     {
166         std::lock_guard<decltype(Base::lock)> guard(Base::lock);
167         remove_child_watch_nolock(handle);
168     }
169
170     void remove_child_watch_nolock(pid_watch_handle_t &handle) noexcept
171     {
172         child_waiters.remove(handle);
173         child_waiters.unreserve(handle);
174     }
175     
176     // Get the reaper lock, which can be used to ensure that a process is not reaped while attempting to
177     // signal it.
178     reaper_mutex_t &get_reaper_lock() noexcept
179     {
180         return reaper_lock;
181     }
182
183     template <typename T> void init(T *loop_mech)
184     {
185         // Mask SIGCHLD:
186         sigset_t sigmask;
187         this->sigmaskf(SIG_UNBLOCK, nullptr, &sigmask);
188         sigaddset(&sigmask, SIGCHLD);
189         this->sigmaskf(SIG_SETMASK, &sigmask, nullptr);
190
191         // On some systems a SIGCHLD handler must be established, or SIGCHLD will not be
192         // generated:
193         struct sigaction chld_action;
194         chld_action.sa_handler = dprivate::sigchld_handler;
195         sigemptyset(&chld_action.sa_mask);
196         chld_action.sa_flags = 0;
197         sigaction(SIGCHLD, &chld_action, nullptr);
198         loop_mech->add_signal_watch(SIGCHLD, nullptr);
199         Base::init(loop_mech);
200     }
201 };
202
203
204 } // end namespace